segregate enterprise and base
This commit is contained in:
@@ -61,6 +61,7 @@ class Account < ApplicationRecord
|
||||
has_many :agent_bots, dependent: :destroy_async
|
||||
has_many :api_channels, dependent: :destroy_async, class_name: '::Channel::Api'
|
||||
has_many :articles, dependent: :destroy_async, class_name: '::Article'
|
||||
has_many :assignment_policies, dependent: :destroy_async
|
||||
has_many :automation_rules, dependent: :destroy_async
|
||||
has_many :macros, dependent: :destroy_async
|
||||
has_many :campaigns, dependent: :destroy_async
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# conversation_priority :integer default("earliest_created"), not null
|
||||
# description :text
|
||||
# enabled :boolean default(TRUE), not null
|
||||
# fair_distribution_limit :integer default(10), not null
|
||||
# fair_distribution_limit :integer default(100), not null
|
||||
# fair_distribution_window :integer default(3600), not null
|
||||
# name :string(255) not null
|
||||
# created_at :datetime not null
|
||||
@@ -18,16 +18,14 @@
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_assignment_policies_on_account_id (account_id)
|
||||
# index_assignment_policies_on_enabled (enabled)
|
||||
# unique_assignment_policy_name_per_account (account_id,name) UNIQUE
|
||||
# index_assignment_policies_on_account_id (account_id)
|
||||
# index_assignment_policies_on_account_id_and_name (account_id,name) UNIQUE
|
||||
# index_assignment_policies_on_enabled (enabled)
|
||||
#
|
||||
|
||||
class AssignmentPolicy < ApplicationRecord
|
||||
include AccountCacheRevalidator
|
||||
|
||||
# Enums
|
||||
enum assignment_order: { round_robin: 0, balanced: 1 }
|
||||
enum assignment_order: { round_robin: 0 }
|
||||
enum conversation_priority: { earliest_created: 0, longest_waiting: 1 }
|
||||
|
||||
# Associations
|
||||
@@ -44,12 +42,6 @@ class AssignmentPolicy < ApplicationRecord
|
||||
validates :assignment_order, inclusion: { in: assignment_orders.keys }
|
||||
validates :conversation_priority, inclusion: { in: conversation_priorities.keys }
|
||||
|
||||
# Validate balanced assignment is only available for enterprise
|
||||
validate :validate_balanced_assignment_enterprise_only
|
||||
|
||||
# Server-side validation to prevent bypass
|
||||
before_save :enforce_enterprise_features
|
||||
|
||||
# Scopes
|
||||
scope :enabled, -> { where(enabled: true) }
|
||||
scope :disabled, -> { where(enabled: false) }
|
||||
@@ -58,10 +50,6 @@ class AssignmentPolicy < ApplicationRecord
|
||||
after_update_commit :clear_assignment_caches
|
||||
after_destroy :clear_assignment_caches
|
||||
|
||||
def can_use_balanced_assignment?
|
||||
account.feature_enabled?(:enterprise_agent_capacity) if account.respond_to?(:feature_enabled?)
|
||||
end
|
||||
|
||||
def webhook_data
|
||||
{
|
||||
id: id,
|
||||
@@ -77,22 +65,6 @@ class AssignmentPolicy < ApplicationRecord
|
||||
|
||||
private
|
||||
|
||||
def validate_balanced_assignment_enterprise_only
|
||||
return unless balanced?
|
||||
return if can_use_balanced_assignment?
|
||||
|
||||
errors.add(:assignment_order, 'Balanced assignment is only available for enterprise accounts')
|
||||
end
|
||||
|
||||
def enforce_enterprise_features
|
||||
# Server-side enforcement to prevent API bypass
|
||||
return unless balanced? && !can_use_balanced_assignment?
|
||||
|
||||
# Force to round_robin if enterprise not available
|
||||
self.assignment_order = 'round_robin'
|
||||
Rails.logger.warn("Assignment V2: Forced assignment_order to round_robin for non-enterprise account #{account_id}")
|
||||
end
|
||||
|
||||
def clear_assignment_caches
|
||||
# Clear Redis caches when policy is updated
|
||||
Rails.cache.delete("assignment_v2:policy:#{id}")
|
||||
@@ -101,3 +73,5 @@ class AssignmentPolicy < ApplicationRecord
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
AssignmentPolicy.prepend_mod_with('AssignmentPolicy')
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#
|
||||
# index_inbox_assignment_policies_on_assignment_policy_id (assignment_policy_id)
|
||||
# index_inbox_assignment_policies_on_inbox_id (inbox_id)
|
||||
# unique_inbox_assignment_policy (inbox_id) UNIQUE
|
||||
#
|
||||
|
||||
class InboxAssignmentPolicy < ApplicationRecord
|
||||
@@ -25,7 +24,6 @@ class InboxAssignmentPolicy < ApplicationRecord
|
||||
belongs_to :assignment_policy
|
||||
|
||||
# Validations
|
||||
validates :inbox_id, uniqueness: true
|
||||
validate :inbox_belongs_to_same_account
|
||||
|
||||
# Delegations
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# idx_notifications_performance (user_id,account_id,snoozed_until,read_at)
|
||||
# index_notifications_on_account_id (account_id)
|
||||
# index_notifications_on_last_activity_at (last_activity_at)
|
||||
# index_notifications_on_user_id (user_id)
|
||||
|
||||
@@ -53,11 +53,7 @@ class AssignmentV2::AssignmentService
|
||||
end
|
||||
|
||||
def selector_service
|
||||
@selector_service ||= if policy.assignment_order == 'balanced' && enterprise_enabled? && policy.can_use_balanced_assignment?
|
||||
::Enterprise::AssignmentV2::BalancedSelector.new(inbox: inbox)
|
||||
else
|
||||
AssignmentV2::RoundRobinSelector.new(inbox: inbox)
|
||||
end
|
||||
@selector_service ||= AssignmentV2::RoundRobinSelector.new(inbox: inbox)
|
||||
end
|
||||
|
||||
def unassigned_conversations(limit)
|
||||
@@ -110,3 +106,5 @@ class AssignmentV2::AssignmentService
|
||||
Rails.logger.error "AssignmentV2: Failed to record assignment in rate limiter: #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
AssignmentV2::AssignmentService.prepend_mod_with('AssignmentV2::AssignmentService')
|
||||
|
||||
@@ -191,3 +191,6 @@
|
||||
display_name: CRM V2
|
||||
enabled: false
|
||||
chatwoot_internal: true
|
||||
- name: assignment_v2
|
||||
display_name: Assignment V2
|
||||
enabled: false
|
||||
|
||||
@@ -217,6 +217,13 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
# Assignment V2 Routes
|
||||
resources :assignment_policies
|
||||
|
||||
resources :inboxes, only: [] do
|
||||
resource :assignment_policy, only: [:show, :create, :destroy], controller: 'inbox_assignment_policies'
|
||||
end
|
||||
|
||||
namespace :twitter do
|
||||
resource :authorization, only: [:create]
|
||||
end
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
module Enterprise::AssignmentPolicy
|
||||
# In enterprise, we extend the enum to include balanced
|
||||
# However, since Rails enums are frozen after definition,
|
||||
# we need to handle this differently
|
||||
|
||||
# Override assignment_order= to accept 'balanced'
|
||||
def assignment_order=(value)
|
||||
if value.to_s == 'balanced'
|
||||
write_attribute(:assignment_order, 1)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Override assignment_order getter to return 'balanced' for value 1
|
||||
def assignment_order
|
||||
value = read_attribute(:assignment_order)
|
||||
return 'balanced' if value == 1
|
||||
|
||||
super
|
||||
end
|
||||
|
||||
# Define balanced? method
|
||||
def balanced?
|
||||
self[:assignment_order] == 1
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,10 @@
|
||||
module Enterprise::AssignmentV2::AssignmentService
|
||||
# Override selector_service to use BalancedSelector when appropriate
|
||||
def selector_service
|
||||
@selector_service ||= if policy&.balanced?
|
||||
Enterprise::AssignmentV2::BalancedSelector.new(inbox: inbox)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,54 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Enterprise::AssignmentV2::BalancedSelector
|
||||
pattr_initialize [:inbox!]
|
||||
|
||||
def select_agent(available_agents)
|
||||
return nil if available_agents.empty?
|
||||
|
||||
# Get current assignment counts for all available agents
|
||||
agent_users = available_agents.map(&:user)
|
||||
assignment_counts = fetch_assignment_counts(agent_users)
|
||||
|
||||
# Find the agent with the least assignments
|
||||
selected_agent = agent_users.min_by { |user| assignment_counts[user.id] || 0 }
|
||||
|
||||
# Log the selection for debugging
|
||||
Rails.logger.info "BalancedSelector: Selected agent #{selected_agent.id} with #{assignment_counts[selected_agent.id] || 0} assignments"
|
||||
|
||||
selected_agent
|
||||
end
|
||||
|
||||
def add_agent_to_queue(user_id)
|
||||
# No-op for balanced assignment - we don't maintain a queue
|
||||
end
|
||||
|
||||
def remove_agent_from_queue(user_id)
|
||||
# No-op for balanced assignment - we don't maintain a queue
|
||||
end
|
||||
|
||||
def reset_queue
|
||||
# No-op for balanced assignment - we don't maintain a queue
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_assignment_counts(users)
|
||||
# Get open conversation counts for each user
|
||||
user_ids = users.map(&:id)
|
||||
|
||||
# Count open conversations assigned to each user in this inbox
|
||||
counts = inbox.conversations
|
||||
.open
|
||||
.where(assignee_id: user_ids)
|
||||
.group(:assignee_id)
|
||||
.count
|
||||
|
||||
# Convert to hash with default value of 0
|
||||
Hash.new(0).merge(counts)
|
||||
end
|
||||
|
||||
def account
|
||||
@account ||= inbox.account
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user