fix(whatsapp): update conversation lookup to handle multiple contact inboxes in single conversation mode (#197)

* fix(whatsapp): update conversation lookup to handle multiple contact inboxes in single conversation mode

* fix(whatsapp): update conversation retrieval logic to ensure correct conversation is reopened
This commit is contained in:
Gabriel Jablonski 2026-01-25 19:51:56 -03:00 committed by GitHub
parent b5d3250a2a
commit b1aaf58097
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 80 additions and 7 deletions

View File

@ -10,7 +10,7 @@ class ConversationBuilder
def look_up_exising_conversation def look_up_exising_conversation
return unless @contact_inbox.inbox.lock_to_single_conversation? return unless @contact_inbox.inbox.lock_to_single_conversation?
@contact_inbox.conversations.last @contact_inbox.inbox.conversations.where(contact_id: @contact_inbox.contact_id).last
end end
def create_new_conversation def create_new_conversation

View File

@ -114,7 +114,7 @@ class Whatsapp::IncomingMessageBaseService
def set_conversation def set_conversation
# if lock to single conversation is disabled, we will create a new conversation if previous conversation is resolved # if lock to single conversation is disabled, we will create a new conversation if previous conversation is resolved
@conversation = if @inbox.lock_to_single_conversation @conversation = if @inbox.lock_to_single_conversation
@contact_inbox.conversations.last @inbox.conversations.where(contact_id: @contact_inbox.contact_id).last
else else
@contact_inbox.conversations @contact_inbox.conversations
.where.not(status: :resolved).last .where.not(status: :resolved).last

View File

@ -44,8 +44,8 @@ describe ConversationBuilder do
end end
it 'returns last from existing sms conversations when existing conversation is not present' do it 'returns last from existing sms conversations when existing conversation is not present' do
create(:conversation, contact_inbox: contact_sms_inbox) create(:conversation, contact_inbox: contact_sms_inbox, contact: contact, inbox: sms_inbox)
existing_conversation = create(:conversation, contact_inbox: contact_sms_inbox) existing_conversation = create(:conversation, contact_inbox: contact_sms_inbox, contact: contact, inbox: sms_inbox)
conversation = described_class.new( conversation = described_class.new(
contact_inbox: contact_sms_inbox, contact_inbox: contact_sms_inbox,
params: {} params: {}
@ -70,8 +70,8 @@ describe ConversationBuilder do
end end
it 'returns last from existing api conversations when existing conversation is not present' do it 'returns last from existing api conversations when existing conversation is not present' do
create(:conversation, contact_inbox: contact_api_inbox) create(:conversation, contact_inbox: contact_api_inbox, contact: contact, inbox: api_inbox)
existing_conversation = create(:conversation, contact_inbox: contact_api_inbox) existing_conversation = create(:conversation, contact_inbox: contact_api_inbox, contact: contact, inbox: api_inbox)
conversation = described_class.new( conversation = described_class.new(
contact_inbox: contact_api_inbox, contact_inbox: contact_api_inbox,
params: {} params: {}
@ -80,5 +80,23 @@ describe ConversationBuilder do
expect(conversation.id).to eq(existing_conversation.id) expect(conversation.id).to eq(existing_conversation.id)
end end
end end
context 'when lock_to_single_conversation is true for whatsapp inbox with multiple contact_inboxes' do
let!(:whatsapp_channel) { create(:channel_whatsapp, account: account, sync_templates: false, validate_provider_config: false) }
let!(:whatsapp_inbox) { whatsapp_channel.inbox }
let(:contact_with_phone) { create(:contact, account: account, phone_number: '+5511912345678') }
before { whatsapp_inbox.update!(lock_to_single_conversation: true) }
it 'finds conversation from different contact_inbox with same contact' do
lid_contact_inbox = create(:contact_inbox, contact: contact_with_phone, inbox: whatsapp_inbox, source_id: '12345678')
existing_conversation = create(:conversation, contact_inbox: lid_contact_inbox, inbox: whatsapp_inbox, contact: contact_with_phone)
phone_contact_inbox = create(:contact_inbox, contact: contact_with_phone, inbox: whatsapp_inbox, source_id: '5511912345678')
conversation = described_class.new(contact_inbox: phone_contact_inbox, params: {}).perform
expect(conversation.id).to eq(existing_conversation.id)
end
end
end end
end end

View File

@ -44,7 +44,7 @@ describe Whatsapp::IncomingMessageService do
it 'reopen last conversation if last conversation is resolved and lock to single conversation is enabled' do it 'reopen last conversation if last conversation is resolved and lock to single conversation is enabled' do
whatsapp_channel.inbox.update!(lock_to_single_conversation: true) whatsapp_channel.inbox.update!(lock_to_single_conversation: true)
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: params[:messages].first[:from]) contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: params[:messages].first[:from])
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox) last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
last_conversation.update!(status: 'resolved') last_conversation.update!(status: 'resolved')
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
# no new conversation should be created # no new conversation should be created

View File

@ -541,6 +541,61 @@ describe Whatsapp::ZapiHandlers::ReceivedCallback do
it_behaves_like 'routes messages to the new conversation', first_msg_id: 'msg_003', second_msg_id: 'msg_004' it_behaves_like 'routes messages to the new conversation', first_msg_id: 'msg_003', second_msg_id: 'msg_004'
end end
end end
describe 'single conversation mode' do
let(:phone) { '5511912345678' }
let(:lid) { '12345678' }
before { inbox.update!(lock_to_single_conversation: true) }
def build_params(message_id:, text:)
{
type: 'ReceivedCallback',
messageId: message_id,
momment: Time.current.to_i * 1000,
fromMe: false,
phone: phone,
chatLid: "#{lid}@lid",
chatName: 'John Doe',
text: { message: text }
}
end
it 'reopens resolved conversation when contact sends new message' do
Whatsapp::IncomingMessageZapiService.new(inbox: inbox, params: build_params(message_id: 'msg_1', text: 'First')).perform
conversation = Conversation.last
conversation.update!(status: :resolved)
expect { Whatsapp::IncomingMessageZapiService.new(inbox: inbox, params: build_params(message_id: 'msg_2', text: 'Second')).perform }
.not_to change(Conversation, :count)
expect(conversation.reload.status).to eq('open')
end
it 'migrates phone source_id to LID and routes to existing conversation' do
contact = create(:contact, account: inbox.account, phone_number: "+#{phone}")
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: phone)
conversation = create(:conversation, inbox: inbox, contact: contact, contact_inbox: contact_inbox)
Whatsapp::IncomingMessageZapiService.new(inbox: inbox, params: build_params(message_id: 'msg_3', text: 'Response')).perform
expect(contact_inbox.reload.source_id).to eq(lid)
expect(conversation.messages.count).to eq(1)
end
it 'finds conversation via ConversationBuilder when agent creates new contact_inbox with phone' do
Whatsapp::IncomingMessageZapiService.new(inbox: inbox, params: build_params(message_id: 'msg_4', text: 'Hello')).perform
first_conversation = Conversation.last
first_conversation.update!(status: :resolved)
contact = Contact.last
phone_contact_inbox = ContactInboxBuilder.new(contact: contact, inbox: inbox, source_id: nil).perform
conversation = ConversationBuilder.new(params: ActionController::Parameters.new({}), contact_inbox: phone_contact_inbox).perform
expect(conversation.id).to eq(first_conversation.id)
end
end
end end
describe '#process_received_callback' do describe '#process_received_callback' do