segregate enterprise and base

This commit is contained in:
Tanmay Sharma
2025-08-11 16:56:50 +05:30
parent b84e9ddca2
commit a3e74c314a
10 changed files with 113 additions and 40 deletions
+1
View File
@@ -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
+7 -33
View File
@@ -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')
-2
View File
@@ -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
+1
View File
@@ -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')
+3
View File
@@ -191,3 +191,6 @@
display_name: CRM V2
enabled: false
chatwoot_internal: true
- name: assignment_v2
display_name: Assignment V2
enabled: false
+7
View File
@@ -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