Compare commits

...
Author SHA1 Message Date
Tanmay Sharma 431c36e102 add agent capacity support 2025-08-12 20:10:33 +05:30
22 changed files with 864 additions and 3 deletions
+10
View File
@@ -50,6 +50,9 @@ Rails.application.routes.draw do
resource :bulk_actions, only: [:create]
resources :agents, only: [:index, :create, :update, :destroy] do
post :bulk_create, on: :collection
member do
get 'capacity', to: 'agents/capacity#show'
end
end
namespace :captain do
resources :assistants do
@@ -97,6 +100,13 @@ 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
member do
post 'users', to: 'agent_capacity_policies#assign_user'
delete 'users/:user_id', to: 'agent_capacity_policies#unassign_user'
put 'inbox_limits/:inbox_id', to: 'agent_capacity_policies#update_inbox_limit'
end
end
resources :campaigns, only: [:index, :create, :show, :update, :destroy]
resources :dashboard_apps, only: [:index, :show, :create, :update, :destroy]
namespace :channels do
+1
View File
@@ -320,6 +320,7 @@ ActiveRecord::Schema[7.1].define(version: 2025_08_08_123008) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "status", default: 0, null: false
t.jsonb "metadata", default: {}
t.index ["account_id"], name: "index_captain_documents_on_account_id"
t.index ["assistant_id", "external_link"], name: "index_captain_documents_on_assistant_id_and_external_link", unique: true
t.index ["assistant_id"], name: "index_captain_documents_on_assistant_id"
@@ -0,0 +1,58 @@
class Api::V1::Accounts::AgentCapacityPoliciesController < Api::V1::Accounts::EnterpriseAccountsController
before_action :fetch_policy, only: [:show, :update, :destroy, :assign_user, :unassign_user, :update_inbox_limit]
before_action :check_enterprise_authorization
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
def assign_user
user = Current.account.users.find(params[:user_id])
account_user = Current.account.account_users.find_by!(user: user)
account_user.update!(agent_capacity_policy: @agent_capacity_policy)
render json: { message: 'User assigned successfully' }
end
def unassign_user
user = Current.account.users.find(params[:user_id])
account_user = Current.account.account_users.find_by!(user: user)
account_user.update!(agent_capacity_policy: nil)
render json: { message: 'User unassigned successfully' }
end
def update_inbox_limit
inbox = Current.account.inboxes.find(params[:inbox_id])
inbox_limit = @agent_capacity_policy.inbox_capacity_limits.find_or_initialize_by(inbox: inbox)
inbox_limit.update!(conversation_limit: params[:conversation_limit])
render json: inbox_limit
end
private
def permitted_params
params.require(:agent_capacity_policy).permit(:name, :description, exclusion_rules: {})
end
def fetch_policy
@agent_capacity_policy = Current.account.agent_capacity_policies.find(params[:id])
end
def check_enterprise_authorization
authorize(Enterprise::AgentCapacityPolicy)
end
end
@@ -0,0 +1,39 @@
class Api::V1::Accounts::Agents::CapacityController < Api::V1::Accounts::EnterpriseAccountsController
before_action :fetch_agent
def show
account_user = Current.account.account_users.find_by!(user: @agent)
capacity_service = Enterprise::AssignmentV2::CapacityService.new(account_user)
inbox = fetch_inbox
render json: build_capacity_response(account_user, capacity_service, inbox)
end
private
def fetch_agent
@agent = Current.account.users.find(params[:id])
end
def fetch_inbox
return if params[:inbox_id].blank?
Current.account.inboxes.find(params[:inbox_id])
end
def build_capacity_response(account_user, capacity_service, inbox)
response = {
has_capacity: capacity_service.agent_has_capacity?(inbox),
overall_capacity: capacity_service.agent_overall_capacity,
inbox_capacity: inbox ? capacity_service.agent_capacity_for_inbox(inbox) : nil,
current_conversations_count: account_user.user.assigned_conversations.open.count
}
add_inbox_conversations_count(response, account_user, inbox) if inbox
response
end
def add_inbox_conversations_count(response, account_user, inbox)
response[:inbox_conversations_count] = account_user.user.assigned_conversations.open.where(inbox: inbox).count
end
end
@@ -0,0 +1,46 @@
# frozen_string_literal: true
class Enterprise::AgentCapacityPolicy < ApplicationRecord
self.table_name = 'agent_capacity_policies'
belongs_to :account, class_name: '::Account'
has_many :inbox_capacity_limits, class_name: 'Enterprise::InboxCapacityLimit', dependent: :destroy
has_many :inboxes, through: :inbox_capacity_limits, class_name: '::Inbox'
has_many :account_users, class_name: '::AccountUser', dependent: :nullify
validates :name, presence: true, length: { maximum: 255 }
def applicable_for_time?(time = Time.current)
return true if exclusion_rules.blank?
!excluded_for_time?(time)
end
def capacity_for_inbox(inbox)
inbox_capacity_limits.find_by(inbox: inbox)&.conversation_limit
end
def overall_capacity
exclusion_rules['overall_capacity'] || Float::INFINITY
end
private
def excluded_for_time?(time)
excluded_by_hours?(time) || excluded_by_days?(time)
end
def excluded_by_hours?(time)
return false if exclusion_rules['hours'].blank?
current_hour = time.hour
exclusion_rules['hours'].include?(current_hour)
end
def excluded_by_days?(time)
return false if exclusion_rules['days'].blank?
current_day = time.strftime('%A').downcase
exclusion_rules['days'].include?(current_day)
end
end
@@ -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, class_name: 'Enterprise::AgentCapacityPolicy'
has_many :captain_assistants, dependent: :destroy_async, class_name: 'Captain::Assistant'
has_many :captain_assistant_responses, dependent: :destroy_async, class_name: 'Captain::AssistantResponse'
@@ -3,5 +3,6 @@ module Enterprise::Concerns::AccountUser
included do
belongs_to :custom_role, optional: true
belongs_to :agent_capacity_policy, class_name: 'Enterprise::AgentCapacityPolicy', optional: true
end
end
@@ -0,0 +1,26 @@
# frozen_string_literal: true
module Enterprise::Concerns::InboxAgentAvailability
def apply_agent_filters(scope, options)
scope = super(scope, options)
# Apply capacity filtering if requested
scope = filter_by_capacity(scope) if options[:check_capacity]
scope
end
private
def filter_by_capacity(inbox_members_scope)
return inbox_members_scope unless assignment_policy&.enabled?
inbox_members_scope.select do |inbox_member|
account_user = AccountUser.find_by(account: account, user: inbox_member.user)
next true if account_user&.agent_capacity_policy.blank?
capacity_service = Enterprise::AssignmentV2::CapacityService.new(account_user)
capacity_service.agent_has_capacity?(self)
end
end
end
+41 -3
View File
@@ -2,9 +2,17 @@ module Enterprise::Inbox
def member_ids_with_assignment_capacity
return super unless enable_auto_assignment?
max_assignment_limit = auto_assignment_config['max_assignment_limit']
overloaded_agent_ids = max_assignment_limit.present? ? get_agent_ids_over_assignment_limit(max_assignment_limit) : []
super - overloaded_agent_ids
member_ids = apply_max_assignment_limit(super)
apply_capacity_policy_filter(member_ids)
end
def available_agents(options = {})
agents = super(options)
# Apply capacity filtering if requested and assignment policy is enabled
agents = filter_agents_by_capacity(agents) if options[:check_capacity] && assignment_policy&.enabled?
agents
end
def active_bot?
@@ -17,6 +25,36 @@ module Enterprise::Inbox
private
def filter_agents_by_capacity(inbox_members_scope)
inbox_members_scope.select do |inbox_member|
account_user = AccountUser.find_by(account: account, user: inbox_member.user)
next true if account_user&.agent_capacity_policy.blank?
capacity_service = Enterprise::AssignmentV2::CapacityService.new(account_user)
capacity_service.agent_has_capacity?(self)
end
end
def apply_max_assignment_limit(member_ids)
max_assignment_limit = auto_assignment_config['max_assignment_limit']
return member_ids if max_assignment_limit.blank?
overloaded_agent_ids = get_agent_ids_over_assignment_limit(max_assignment_limit)
member_ids - overloaded_agent_ids
end
def apply_capacity_policy_filter(member_ids)
return member_ids unless assignment_policy&.enabled?
account_users = AccountUser.where(account_id: account_id, user_id: member_ids)
account_users.select do |account_user|
next true if account_user.agent_capacity_policy.blank?
capacity_service = Enterprise::AssignmentV2::CapacityService.new(account_user)
capacity_service.agent_has_capacity?(self)
end.map(&:user_id)
end
def more_responses?
account.usage_limits[:captain][:responses][:current_available].positive?
end
@@ -0,0 +1,11 @@
# frozen_string_literal: true
class Enterprise::InboxCapacityLimit < ApplicationRecord
self.table_name = 'inbox_capacity_limits'
belongs_to :agent_capacity_policy, class_name: 'Enterprise::AgentCapacityPolicy'
belongs_to :inbox, class_name: '::Inbox'
validates :conversation_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :inbox_id, uniqueness: { scope: :agent_capacity_policy_id }
end
@@ -0,0 +1,33 @@
class Enterprise::AgentCapacityPolicyPolicy < ApplicationPolicy
def index?
@account_user.administrator? || @account_user.agent?
end
def update?
@account_user.administrator?
end
def show?
@account_user.administrator? || @account_user.agent?
end
def create?
@account_user.administrator?
end
def destroy?
@account_user.administrator?
end
def assign_user?
@account_user.administrator?
end
def unassign_user?
@account_user.administrator?
end
def update_inbox_limit?
@account_user.administrator?
end
end
@@ -7,4 +7,19 @@ module Enterprise::AssignmentV2::AssignmentService
super
end
end
# Override find_agent_for_conversation to include capacity checks
def find_agent_for_conversation(_conversation)
available_agents = inbox.available_agents(
check_rate_limits: true,
check_capacity: true
)
if available_agents.empty?
log_no_agents_available
return nil
end
selector_service.select_agent(available_agents)
end
end
@@ -0,0 +1,76 @@
# frozen_string_literal: true
class Enterprise::AssignmentV2::CapacityService
def initialize(account_user)
@account_user = account_user
@account = account_user.account
end
def agent_has_capacity?(inbox = nil)
return true unless capacity_policy_applicable?
if inbox
check_inbox_capacity(inbox)
else
check_overall_capacity
end
end
def agent_capacity_for_inbox(inbox)
return Float::INFINITY unless capacity_policy_applicable?
policy = @account_user.agent_capacity_policy
inbox_limit = policy.capacity_for_inbox(inbox)
return Float::INFINITY unless inbox_limit
current_count = current_conversations_count(inbox)
[inbox_limit - current_count, 0].max
end
def agent_overall_capacity
return Float::INFINITY unless capacity_policy_applicable?
policy = @account_user.agent_capacity_policy
overall_limit = policy.overall_capacity
return Float::INFINITY if overall_limit == Float::INFINITY
current_count = current_conversations_count
[overall_limit - current_count, 0].max
end
def current_conversations_count(inbox = nil)
scope = @account_user.user.conversations
.joins(:account)
.where(account: @account, status: :open)
scope = scope.where(inbox: inbox) if inbox
scope.count
end
private
def capacity_policy_applicable?
return false if @account_user.agent_capacity_policy.blank?
@account_user.agent_capacity_policy.applicable_for_time?
end
def check_inbox_capacity(inbox)
policy = @account_user.agent_capacity_policy
inbox_limit = policy.capacity_for_inbox(inbox)
return check_overall_capacity unless inbox_limit
current_count = current_conversations_count(inbox)
current_count < inbox_limit && check_overall_capacity
end
def check_overall_capacity
policy = @account_user.agent_capacity_policy
overall_limit = policy.overall_capacity
return true if overall_limit == Float::INFINITY
current_count = current_conversations_count
current_count < overall_limit
end
end
@@ -0,0 +1,12 @@
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
json.updated_at @agent_capacity_policy.updated_at
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,14 @@
json.array! @agent_capacity_policies do |policy|
json.id policy.id
json.name policy.name
json.description policy.description
json.exclusion_rules policy.exclusion_rules
json.created_at policy.created_at
json.updated_at policy.updated_at
json.account_id policy.account_id
json.inbox_capacity_limits policy.inbox_capacity_limits do |limit|
json.id limit.id
json.inbox_id limit.inbox_id
json.conversation_limit limit.conversation_limit
end
end
@@ -0,0 +1,12 @@
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
json.updated_at @agent_capacity_policy.updated_at
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,12 @@
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
json.updated_at @agent_capacity_policy.updated_at
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,128 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Enterprise::AgentCapacityPolicy do
let(:account) { create(:account) }
let(:policy) { described_class.create!(account: account, name: 'Test Policy') }
describe 'associations' do
it 'belongs to account' do
expect(policy.account).to eq(account)
end
it 'has many inbox capacity limits' do
expect(policy).to respond_to(:inbox_capacity_limits)
end
it 'has many inboxes through inbox capacity limits' do
expect(policy).to respond_to(:inboxes)
end
it 'has many account users' do
expect(policy).to respond_to(:account_users)
end
end
describe 'validations' do
it 'validates presence of name' do
invalid_policy = described_class.new(account: account)
expect(invalid_policy).not_to be_valid
expect(invalid_policy.errors[:name]).to include("can't be blank")
end
it 'validates length of name' do
invalid_policy = described_class.new(account: account, name: 'a' * 256)
expect(invalid_policy).not_to be_valid
expect(invalid_policy.errors[:name]).to include('is too long (maximum is 255 characters)')
end
end
describe '#applicable_for_time?' do
context 'when no exclusion rules' do
it 'returns true' do
expect(policy.applicable_for_time?).to be true
end
end
context 'with hour exclusions' do
let(:policy) do
described_class.create!(
account: account,
name: 'Hour Exclusion Policy',
exclusion_rules: { 'hours' => [10, 11, 12] }
)
end
it 'returns false during excluded hours' do
time = Time.zone.parse('10:30')
expect(policy.applicable_for_time?(time)).to be false
end
it 'returns true outside excluded hours' do
time = Time.zone.parse('13:30')
expect(policy.applicable_for_time?(time)).to be true
end
end
context 'with day exclusions' do
let(:policy) do
described_class.create!(
account: account,
name: 'Day Exclusion Policy',
exclusion_rules: { 'days' => %w[saturday sunday] }
)
end
it 'returns false on excluded days' do
time = Time.zone.parse('2024-01-06 10:00') # Saturday
expect(policy.applicable_for_time?(time)).to be false
end
it 'returns true on non-excluded days' do
time = Time.zone.parse('2024-01-08 10:00') # Monday
expect(policy.applicable_for_time?(time)).to be true
end
end
end
describe '#capacity_for_inbox' do
let(:inbox) { create(:inbox, account: account) }
it 'returns the conversation limit for the inbox' do
Enterprise::InboxCapacityLimit.create!(
agent_capacity_policy: policy,
inbox: inbox,
conversation_limit: 10
)
expect(policy.capacity_for_inbox(inbox)).to eq(10)
end
it 'returns nil for inbox without limit' do
other_inbox = create(:inbox, account: account)
expect(policy.capacity_for_inbox(other_inbox)).to be_nil
end
end
describe '#overall_capacity' do
context 'when overall_capacity is set' do
let(:policy) do
described_class.create!(
account: account,
name: 'Overall Capacity Policy',
exclusion_rules: { 'overall_capacity' => 25 }
)
end
it 'returns the overall capacity' do
expect(policy.overall_capacity).to eq(25)
end
end
context 'when overall_capacity is not set' do
it 'returns infinity' do
expect(policy.overall_capacity).to eq(Float::INFINITY)
end
end
end
end
@@ -0,0 +1,141 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Assignment with Capacity' do
let(:account) { create(:account) }
let(:inbox) { create(:inbox, account: account, enable_auto_assignment: false) }
let(:agent1) { create(:user, accounts: [account]) }
let(:agent2) { create(:user, accounts: [account]) }
let(:account_user1) { AccountUser.find_by(account: account, user: agent1) }
let(:account_user2) { AccountUser.find_by(account: account, user: agent2) }
let(:capacity_policy) do
Enterprise::AgentCapacityPolicy.create!(
account: account,
name: 'Test Capacity Policy',
exclusion_rules: { 'overall_capacity' => 5 }
)
end
let!(:inbox_capacity_limit) do
Enterprise::InboxCapacityLimit.create!(
agent_capacity_policy: capacity_policy,
inbox: inbox,
conversation_limit: 3
)
end
before do
# Create and setup assignment policy for the inbox
@assignment_policy = create(:assignment_policy, account: account, enabled: true)
create(:inbox_assignment_policy, inbox: inbox, assignment_policy: @assignment_policy)
# Add agents to inbox
create(:inbox_member, inbox: inbox, user: agent1)
create(:inbox_member, inbox: inbox, user: agent2)
# Set agents online using presence and status
OnlineStatusTracker.update_presence(account.id, 'User', agent1.id)
OnlineStatusTracker.update_presence(account.id, 'User', agent2.id)
OnlineStatusTracker.set_status(account.id, agent1.id, 'online')
OnlineStatusTracker.set_status(account.id, agent2.id, 'online')
# Also set account_user availability
account_user1.update!(availability: 'online')
account_user2.update!(availability: 'online')
# Assign capacity policy to agent1 only
account_user1.update!(agent_capacity_policy: capacity_policy)
end
describe 'capacity-based agent filtering' do
context 'when agent has capacity' do
it 'includes agent in available agents' do
available_agents = inbox.available_agents(check_capacity: true)
expect(available_agents.map(&:user)).to include(agent1, agent2)
end
end
context 'when agent reaches inbox capacity limit' do
before do
# Create 3 conversations for agent1 (at inbox limit)
create_list(:conversation, 3, account: account, inbox: inbox, assignee: agent1, status: :open)
end
it 'excludes agent from available agents for that inbox' do
available_agents = inbox.available_agents(check_capacity: true)
expect(available_agents.map(&:user)).not_to include(agent1)
expect(available_agents.map(&:user)).to include(agent2)
end
end
context 'when agent reaches overall capacity limit' do
before do
# Create 5 conversations for agent1 (at overall limit)
create_list(:conversation, 5, account: account, assignee: agent1, status: :open)
end
it 'excludes agent from all inbox assignments' do
available_agents = inbox.available_agents(check_capacity: true)
expect(available_agents.map(&:user)).not_to include(agent1)
expect(available_agents.map(&:user)).to include(agent2)
end
end
context 'when capacity policy is not applicable (time exclusion)' do
before do
capacity_policy.update!(exclusion_rules: {
'overall_capacity' => 5,
'hours' => [Time.current.hour]
})
# Create 5 conversations for agent1 (would be at limit if policy was active)
create_list(:conversation, 5, account: account, assignee: agent1, status: :open)
end
it 'includes agent in available agents' do
available_agents = inbox.available_agents(check_capacity: true)
expect(available_agents.map(&:user)).to include(agent1, agent2)
end
end
end
describe 'capacity-aware assignment' do
let(:conversation) { create(:conversation, account: account, inbox: inbox, status: :open, assignee: nil) }
context 'when agents have capacity' do
it 'both agents are available for assignment' do
available = inbox.available_agents(check_capacity: true)
expect(available.map(&:user)).to include(agent1, agent2)
end
end
context 'when one agent is at capacity' do
before do
# Agent1 at inbox capacity
create_list(:conversation, 3, account: account, inbox: inbox, assignee: agent1, status: :open)
end
it 'only agent with capacity is available' do
available = inbox.available_agents(check_capacity: true)
expect(available.map(&:user)).not_to include(agent1)
expect(available.map(&:user)).to include(agent2)
end
end
context 'when all agents are at capacity' do
before do
# Both agents at capacity
account_user2.update!(agent_capacity_policy: capacity_policy)
create_list(:conversation, 3, account: account, inbox: inbox, assignee: agent1, status: :open)
create_list(:conversation, 3, account: account, inbox: inbox, assignee: agent2, status: :open)
end
it 'no agents are available' do
available = inbox.available_agents(check_capacity: true)
expect(available).to be_empty
end
end
end
end
@@ -0,0 +1,168 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe Enterprise::AssignmentV2::CapacityService do
let(:account) { create(:account) }
let(:user) { create(:user, accounts: [account]) }
let(:account_user) { AccountUser.find_by(account: account, user: user) }
let(:inbox) { create(:inbox, account: account) }
let(:service) { described_class.new(account_user) }
describe '#agent_has_capacity?' do
context 'without capacity policy' do
it 'returns true' do
expect(service.agent_has_capacity?).to be true
expect(service.agent_has_capacity?(inbox)).to be true
end
end
context 'with capacity policy' do
let(:policy) { Enterprise::AgentCapacityPolicy.create!(account: account, name: 'Test Policy') }
before do
account_user.update!(agent_capacity_policy: policy)
end
context 'when policy is not applicable' do
before do
allow(policy).to receive(:applicable_for_time?).and_return(false)
end
it 'returns true' do
expect(service.agent_has_capacity?).to be true
end
end
context 'when checking inbox capacity' do
let!(:inbox_limit) do
Enterprise::InboxCapacityLimit.create!(
agent_capacity_policy: policy,
inbox: inbox,
conversation_limit: 5
)
end
it 'returns true when under limit' do
create_list(:conversation, 3, account: account, inbox: inbox, assignee: user, status: :open)
expect(service.agent_has_capacity?(inbox)).to be true
end
it 'returns false when at limit' do
create_list(:conversation, 5, account: account, inbox: inbox, assignee: user, status: :open)
expect(service.agent_has_capacity?(inbox)).to be false
end
it 'checks overall capacity too' do
policy.update!(exclusion_rules: { 'overall_capacity' => 10 })
create_list(:conversation, 9, account: account, assignee: user, status: :open)
# Under inbox limit but close to overall limit
expect(service.agent_has_capacity?(inbox)).to be true
# Add one more to hit overall limit
create(:conversation, account: account, assignee: user, status: :open)
expect(service.agent_has_capacity?(inbox)).to be false
end
end
context 'when checking overall capacity' do
before do
policy.update!(exclusion_rules: { 'overall_capacity' => 10 })
end
it 'returns true when under limit' do
create_list(:conversation, 8, account: account, assignee: user, status: :open)
expect(service.agent_has_capacity?).to be true
end
it 'returns false when at limit' do
create_list(:conversation, 10, account: account, assignee: user, status: :open)
expect(service.agent_has_capacity?).to be false
end
end
end
end
describe '#agent_capacity_for_inbox' do
context 'without capacity policy' do
it 'returns infinity' do
expect(service.agent_capacity_for_inbox(inbox)).to eq(Float::INFINITY)
end
end
context 'with capacity policy and inbox limit' do
let(:policy) { Enterprise::AgentCapacityPolicy.create!(account: account, name: 'Test Policy') }
let!(:inbox_limit) do
Enterprise::InboxCapacityLimit.create!(
agent_capacity_policy: policy,
inbox: inbox,
conversation_limit: 5
)
end
before do
account_user.update!(agent_capacity_policy: policy)
end
it 'returns remaining capacity' do
create_list(:conversation, 2, account: account, inbox: inbox, assignee: user, status: :open)
expect(service.agent_capacity_for_inbox(inbox)).to eq(3)
end
it 'returns 0 when at capacity' do
create_list(:conversation, 5, account: account, inbox: inbox, assignee: user, status: :open)
expect(service.agent_capacity_for_inbox(inbox)).to eq(0)
end
end
end
describe '#agent_overall_capacity' do
context 'without capacity policy' do
it 'returns infinity' do
expect(service.agent_overall_capacity).to eq(Float::INFINITY)
end
end
context 'with capacity policy and overall limit' do
let(:policy) do
Enterprise::AgentCapacityPolicy.create!(
account: account,
name: 'Overall Policy',
exclusion_rules: { 'overall_capacity' => 10 }
)
end
before do
account_user.update!(agent_capacity_policy: policy)
end
it 'returns remaining capacity' do
create_list(:conversation, 6, account: account, assignee: user, status: :open)
expect(service.agent_overall_capacity).to eq(4)
end
it 'returns 0 when at capacity' do
create_list(:conversation, 10, account: account, assignee: user, status: :open)
expect(service.agent_overall_capacity).to eq(0)
end
end
end
describe '#current_conversations_count' do
it 'counts open conversations assigned to user' do
create_list(:conversation, 3, account: account, assignee: user, status: :open)
create(:conversation, account: account, assignee: user, status: :resolved)
create(:conversation, account: account, status: :open)
expect(service.current_conversations_count).to eq(3)
end
it 'counts inbox-specific conversations when inbox provided' do
create_list(:conversation, 2, account: account, inbox: inbox, assignee: user, status: :open)
create(:conversation, account: account, assignee: user, status: :open)
expect(service.current_conversations_count(inbox)).to eq(2)
end
end
end
+10
View File
@@ -0,0 +1,10 @@
# frozen_string_literal: true
FactoryBot.define do
factory :agent_capacity_policy, class: 'Enterprise::AgentCapacityPolicy' do
account
name { Faker::Name.name }
description { Faker::Lorem.sentence }
exclusion_rules { {} }
end
end
+9
View File
@@ -0,0 +1,9 @@
# frozen_string_literal: true
FactoryBot.define do
factory :inbox_capacity_limit, class: 'Enterprise::InboxCapacityLimit' do
association :agent_capacity_policy, factory: :agent_capacity_policy
inbox
conversation_limit { 10 }
end
end