diff --git a/config/locales/en.yml b/config/locales/en.yml index e55132709..f424716af 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -372,6 +372,8 @@ en: Transcript: %{format_messages} + agent_capacity_policy: + inbox_already_assigned: 'Inbox has already been assigned to this policy' portals: send_instructions: email_required: 'Email is required' diff --git a/config/routes.rb b/config/routes.rb index 1409b11fd..67ded50cb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -97,6 +97,12 @@ Rails.application.routes.draw do end resources :sla_policies, 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 :dashboard_apps, only: [:index, :show, :create, :update, :destroy] namespace :channels do diff --git a/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies/inbox_limits_controller.rb b/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies/inbox_limits_controller.rb new file mode 100644 index 000000000..8d19e29ef --- /dev/null +++ b/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies/inbox_limits_controller.rb @@ -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 diff --git a/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies/users_controller.rb b/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies/users_controller.rb new file mode 100644 index 000000000..a49b4f00f --- /dev/null +++ b/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies/users_controller.rb @@ -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 diff --git a/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies_controller.rb b/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies_controller.rb new file mode 100644 index 000000000..d6d166ee5 --- /dev/null +++ b/enterprise/app/controllers/api/v1/accounts/agent_capacity_policies_controller.rb @@ -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 diff --git a/enterprise/app/models/agent_capacity_policy.rb b/enterprise/app/models/agent_capacity_policy.rb new file mode 100644 index 000000000..baec7d978 --- /dev/null +++ b/enterprise/app/models/agent_capacity_policy.rb @@ -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 diff --git a/enterprise/app/models/enterprise/concerns/account.rb b/enterprise/app/models/enterprise/concerns/account.rb index c31b6c10e..e1136fd07 100644 --- a/enterprise/app/models/enterprise/concerns/account.rb +++ b/enterprise/app/models/enterprise/concerns/account.rb @@ -5,6 +5,7 @@ module Enterprise::Concerns::Account has_many :sla_policies, dependent: :destroy_async has_many :applied_slas, 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_assistant_responses, dependent: :destroy_async, class_name: 'Captain::AssistantResponse' diff --git a/enterprise/app/models/enterprise/concerns/account_user.rb b/enterprise/app/models/enterprise/concerns/account_user.rb index 0887d31d9..70a90226b 100644 --- a/enterprise/app/models/enterprise/concerns/account_user.rb +++ b/enterprise/app/models/enterprise/concerns/account_user.rb @@ -3,5 +3,6 @@ module Enterprise::Concerns::AccountUser included do belongs_to :custom_role, optional: true + belongs_to :agent_capacity_policy, optional: true end end diff --git a/enterprise/app/models/inbox_capacity_limit.rb b/enterprise/app/models/inbox_capacity_limit.rb new file mode 100644 index 000000000..709dd809f --- /dev/null +++ b/enterprise/app/models/inbox_capacity_limit.rb @@ -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 diff --git a/enterprise/app/policies/agent_capacity_policy_policy.rb b/enterprise/app/policies/agent_capacity_policy_policy.rb new file mode 100644 index 000000000..d57d5a58f --- /dev/null +++ b/enterprise/app/policies/agent_capacity_policy_policy.rb @@ -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 diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/create.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/create.json.jbuilder new file mode 100644 index 000000000..55f6b473e --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/create.json.jbuilder @@ -0,0 +1,2 @@ +json.partial! 'api/v1/models/agent_capacity_policy', formats: [:json], + agent_capacity_policy: @agent_capacity_policy diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/create.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/create.json.jbuilder new file mode 100644 index 000000000..07e4a04a5 --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/create.json.jbuilder @@ -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 diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/update.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/update.json.jbuilder new file mode 100644 index 000000000..370ca7d9f --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/inbox_limits/update.json.jbuilder @@ -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 diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/index.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/index.json.jbuilder new file mode 100644 index 000000000..7d869e4ea --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/index.json.jbuilder @@ -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 diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/show.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/show.json.jbuilder new file mode 100644 index 000000000..55f6b473e --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/show.json.jbuilder @@ -0,0 +1,2 @@ +json.partial! 'api/v1/models/agent_capacity_policy', formats: [:json], + agent_capacity_policy: @agent_capacity_policy diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/update.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/update.json.jbuilder new file mode 100644 index 000000000..55f6b473e --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/update.json.jbuilder @@ -0,0 +1,2 @@ +json.partial! 'api/v1/models/agent_capacity_policy', formats: [:json], + agent_capacity_policy: @agent_capacity_policy diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/create.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/create.json.jbuilder new file mode 100644 index 000000000..ca84a01f7 --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/create.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/v1/models/user', resource: @user diff --git a/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/index.json.jbuilder b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/index.json.jbuilder new file mode 100644 index 000000000..bfbc77874 --- /dev/null +++ b/enterprise/app/views/api/v1/accounts/agent_capacity_policies/users/index.json.jbuilder @@ -0,0 +1,3 @@ +json.array! @users do |user| + json.partial! 'api/v1/models/user', resource: user +end diff --git a/enterprise/app/views/api/v1/models/_agent_capacity_policy.json.jbuilder b/enterprise/app/views/api/v1/models/_agent_capacity_policy.json.jbuilder new file mode 100644 index 000000000..8f7a41aa1 --- /dev/null +++ b/enterprise/app/views/api/v1/models/_agent_capacity_policy.json.jbuilder @@ -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 diff --git a/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/inbox_limits_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/inbox_limits_controller_spec.rb new file mode 100644 index 000000000..4f80456d9 --- /dev/null +++ b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/inbox_limits_controller_spec.rb @@ -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 diff --git a/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/users_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/users_controller_spec.rb new file mode 100644 index 000000000..be25151ae --- /dev/null +++ b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies/users_controller_spec.rb @@ -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 diff --git a/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies_controller_spec.rb new file mode 100644 index 000000000..d6b171fe8 --- /dev/null +++ b/spec/enterprise/controllers/api/v1/accounts/agent_capacity_policies_controller_spec.rb @@ -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 diff --git a/spec/enterprise/models/agent_capacity_policy_spec.rb b/spec/enterprise/models/agent_capacity_policy_spec.rb new file mode 100644 index 000000000..231e85423 --- /dev/null +++ b/spec/enterprise/models/agent_capacity_policy_spec.rb @@ -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 diff --git a/spec/enterprise/models/inbox_capacity_limit_spec.rb b/spec/enterprise/models/inbox_capacity_limit_spec.rb new file mode 100644 index 000000000..8c3f76dc4 --- /dev/null +++ b/spec/enterprise/models/inbox_capacity_limit_spec.rb @@ -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 diff --git a/spec/factories/agent_capacity_policies.rb b/spec/factories/agent_capacity_policies.rb new file mode 100644 index 000000000..98a60f1fd --- /dev/null +++ b/spec/factories/agent_capacity_policies.rb @@ -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 diff --git a/spec/factories/inbox_capacity_limits.rb b/spec/factories/inbox_capacity_limits.rb new file mode 100644 index 000000000..bffd74fcd --- /dev/null +++ b/spec/factories/inbox_capacity_limits.rb @@ -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