feat: add assignment service

This commit is contained in:
Tanmay Sharma
2025-08-28 17:03:11 +07:00
parent 472493b9af
commit eacfa2e19a
23 changed files with 1153 additions and 10 deletions

View File

@@ -0,0 +1,20 @@
module Enterprise::AssignmentPolicy
def assignment_order=(value)
if value.to_s == 'balanced'
write_attribute(:assignment_order, 1)
else
super
end
end
def assignment_order
value = read_attribute(:assignment_order)
return 'balanced' if value == 1
super
end
def balanced?
self[:assignment_order] == 1
end
end

View File

@@ -0,0 +1,31 @@
module Enterprise::InboxAgentAvailability
extend ActiveSupport::Concern
def member_ids_with_assignment_capacity
return member_ids unless capacity_filtering_enabled?
# Get online agents with capacity
agents = available_agents
agents = filter_by_capacity(agents) if capacity_filtering_enabled?
agents.map(&:user_id)
end
private
def filter_by_capacity(inbox_members_scope)
return inbox_members_scope unless capacity_filtering_enabled?
inbox_members_scope.select do |inbox_member|
capacity_service.agent_has_capacity?(inbox_member.user, self)
end
end
def capacity_filtering_enabled?
account.feature_enabled?('assignment_v2') &&
account.account_users.joins(:agent_capacity_policy).exists?
end
def capacity_service
@capacity_service ||= Enterprise::AutoAssignment::CapacityService.new
end
end

View File

@@ -0,0 +1,60 @@
module Enterprise::AutoAssignment::AssignmentService
private
# Override assignment config to use policy if available
def assignment_config
return super unless policy
{
'conversation_priority' => policy.conversation_priority,
'fair_distribution_limit' => policy.fair_distribution_limit,
'fair_distribution_window' => policy.fair_distribution_window,
'balanced' => policy.balanced?
}.compact
end
# Override to check policy first
def assignment_enabled?
return policy.enabled? if policy
super
end
# Extend agent finding to add capacity checks
def find_available_agent
agents = filter_agents_by_rate_limit(inbox.available_agents)
agents = filter_agents_by_capacity(agents) if capacity_filtering_enabled?
return nil if agents.empty?
selector = policy&.balanced? ? balanced_selector : round_robin_selector
selector.select_agent(agents)
end
def filter_agents_by_capacity(agents)
return agents unless capacity_filtering_enabled?
capacity_service = Enterprise::AutoAssignment::CapacityService.new
agents.select { |agent_member| capacity_service.agent_has_capacity?(agent_member.user, inbox) }
end
def capacity_filtering_enabled?
account.feature_enabled?('assignment_v2') &&
account.account_users.joins(:agent_capacity_policy).exists?
end
def round_robin_selector
@round_robin_selector ||= AutoAssignment::RoundRobinSelector.new(inbox: inbox)
end
def balanced_selector
@balanced_selector ||= Enterprise::AutoAssignment::BalancedSelector.new(inbox: inbox)
end
def policy
@policy ||= inbox.assignment_policy
end
def account
inbox.account
end
end

View File

@@ -0,0 +1,26 @@
class Enterprise::AutoAssignment::BalancedSelector
pattr_initialize [:inbox!]
def select_agent(available_agents)
return nil if available_agents.empty?
agent_users = available_agents.map(&:user)
assignment_counts = fetch_assignment_counts(agent_users)
agent_users.min_by { |user| assignment_counts[user.id] || 0 }
end
private
def fetch_assignment_counts(users)
user_ids = users.map(&:id)
counts = inbox.conversations
.open
.where(assignee_id: user_ids)
.group(:assignee_id)
.count
Hash.new(0).merge(counts)
end
end

View File

@@ -0,0 +1,44 @@
class Enterprise::AutoAssignment::CapacityService
def agent_has_capacity?(user, inbox)
# Get the account_user for this specific account
account_user = user.account_users.find_by(account: inbox.account)
# If no account_user or no capacity policy, agent has unlimited capacity
return true unless account_user&.agent_capacity_policy
policy = account_user.agent_capacity_policy
# Check if there's a specific limit for this inbox
inbox_limit = policy.inbox_capacity_limits.find_by(inbox: inbox)
# If no specific limit for this inbox, agent has unlimited capacity for this inbox
return true unless inbox_limit
# Count current open conversations for this agent in this inbox
current_count = user.assigned_conversations
.where(inbox: inbox, status: :open)
.count
# Agent has capacity if current count is below the limit
current_count < inbox_limit.conversation_limit
end
def agent_capacity_status(user, inbox)
account_user = user.account_users.find_by(account: inbox.account)
return { has_capacity: true, current: 0, limit: nil } unless account_user&.agent_capacity_policy
policy = account_user.agent_capacity_policy
inbox_limit = policy.inbox_capacity_limits.find_by(inbox: inbox)
return { has_capacity: true, current: 0, limit: nil } unless inbox_limit
current_count = user.assigned_conversations
.where(inbox: inbox, status: :open)
.count
{
has_capacity: current_count < inbox_limit.conversation_limit,
current: current_count,
limit: inbox_limit.conversation_limit
}
end
end