fix(whatsapp): reopen conversation across a contact's coexistence identities (#15098)
WhatsApp contacts using coexistence are identified by more than one source ID (a phone `wa_id` and a `BR.`/BSUID identity), so a single contact ends up owning multiple `contact_inbox` records. The "reopen the same conversation" feature scoped conversation reuse to a single `contact_inbox`, so messages arriving under a different identity of the same contact started a brand-new conversation — even with reopen enabled — producing duplicate conversations. This scopes reuse to the contact across all of its `contact_inbox` records in the inbox instead of a single `contact_inbox`. ## Closes - [CW-7651 ](https://linear.app/chatwoot/issue/CW-7651/duplicate-conversations) ## How to reproduce 1. On a WhatsApp Cloud inbox with "reopen the same conversation" (lock to single conversation) enabled. 2. Have a coexistence contact whose webhooks alternate between carrying the phone `wa_id` and only the BSUID identity. 3. Before: each identity opens its own conversation → duplicates. After: incoming messages reopen the contact's existing conversation regardless of which identity the webhook carried. ## What changed - `Whatsapp::IncomingMessageBaseService#set_conversation` now looks up reusable conversations via `@contact.conversations.where(inbox_id: @inbox.id)` instead of `@contact_inbox.conversations`. - Updated existing specs to wire the conversation's `contact` to the contact_inbox's contact, mirroring production data.
This commit is contained in:
@@ -113,12 +113,14 @@ class Whatsapp::IncomingMessageBaseService
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
# Scope reuse to the contact across all its contact_inboxes in this inbox: WhatsApp coexistence
|
||||
# gives one contact multiple source_ids (phone + BSUID), so reopen must not be limited to a single contact_inbox.
|
||||
conversations = @contact.conversations.where(inbox_id: @inbox.id)
|
||||
# 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
|
||||
conversations.last
|
||||
else
|
||||
@contact_inbox.conversations
|
||||
.where.not(status: :resolved).last
|
||||
conversations.where.not(status: :resolved).last
|
||||
end
|
||||
return if @conversation
|
||||
|
||||
|
||||
@@ -34,8 +34,8 @@ describe Whatsapp::IncomingMessageService do
|
||||
|
||||
it 'appends to last conversation when if conversation already exists' do
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: params[:messages].first[:from])
|
||||
2.times.each { create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox) }
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
2.times.each { create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact) }
|
||||
last_conversation = create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(3)
|
||||
@@ -46,7 +46,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
|
||||
@@ -59,7 +59,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
it 'creates a new conversation if last conversation is resolved and lock to single conversation is disabled' do
|
||||
whatsapp_channel.inbox.update(lock_to_single_conversation: false)
|
||||
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
|
||||
# new conversation should be created
|
||||
@@ -70,7 +70,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
it 'will not create a new conversation if last conversation is not resolved and lock to single conversation is disabled' do
|
||||
whatsapp_channel.inbox.update(lock_to_single_conversation: false)
|
||||
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: Conversation.statuses.except('resolved').keys.sample)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# new conversation should be created
|
||||
@@ -238,7 +238,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
end
|
||||
|
||||
before do
|
||||
create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox)
|
||||
create(:conversation, inbox: whatsapp_channel.inbox, contact_inbox: contact_inbox, contact: contact_inbox.contact)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
end
|
||||
|
||||
@@ -453,7 +453,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
|
||||
it 'appends to existing contact if contact inbox exists' do
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: wa_id)
|
||||
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)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(1)
|
||||
@@ -468,7 +468,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
context 'when a contact inbox exists in the old format without 9 included' do
|
||||
it 'appends to existing contact' do
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: wa_id)
|
||||
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)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(1)
|
||||
@@ -480,7 +480,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
context 'when a contact inbox exists in the new format with 9 included' do
|
||||
it 'appends to existing contact' do
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: '5541988887777')
|
||||
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)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(1)
|
||||
@@ -515,7 +515,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
# Normalized format removes the 9 after country code
|
||||
normalized_wa_id = '541123456789'
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: normalized_wa_id)
|
||||
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)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(1)
|
||||
@@ -532,7 +532,7 @@ describe Whatsapp::IncomingMessageService do
|
||||
context 'when a contact inbox exists with the same format' do
|
||||
it 'appends to existing contact' do
|
||||
contact_inbox = create(:contact_inbox, inbox: whatsapp_channel.inbox, source_id: wa_id)
|
||||
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)
|
||||
described_class.new(inbox: whatsapp_channel.inbox, params: params).perform
|
||||
# no new conversation should be created
|
||||
expect(whatsapp_channel.inbox.conversations.count).to eq(1)
|
||||
|
||||
Reference in New Issue
Block a user