mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-04 04:57:51 +00:00 
			
		
		
		
	feat: add assignment service
This commit is contained in:
		
							
								
								
									
										20
									
								
								enterprise/app/models/enterprise/assignment_policy.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								enterprise/app/models/enterprise/assignment_policy.rb
									
									
									
									
									
										Normal 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
 | 
			
		||||
							
								
								
									
										31
									
								
								enterprise/app/models/enterprise/inbox_agent_availability.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								enterprise/app/models/enterprise/inbox_agent_availability.rb
									
									
									
									
									
										Normal 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
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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
 | 
			
		||||
@@ -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
 | 
			
		||||
		Reference in New Issue
	
	Block a user