fix: assign outbound voice call conversation to the calling agent (#14906)

Outbound voice calls were being auto-assigned to the wrong agent. When
an agent placed an outbound call, the conversation was created without
an assignee, so inboxes with auto-assignment enabled would round-robin
it to a different agent instead of keeping it with the person who
actually made the call. This made it hard to tell which agent was on an
active call.

## What changed

- Set the calling agent as the conversation's assignee when creating an
outbound voice call conversation.
- This prevents the generic auto-assignment handler from treating the
conversation as unassigned and reassigning it.
- Added specs covering the assignment, including a regression case with
inbox auto-assignment enabled.

**Note:** this applies to newly placed calls; it does not retroactively
fix conversations that were already mis-assigned.

---------

Co-authored-by: Tanmay Deep Sharma <tanmaydeepsharma21@gmail.com>
Co-authored-by: Tanmay Deep Sharma <32020192+tds-1@users.noreply.github.com>
This commit is contained in:
Shivam Mishra
2026-07-02 16:06:11 +05:30
committed by GitHub
parent 34d8741b9b
commit 6c9efc4e92
4 changed files with 88 additions and 0 deletions

View File

@@ -106,9 +106,14 @@ class Api::V1::Accounts::WhatsappCallsController < Api::V1::Accounts::BaseContro
def create_outbound_call
contact_phone = @conversation.contact.phone_number.delete('+')
# Claim for the caller only if unassigned at trigger time (before the round-trip); wins over auto-assignment.
claim_for_caller = @conversation.assignee_id.nil?
result = provider_service.initiate_call(contact_phone, params[:sdp_offer])
provider_call_id = result.dig('calls', 0, 'id') || result['call_id']
@conversation.with_lock { @conversation.update!(assignee: Current.user) } if claim_for_caller
Current.account.calls.create!(
provider: :whatsapp, inbox: @conversation.inbox, conversation: @conversation, contact: @conversation.contact,
provider_call_id: provider_call_id, direction: :outgoing, status: 'ringing',

View File

@@ -17,10 +17,19 @@ class Voice::OutboundCallBuilder
raise ArgumentError, 'Contact phone number required' if contact.phone_number.blank?
raise ArgumentError, 'Agent required' if user.blank?
# Claim for the caller if a reused conversation is unassigned at trigger time; wins over auto-assignment.
# New conversations set the assignee at creation instead (see create_conversation!).
claim_for_caller = @existing_conversation && @existing_conversation.assignee_id.nil?
ActiveRecord::Base.transaction do
contact_inbox = ensure_contact_inbox!
conversation = @existing_conversation || create_conversation!(contact_inbox)
# Dial before locking so the Twilio round-trip doesn't hold the conversation row lock.
call_sid = initiate_call!
if claim_for_caller
@existing_conversation.lock!
@existing_conversation.update!(assignee: user)
end
call = create_call!(conversation, call_sid)
message = Voice::CallMessageBuilder.new(call).perform!
call.update!(message_id: message.id)
@@ -44,6 +53,7 @@ class Voice::OutboundCallBuilder
contact_inbox_id: contact_inbox.id,
inbox_id: inbox.id,
contact_id: contact.id,
assignee_id: user.id,
status: :open
)
end