fix: resolve V2 capacity bypass in team assignment (#13904)
## Description When Assignment V2 is enabled, the V2 capacity policies (AgentCapacityPolicy / InboxCapacityLimit) are not respected during team-based assignment paths. The system falls back to the legacy V1 max_assignment_limit, and since V1 is deprecated and typically unconfigured in V2 setups, agents receive unlimited assignments regardless of their V2 capacity. Root cause: Inbox class directly defined member_ids_with_assignment_capacity, which shadowed the Enterprise::InboxAgentAvailability module override in Ruby's method resolution order (MRO). This made the V2 capacity check unreachable (dead code) for any code path using member_ids_with_assignment_capacity. ## Type of change - [ ] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? ⏺ Before the fix 1. Enable assignment_v2 + advanced_assignment on account 2. Create AgentCapacityPolicy with InboxCapacityLimit = 1 for an inbox 3. Assign the policy to an agent (e.g., John) 4. Create 1 open conversation assigned to John (now at capacity) 5. Create a new unassigned conversation in the same inbox 6. Assign a team (containing John) to that conversation 7. Result: John gets assigned despite being at capacity ⏺ After the fix Same steps 1–6. 7. Result: John is NOT assigned — conversation stays unassigned (no agents with capacity available) ## Checklist: - [ ] My code follows the style guidelines of this project - [ ] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
This commit is contained in:
committed by
GitHub
parent
2b296c06fb
commit
9efd554693
@@ -1,6 +1,7 @@
|
||||
module Enterprise::Inbox
|
||||
def member_ids_with_assignment_capacity
|
||||
return super unless enable_auto_assignment?
|
||||
return filter_by_capacity(available_agents).map(&:user_id) if auto_assignment_v2_enabled?
|
||||
|
||||
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) : []
|
||||
|
||||
@@ -21,7 +21,7 @@ module Enterprise::InboxAgentAvailability
|
||||
end
|
||||
|
||||
def capacity_filtering_enabled?
|
||||
account.feature_enabled?('assignment_v2') &&
|
||||
account.feature_enabled?('advanced_assignment') &&
|
||||
account.account_users.joins(:agent_capacity_policy).exists?
|
||||
end
|
||||
|
||||
|
||||
@@ -37,6 +37,103 @@ RSpec.describe Inbox do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'member_ids_with_assignment_capacity with V2 capacity' do
|
||||
let(:account) { create(:account) }
|
||||
let(:v2_inbox) { create(:inbox, account: account, enable_auto_assignment: true) }
|
||||
let(:agent_capacity_policy) { create(:agent_capacity_policy, account: account) }
|
||||
|
||||
let!(:agent1) { create(:user, account: account, role: :agent, auto_offline: false) }
|
||||
let!(:agent2) { create(:user, account: account, role: :agent, auto_offline: false) }
|
||||
|
||||
before do
|
||||
create(:inbox_member, inbox: v2_inbox, user: agent1)
|
||||
create(:inbox_member, inbox: v2_inbox, user: agent2)
|
||||
|
||||
allow(OnlineStatusTracker).to receive(:get_available_users).and_return(
|
||||
agent1.id.to_s => 'online',
|
||||
agent2.id.to_s => 'online'
|
||||
)
|
||||
end
|
||||
|
||||
context 'when assignment_v2 is enabled with capacity policies' do
|
||||
before do
|
||||
account.enable_features('assignment_v2', 'advanced_assignment')
|
||||
account.save!
|
||||
|
||||
create(:inbox_capacity_limit, agent_capacity_policy: agent_capacity_policy, inbox: v2_inbox, conversation_limit: 1)
|
||||
agent1.account_users.find_by(account: account).update!(agent_capacity_policy: agent_capacity_policy)
|
||||
agent2.account_users.find_by(account: account).update!(agent_capacity_policy: agent_capacity_policy)
|
||||
end
|
||||
|
||||
it 'filters out agents at capacity' do
|
||||
create(:conversation, inbox: v2_inbox, account: account, assignee: agent1, status: :open)
|
||||
|
||||
result = v2_inbox.member_ids_with_assignment_capacity
|
||||
expect(result).to include(agent2.id)
|
||||
expect(result).not_to include(agent1.id)
|
||||
end
|
||||
|
||||
it 'filters out all agents when all are at capacity' do
|
||||
create(:conversation, inbox: v2_inbox, account: account, assignee: agent1, status: :open)
|
||||
create(:conversation, inbox: v2_inbox, account: account, assignee: agent2, status: :open)
|
||||
|
||||
expect(v2_inbox.member_ids_with_assignment_capacity).to be_empty
|
||||
end
|
||||
|
||||
it 'skips V1 max_assignment_limit when V2 is enabled' do
|
||||
v2_inbox.update(auto_assignment_config: { max_assignment_limit: 100 })
|
||||
|
||||
create(:conversation, inbox: v2_inbox, account: account, assignee: agent1, status: :open)
|
||||
|
||||
result = v2_inbox.member_ids_with_assignment_capacity
|
||||
expect(result).not_to include(agent1.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assignment_v2 is enabled without capacity policies' do
|
||||
before do
|
||||
account.enable_features('assignment_v2', 'advanced_assignment')
|
||||
account.save!
|
||||
end
|
||||
|
||||
it 'returns all online agents' do
|
||||
result = v2_inbox.member_ids_with_assignment_capacity
|
||||
expect(result).to contain_exactly(agent1.id, agent2.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when advanced_assignment is disabled (downgraded account with stale policies)' do
|
||||
before do
|
||||
account.enable_features('assignment_v2')
|
||||
account.save!
|
||||
|
||||
create(:inbox_capacity_limit, agent_capacity_policy: agent_capacity_policy, inbox: v2_inbox, conversation_limit: 1)
|
||||
agent1.account_users.find_by(account: account).update!(agent_capacity_policy: agent_capacity_policy)
|
||||
|
||||
create(:conversation, inbox: v2_inbox, account: account, assignee: agent1, status: :open)
|
||||
end
|
||||
|
||||
it 'does not enforce capacity limits' do
|
||||
result = v2_inbox.member_ids_with_assignment_capacity
|
||||
expect(result).to include(agent1.id)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when assignment_v2 is disabled (V1 path)' do
|
||||
before do
|
||||
v2_inbox.update(auto_assignment_config: { max_assignment_limit: 2 })
|
||||
end
|
||||
|
||||
it 'uses V1 max_assignment_limit' do
|
||||
create_list(:conversation, 2, inbox: v2_inbox, account: account, assignee: agent1, status: :open)
|
||||
|
||||
result = v2_inbox.member_ids_with_assignment_capacity
|
||||
expect(result).not_to include(agent1.id)
|
||||
expect(result).to include(agent2.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'audit log' do
|
||||
context 'when inbox is created' do
|
||||
it 'has associated audit log created' do
|
||||
|
||||
Reference in New Issue
Block a user