mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 19:48:08 +00:00
feat: Add agent capacity controllers (#12200)
## Linear reference: https://linear.app/chatwoot/issue/CW-4649/re-imagine-assignments ## Description This PR introduces the foundation for Assignment V2 system by implementing agent_capacity and their association with inboxes and users. ## Type of change - [ ] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? Test Coverage: - Controller specs for assignment policies CRUD operations - Enterprise-specific specs for balanced assignment order - Model specs for community/enterprise separation ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
committed by
GitHub
parent
39dfa35229
commit
ad90deb709
@@ -372,6 +372,8 @@ en:
|
|||||||
|
|
||||||
Transcript:
|
Transcript:
|
||||||
%{format_messages}
|
%{format_messages}
|
||||||
|
agent_capacity_policy:
|
||||||
|
inbox_already_assigned: 'Inbox has already been assigned to this policy'
|
||||||
portals:
|
portals:
|
||||||
send_instructions:
|
send_instructions:
|
||||||
email_required: 'Email is required'
|
email_required: 'Email is required'
|
||||||
|
|||||||
@@ -97,6 +97,12 @@ Rails.application.routes.draw do
|
|||||||
end
|
end
|
||||||
resources :sla_policies, only: [:index, :create, :show, :update, :destroy]
|
resources :sla_policies, only: [:index, :create, :show, :update, :destroy]
|
||||||
resources :custom_roles, only: [:index, :create, :show, :update, :destroy]
|
resources :custom_roles, only: [:index, :create, :show, :update, :destroy]
|
||||||
|
resources :agent_capacity_policies, only: [:index, :create, :show, :update, :destroy] do
|
||||||
|
scope module: :agent_capacity_policies do
|
||||||
|
resources :users, only: [:index, :create, :destroy]
|
||||||
|
resources :inbox_limits, only: [:create, :update, :destroy]
|
||||||
|
end
|
||||||
|
end
|
||||||
resources :campaigns, only: [:index, :create, :show, :update, :destroy]
|
resources :campaigns, only: [:index, :create, :show, :update, :destroy]
|
||||||
resources :dashboard_apps, only: [:index, :show, :create, :update, :destroy]
|
resources :dashboard_apps, only: [:index, :show, :create, :update, :destroy]
|
||||||
namespace :channels do
|
namespace :channels do
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
class Api::V1::Accounts::AgentCapacityPolicies::InboxLimitsController < Api::V1::Accounts::EnterpriseAccountsController
|
||||||
|
before_action -> { check_authorization(AgentCapacityPolicy) }
|
||||||
|
before_action :fetch_policy
|
||||||
|
before_action :fetch_inbox, only: [:create]
|
||||||
|
before_action :fetch_inbox_limit, only: [:update, :destroy]
|
||||||
|
before_action :validate_no_duplicate, only: [:create]
|
||||||
|
|
||||||
|
def create
|
||||||
|
@inbox_limit = @agent_capacity_policy.inbox_capacity_limits.create!(
|
||||||
|
inbox: @inbox,
|
||||||
|
conversation_limit: permitted_params[:conversation_limit]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@inbox_limit.update!(conversation_limit: permitted_params[:conversation_limit])
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@inbox_limit.destroy!
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def fetch_policy
|
||||||
|
@agent_capacity_policy = Current.account.agent_capacity_policies.find(params[:agent_capacity_policy_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_inbox
|
||||||
|
@inbox = Current.account.inboxes.find(permitted_params[:inbox_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_inbox_limit
|
||||||
|
@inbox_limit = @agent_capacity_policy.inbox_capacity_limits.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_no_duplicate
|
||||||
|
return unless @agent_capacity_policy.inbox_capacity_limits.exists?(inbox: @inbox)
|
||||||
|
|
||||||
|
render_could_not_create_error(I18n.t('agent_capacity_policy.inbox_already_assigned'))
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted_params
|
||||||
|
params.permit(:inbox_id, :conversation_limit)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
class Api::V1::Accounts::AgentCapacityPolicies::UsersController < Api::V1::Accounts::EnterpriseAccountsController
|
||||||
|
before_action -> { check_authorization(AgentCapacityPolicy) }
|
||||||
|
before_action :fetch_policy
|
||||||
|
before_action :fetch_user, only: [:destroy]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@users = Current.account.users.joins(:account_users)
|
||||||
|
.where(account_users: { agent_capacity_policy_id: @agent_capacity_policy.id })
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@account_user = Current.account.account_users.find_by!(user_id: permitted_params[:user_id])
|
||||||
|
@account_user.update!(agent_capacity_policy: @agent_capacity_policy)
|
||||||
|
@user = @account_user.user
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@account_user.update!(agent_capacity_policy: nil)
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def fetch_policy
|
||||||
|
@agent_capacity_policy = Current.account.agent_capacity_policies.find(params[:agent_capacity_policy_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_user
|
||||||
|
@account_user = Current.account.account_users.find_by!(user_id: params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted_params
|
||||||
|
params.permit(:user_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
class Api::V1::Accounts::AgentCapacityPoliciesController < Api::V1::Accounts::EnterpriseAccountsController
|
||||||
|
before_action :check_authorization
|
||||||
|
before_action :fetch_policy, only: [:show, :update, :destroy]
|
||||||
|
|
||||||
|
def index
|
||||||
|
@agent_capacity_policies = Current.account.agent_capacity_policies
|
||||||
|
end
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@agent_capacity_policy = Current.account.agent_capacity_policies.create!(permitted_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@agent_capacity_policy.update!(permitted_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@agent_capacity_policy.destroy!
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def permitted_params
|
||||||
|
params.require(:agent_capacity_policy).permit(
|
||||||
|
:name,
|
||||||
|
:description,
|
||||||
|
exclusion_rules: [:overall_capacity, { hours: [], days: [] }]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fetch_policy
|
||||||
|
@agent_capacity_policy = Current.account.agent_capacity_policies.find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
||||||
27
enterprise/app/models/agent_capacity_policy.rb
Normal file
27
enterprise/app/models/agent_capacity_policy.rb
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: agent_capacity_policies
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# description :text
|
||||||
|
# exclusion_rules :jsonb not null
|
||||||
|
# name :string(255) not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_agent_capacity_policies_on_account_id (account_id)
|
||||||
|
#
|
||||||
|
class AgentCapacityPolicy < ApplicationRecord
|
||||||
|
MAX_NAME_LENGTH = 255
|
||||||
|
|
||||||
|
belongs_to :account
|
||||||
|
has_many :inbox_capacity_limits, dependent: :destroy
|
||||||
|
has_many :inboxes, through: :inbox_capacity_limits
|
||||||
|
has_many :account_users, dependent: :nullify
|
||||||
|
|
||||||
|
validates :name, presence: true, length: { maximum: MAX_NAME_LENGTH }
|
||||||
|
validates :account, presence: true
|
||||||
|
end
|
||||||
@@ -5,6 +5,7 @@ module Enterprise::Concerns::Account
|
|||||||
has_many :sla_policies, dependent: :destroy_async
|
has_many :sla_policies, dependent: :destroy_async
|
||||||
has_many :applied_slas, dependent: :destroy_async
|
has_many :applied_slas, dependent: :destroy_async
|
||||||
has_many :custom_roles, dependent: :destroy_async
|
has_many :custom_roles, dependent: :destroy_async
|
||||||
|
has_many :agent_capacity_policies, dependent: :destroy_async
|
||||||
|
|
||||||
has_many :captain_assistants, dependent: :destroy_async, class_name: 'Captain::Assistant'
|
has_many :captain_assistants, dependent: :destroy_async, class_name: 'Captain::Assistant'
|
||||||
has_many :captain_assistant_responses, dependent: :destroy_async, class_name: 'Captain::AssistantResponse'
|
has_many :captain_assistant_responses, dependent: :destroy_async, class_name: 'Captain::AssistantResponse'
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ module Enterprise::Concerns::AccountUser
|
|||||||
|
|
||||||
included do
|
included do
|
||||||
belongs_to :custom_role, optional: true
|
belongs_to :custom_role, optional: true
|
||||||
|
belongs_to :agent_capacity_policy, optional: true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
24
enterprise/app/models/inbox_capacity_limit.rb
Normal file
24
enterprise/app/models/inbox_capacity_limit.rb
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: inbox_capacity_limits
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# conversation_limit :integer not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# agent_capacity_policy_id :bigint not null
|
||||||
|
# inbox_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# idx_on_agent_capacity_policy_id_inbox_id_71c7ec4caf (agent_capacity_policy_id,inbox_id) UNIQUE
|
||||||
|
# index_inbox_capacity_limits_on_agent_capacity_policy_id (agent_capacity_policy_id)
|
||||||
|
# index_inbox_capacity_limits_on_inbox_id (inbox_id)
|
||||||
|
#
|
||||||
|
class InboxCapacityLimit < ApplicationRecord
|
||||||
|
belongs_to :agent_capacity_policy
|
||||||
|
belongs_to :inbox
|
||||||
|
|
||||||
|
validates :conversation_limit, presence: true, numericality: { greater_than: 0, only_integer: true }
|
||||||
|
validates :inbox_id, uniqueness: { scope: :agent_capacity_policy_id }
|
||||||
|
end
|
||||||
21
enterprise/app/policies/agent_capacity_policy_policy.rb
Normal file
21
enterprise/app/policies/agent_capacity_policy_policy.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
class AgentCapacityPolicyPolicy < ApplicationPolicy
|
||||||
|
def index?
|
||||||
|
@account_user.administrator?
|
||||||
|
end
|
||||||
|
|
||||||
|
def create?
|
||||||
|
@account_user.administrator?
|
||||||
|
end
|
||||||
|
|
||||||
|
def show?
|
||||||
|
@account_user.administrator?
|
||||||
|
end
|
||||||
|
|
||||||
|
def update?
|
||||||
|
@account_user.administrator?
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy?
|
||||||
|
@account_user.administrator?
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
json.partial! 'api/v1/models/agent_capacity_policy', formats: [:json],
|
||||||
|
agent_capacity_policy: @agent_capacity_policy
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
json.id @inbox_limit.id
|
||||||
|
json.inbox_id @inbox_limit.inbox_id
|
||||||
|
json.agent_capacity_policy_id @inbox_limit.agent_capacity_policy_id
|
||||||
|
json.conversation_limit @inbox_limit.conversation_limit
|
||||||
|
json.created_at @inbox_limit.created_at.to_i
|
||||||
|
json.updated_at @inbox_limit.updated_at.to_i
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
json.id @inbox_limit.id
|
||||||
|
json.inbox_id @inbox_limit.inbox_id
|
||||||
|
json.inbox_name @inbox_limit.inbox.name
|
||||||
|
json.agent_capacity_policy_id @agent_capacity_policy.id
|
||||||
|
json.conversation_limit @inbox_limit.conversation_limit
|
||||||
|
json.created_at @inbox_limit.created_at.to_i
|
||||||
|
json.updated_at @inbox_limit.updated_at.to_i
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
json.array! @agent_capacity_policies do |policy|
|
||||||
|
json.partial! 'api/v1/models/agent_capacity_policy', formats: [:json],
|
||||||
|
agent_capacity_policy: policy
|
||||||
|
end
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
json.partial! 'api/v1/models/agent_capacity_policy', formats: [:json],
|
||||||
|
agent_capacity_policy: @agent_capacity_policy
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
json.partial! 'api/v1/models/agent_capacity_policy', formats: [:json],
|
||||||
|
agent_capacity_policy: @agent_capacity_policy
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
json.partial! 'api/v1/models/user', resource: @user
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
json.array! @users do |user|
|
||||||
|
json.partial! 'api/v1/models/user', resource: user
|
||||||
|
end
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
json.id agent_capacity_policy.id
|
||||||
|
json.name agent_capacity_policy.name
|
||||||
|
json.description agent_capacity_policy.description
|
||||||
|
json.exclusion_rules agent_capacity_policy.exclusion_rules
|
||||||
|
json.created_at agent_capacity_policy.created_at.to_i
|
||||||
|
json.updated_at agent_capacity_policy.updated_at.to_i
|
||||||
|
json.account_id agent_capacity_policy.account_id
|
||||||
|
|
||||||
|
json.inbox_capacity_limits agent_capacity_policy.inbox_capacity_limits do |limit|
|
||||||
|
json.id limit.id
|
||||||
|
json.inbox_id limit.inbox_id
|
||||||
|
json.conversation_limit limit.conversation_limit
|
||||||
|
end
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Agent Capacity Policy Inbox Limits API', type: :request do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let!(:agent_capacity_policy) { create(:agent_capacity_policy, account: account) }
|
||||||
|
let!(:inbox) { create(:inbox, account: account) }
|
||||||
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
describe 'POST /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/inbox_limits' do
|
||||||
|
context 'when not admin' do
|
||||||
|
it 'requires admin role' do
|
||||||
|
post "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/inbox_limits",
|
||||||
|
params: { inbox_id: inbox.id, conversation_limit: 10 },
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when admin' do
|
||||||
|
it 'creates an inbox limit' do
|
||||||
|
post "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/inbox_limits",
|
||||||
|
params: { inbox_id: inbox.id, conversation_limit: 10 },
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
json_response = response.parsed_body
|
||||||
|
expect(json_response['conversation_limit']).to eq(10)
|
||||||
|
expect(json_response['inbox_id']).to eq(inbox.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'prevents duplicate inbox assignments' do
|
||||||
|
create(:inbox_capacity_limit, agent_capacity_policy: agent_capacity_policy, inbox: inbox)
|
||||||
|
|
||||||
|
post "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/inbox_limits",
|
||||||
|
params: { inbox_id: inbox.id, conversation_limit: 10 },
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
expect(response.parsed_body['error']).to eq(I18n.t('agent_capacity_policy.inbox_already_assigned'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'PUT /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/inbox_limits/{id}' do
|
||||||
|
let!(:inbox_limit) { create(:inbox_capacity_limit, agent_capacity_policy: agent_capacity_policy, inbox: inbox, conversation_limit: 5) }
|
||||||
|
|
||||||
|
context 'when admin' do
|
||||||
|
it 'updates the inbox limit' do
|
||||||
|
put "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/inbox_limits/#{inbox_limit.id}",
|
||||||
|
params: { conversation_limit: 15 },
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.parsed_body['conversation_limit']).to eq(15)
|
||||||
|
expect(inbox_limit.reload.conversation_limit).to eq(15)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'DELETE /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/inbox_limits/{id}' do
|
||||||
|
let!(:inbox_limit) { create(:inbox_capacity_limit, agent_capacity_policy: agent_capacity_policy, inbox: inbox) }
|
||||||
|
|
||||||
|
context 'when admin' do
|
||||||
|
it 'removes the inbox limit' do
|
||||||
|
delete "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/inbox_limits/#{inbox_limit.id}",
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:no_content)
|
||||||
|
expect(agent_capacity_policy.inbox_capacity_limits.find_by(id: inbox_limit.id)).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Agent Capacity Policy Users API', type: :request do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let!(:agent_capacity_policy) { create(:agent_capacity_policy, account: account) }
|
||||||
|
let!(:user) { create(:user, account: account, role: :agent) }
|
||||||
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
describe 'GET /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/users' do
|
||||||
|
context 'when admin' do
|
||||||
|
it 'returns assigned users' do
|
||||||
|
user.account_users.first.update!(agent_capacity_policy: agent_capacity_policy)
|
||||||
|
|
||||||
|
get "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/users",
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.parsed_body.first['id']).to eq(user.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/users' do
|
||||||
|
context 'when not admin' do
|
||||||
|
it 'requires admin role' do
|
||||||
|
post "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/users",
|
||||||
|
params: { user_id: user.id },
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when admin' do
|
||||||
|
it 'assigns user to the policy' do
|
||||||
|
post "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/users",
|
||||||
|
params: { user_id: user.id },
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(user.account_users.first.reload.agent_capacity_policy).to eq(agent_capacity_policy)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'DELETE /api/v1/accounts/{account.id}/agent_capacity_policies/{policy_id}/users/{id}' do
|
||||||
|
context 'when admin' do
|
||||||
|
before do
|
||||||
|
user.account_users.first.update!(agent_capacity_policy: agent_capacity_policy)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes user from the policy' do
|
||||||
|
delete "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}/users/#{user.id}",
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect(user.account_users.first.reload.agent_capacity_policy).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Agent Capacity Policies API', type: :request do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let!(:agent_capacity_policy) { create(:agent_capacity_policy, account: account) }
|
||||||
|
|
||||||
|
describe 'GET /api/v1/accounts/{account.id}/agent_capacity_policies' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/agent_capacity_policies"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated agent' do
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
it 'returns unauthorized for agent' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/agent_capacity_policies",
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an administrator' do
|
||||||
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||||
|
|
||||||
|
it 'returns all agent capacity policies' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/agent_capacity_policies",
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.parsed_body.first['id']).to eq(agent_capacity_policy.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET /api/v1/accounts/{account.id}/agent_capacity_policies/{id}' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated agent' do
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
|
||||||
|
it 'returns unauthorized for agent' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an administrator' do
|
||||||
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||||
|
|
||||||
|
it 'returns the agent capacity policy' do
|
||||||
|
get "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.parsed_body['id']).to eq(agent_capacity_policy.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/accounts/{account.id}/agent_capacity_policies' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post "/api/v1/accounts/#{account.id}/agent_capacity_policies"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||||
|
|
||||||
|
it 'returns unauthorized for agent' do
|
||||||
|
params = { agent_capacity_policy: { name: 'Test Policy' } }
|
||||||
|
|
||||||
|
post "/api/v1/accounts/#{account.id}/agent_capacity_policies",
|
||||||
|
params: params,
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a new agent capacity policy when administrator' do
|
||||||
|
params = {
|
||||||
|
agent_capacity_policy: {
|
||||||
|
name: 'Test Policy',
|
||||||
|
description: 'Test Description',
|
||||||
|
exclusion_rules: { overall_capacity: 10 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post "/api/v1/accounts/#{account.id}/agent_capacity_policies",
|
||||||
|
params: params,
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.parsed_body['name']).to eq('Test Policy')
|
||||||
|
expect(response.parsed_body['description']).to eq('Test Description')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns validation errors for invalid data' do
|
||||||
|
params = { agent_capacity_policy: { name: '' } }
|
||||||
|
|
||||||
|
post "/api/v1/accounts/#{account.id}/agent_capacity_policies",
|
||||||
|
params: params,
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'PUT /api/v1/accounts/{account.id}/agent_capacity_policies/{id}' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
put "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||||
|
|
||||||
|
it 'returns unauthorized for agent' do
|
||||||
|
params = { agent_capacity_policy: { name: 'Updated Policy' } }
|
||||||
|
|
||||||
|
put "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
|
||||||
|
params: params,
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'updates the agent capacity policy when administrator' do
|
||||||
|
params = { agent_capacity_policy: { name: 'Updated Policy' } }
|
||||||
|
|
||||||
|
put "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
|
||||||
|
params: params,
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(response.parsed_body['name']).to eq('Updated Policy')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'DELETE /api/v1/accounts/{account.id}/agent_capacity_policies/{id}' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
delete "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
let(:agent) { create(:user, account: account, role: :agent) }
|
||||||
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||||
|
|
||||||
|
it 'returns unauthorized for agent' do
|
||||||
|
delete "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
|
||||||
|
headers: agent.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'deletes the agent capacity policy when administrator' do
|
||||||
|
delete "/api/v1/accounts/#{account.id}/agent_capacity_policies/#{agent_capacity_policy.id}",
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
as: :json
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
expect { agent_capacity_policy.reload }.to raise_error(ActiveRecord::RecordNotFound)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
29
spec/enterprise/models/agent_capacity_policy_spec.rb
Normal file
29
spec/enterprise/models/agent_capacity_policy_spec.rb
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe AgentCapacityPolicy, type: :model do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
|
||||||
|
describe 'validations' do
|
||||||
|
it { is_expected.to validate_presence_of(:name) }
|
||||||
|
it { is_expected.to validate_length_of(:name).is_at_most(255) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'destruction' do
|
||||||
|
let(:policy) { create(:agent_capacity_policy, account: account) }
|
||||||
|
let(:user) { create(:user, account: account) }
|
||||||
|
let(:inbox) { create(:inbox, account: account) }
|
||||||
|
|
||||||
|
it 'destroys associated inbox capacity limits' do
|
||||||
|
create(:inbox_capacity_limit, agent_capacity_policy: policy, inbox: inbox)
|
||||||
|
expect { policy.destroy }.to change(InboxCapacityLimit, :count).by(-1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'nullifies associated account users' do
|
||||||
|
account_user = user.account_users.first
|
||||||
|
account_user.update!(agent_capacity_policy: policy)
|
||||||
|
|
||||||
|
policy.destroy
|
||||||
|
expect(account_user.reload.agent_capacity_policy).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
33
spec/enterprise/models/inbox_capacity_limit_spec.rb
Normal file
33
spec/enterprise/models/inbox_capacity_limit_spec.rb
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe InboxCapacityLimit, type: :model do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:policy) { create(:agent_capacity_policy, account: account) }
|
||||||
|
let(:inbox) { create(:inbox, account: account) }
|
||||||
|
|
||||||
|
describe 'validations' do
|
||||||
|
subject { create(:inbox_capacity_limit, agent_capacity_policy: policy, inbox: inbox) }
|
||||||
|
|
||||||
|
it { is_expected.to validate_presence_of(:conversation_limit) }
|
||||||
|
it { is_expected.to validate_numericality_of(:conversation_limit).is_greater_than(0).only_integer }
|
||||||
|
it { is_expected.to validate_uniqueness_of(:inbox_id).scoped_to(:agent_capacity_policy_id) }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'uniqueness constraint' do
|
||||||
|
it 'prevents duplicate inbox limits for the same policy' do
|
||||||
|
create(:inbox_capacity_limit, agent_capacity_policy: policy, inbox: inbox)
|
||||||
|
duplicate = build(:inbox_capacity_limit, agent_capacity_policy: policy, inbox: inbox)
|
||||||
|
|
||||||
|
expect(duplicate).not_to be_valid
|
||||||
|
expect(duplicate.errors[:inbox_id]).to include('has already been taken')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'allows the same inbox in different policies' do
|
||||||
|
other_policy = create(:agent_capacity_policy, account: account)
|
||||||
|
create(:inbox_capacity_limit, agent_capacity_policy: policy, inbox: inbox)
|
||||||
|
|
||||||
|
different_policy_limit = build(:inbox_capacity_limit, agent_capacity_policy: other_policy, inbox: inbox)
|
||||||
|
expect(different_policy_limit).to be_valid
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
21
spec/factories/agent_capacity_policies.rb
Normal file
21
spec/factories/agent_capacity_policies.rb
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FactoryBot.define do
|
||||||
|
factory :agent_capacity_policy do
|
||||||
|
account
|
||||||
|
sequence(:name) { |n| "Agent Capacity Policy #{n}" }
|
||||||
|
description { 'Test agent capacity policy' }
|
||||||
|
exclusion_rules { {} }
|
||||||
|
|
||||||
|
trait :with_overall_capacity do
|
||||||
|
exclusion_rules { { 'overall_capacity' => 10 } }
|
||||||
|
end
|
||||||
|
|
||||||
|
trait :with_time_exclusions do
|
||||||
|
exclusion_rules do
|
||||||
|
{
|
||||||
|
'hours' => [0, 1, 2, 3, 4, 5],
|
||||||
|
'days' => %w[saturday sunday]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
7
spec/factories/inbox_capacity_limits.rb
Normal file
7
spec/factories/inbox_capacity_limits.rb
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
FactoryBot.define do
|
||||||
|
factory :inbox_capacity_limit do
|
||||||
|
association :agent_capacity_policy, factory: :agent_capacity_policy
|
||||||
|
inbox
|
||||||
|
conversation_limit { 5 }
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user