From b1aaf580971f72a9dd4cc9e1ab1182db3cbe9bf3 Mon Sep 17 00:00:00 2001 From: Gabriel Jablonski Date: Sun, 25 Jan 2026 19:51:56 -0300 Subject: [PATCH] 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 --- app/builders/conversation_builder.rb | 2 +- .../whatsapp/incoming_message_base_service.rb | 2 +- spec/builders/conversation_builder_spec.rb | 26 +++++++-- .../whatsapp/incoming_message_service_spec.rb | 2 +- .../zapi_handlers/received_callback_spec.rb | 55 +++++++++++++++++++ 5 files changed, 80 insertions(+), 7 deletions(-) diff --git a/app/builders/conversation_builder.rb b/app/builders/conversation_builder.rb index 6a995b188..b9d9b8148 100644 --- a/app/builders/conversation_builder.rb +++ b/app/builders/conversation_builder.rb @@ -10,7 +10,7 @@ class ConversationBuilder def look_up_exising_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 def create_new_conversation diff --git a/app/services/whatsapp/incoming_message_base_service.rb b/app/services/whatsapp/incoming_message_base_service.rb index b0cf46592..5629342ca 100644 --- a/app/services/whatsapp/incoming_message_base_service.rb +++ b/app/services/whatsapp/incoming_message_base_service.rb @@ -114,7 +114,7 @@ class Whatsapp::IncomingMessageBaseService def set_conversation # 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 - @contact_inbox.conversations.last + @inbox.conversations.where(contact_id: @contact_inbox.contact_id).last else @contact_inbox.conversations .where.not(status: :resolved).last diff --git a/spec/builders/conversation_builder_spec.rb b/spec/builders/conversation_builder_spec.rb index c0c5f248b..cd03ba862 100644 --- a/spec/builders/conversation_builder_spec.rb +++ b/spec/builders/conversation_builder_spec.rb @@ -44,8 +44,8 @@ describe ConversationBuilder do end it 'returns last from existing sms conversations when existing conversation is not present' do - create(:conversation, contact_inbox: contact_sms_inbox) - existing_conversation = 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, contact: contact, inbox: sms_inbox) conversation = described_class.new( contact_inbox: contact_sms_inbox, params: {} @@ -70,8 +70,8 @@ describe ConversationBuilder do end it 'returns last from existing api conversations when existing conversation is not present' do - create(:conversation, contact_inbox: contact_api_inbox) - existing_conversation = 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, contact: contact, inbox: api_inbox) conversation = described_class.new( contact_inbox: contact_api_inbox, params: {} @@ -80,5 +80,23 @@ describe ConversationBuilder do expect(conversation.id).to eq(existing_conversation.id) 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 diff --git a/spec/services/whatsapp/incoming_message_service_spec.rb b/spec/services/whatsapp/incoming_message_service_spec.rb index a0821f308..e044c720d 100644 --- a/spec/services/whatsapp/incoming_message_service_spec.rb +++ b/spec/services/whatsapp/incoming_message_service_spec.rb @@ -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 whatsapp_channel.inbox.update!(lock_to_single_conversation: true) 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') described_class.new(inbox: whatsapp_channel.inbox, params: params).perform # no new conversation should be created diff --git a/spec/services/whatsapp/zapi_handlers/received_callback_spec.rb b/spec/services/whatsapp/zapi_handlers/received_callback_spec.rb index 52c009c5a..41643cff5 100644 --- a/spec/services/whatsapp/zapi_handlers/received_callback_spec.rb +++ b/spec/services/whatsapp/zapi_handlers/received_callback_spec.rb @@ -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' 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 describe '#process_received_callback' do