From e585c847780ce5528e36c370f5b15cb5785ba1b7 Mon Sep 17 00:00:00 2001 From: Muhsin Date: Wed, 18 Mar 2026 16:44:51 +0400 Subject: [PATCH] chore: preserve terminal statuses; skip early in-progress --- .../app/services/voice/call_status/manager.rb | 6 +++ .../services/voice/status_update_service.rb | 7 ++++ .../voice/status_update_service_spec.rb | 37 +++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/enterprise/app/services/voice/call_status/manager.rb b/enterprise/app/services/voice/call_status/manager.rb index f10890666..0a9f58d70 100644 --- a/enterprise/app/services/voice/call_status/manager.rb +++ b/enterprise/app/services/voice/call_status/manager.rb @@ -9,6 +9,7 @@ class Voice::CallStatus::Manager current_status = conversation.additional_attributes&.dig('call_status') return if current_status == status + return if preserve_existing_terminal_status?(current_status, status) apply_status(status, duration: duration, timestamp: timestamp) update_message(status) @@ -82,4 +83,9 @@ class Voice::CallStatus::Manager data['data']['meta'].delete('duration') end end + + def preserve_existing_terminal_status?(current_status, next_status) + TERMINAL_STATUSES.include?(current_status) && + TERMINAL_STATUSES.include?(next_status) + end end diff --git a/enterprise/app/services/voice/status_update_service.rb b/enterprise/app/services/voice/status_update_service.rb index 8503d8d3b..3a03efff3 100644 --- a/enterprise/app/services/voice/status_update_service.rb +++ b/enterprise/app/services/voice/status_update_service.rb @@ -21,6 +21,7 @@ class Voice::StatusUpdateService conversation = account.conversations.find_by(identifier: call_sid) return unless conversation + return if ignore_premature_in_progress?(conversation, normalized_status) Voice::CallStatus::Manager.new( conversation: conversation, @@ -57,4 +58,10 @@ class Voice::StatusUpdateService rescue ArgumentError nil end + + def ignore_premature_in_progress?(conversation, normalized_status) + normalized_status == 'in-progress' && + conversation.additional_attributes&.dig('call_direction') == 'inbound' && + !conversation.additional_attributes&.dig('agent_joined') + end end diff --git a/spec/enterprise/services/voice/status_update_service_spec.rb b/spec/enterprise/services/voice/status_update_service_spec.rb index 2366ad17d..5cffeade5 100644 --- a/spec/enterprise/services/voice/status_update_service_spec.rb +++ b/spec/enterprise/services/voice/status_update_service_spec.rb @@ -75,6 +75,43 @@ RSpec.describe Voice::StatusUpdateService do expect(message.content_attributes.dig('data', 'status')).to eq('no-answer') end + it 'does not overwrite no-answer with a later completed callback' do + conversation.update!(additional_attributes: { 'call_direction' => 'inbound', 'call_status' => 'no-answer' }) + message.update!(content_attributes: { data: { call_sid: call_sid, status: 'no-answer' } }) + + described_class.new( + account: account, + call_sid: call_sid, + call_status: 'completed', + payload: { 'CallDuration' => '4' } + ).perform + + conversation.reload + message.reload + + expect(conversation.additional_attributes['call_status']).to eq('no-answer') + expect(message.content_attributes.dig('data', 'status')).to eq('no-answer') + expect(message.content_attributes.dig('data', 'meta', 'duration')).to be_nil + end + + it 'ignores inbound in-progress callbacks before any agent joins' do + conversation + message + + described_class.new( + account: account, + call_sid: call_sid, + call_status: 'in-progress' + ).perform + + conversation.reload + message.reload + + expect(conversation.additional_attributes['call_status']).to eq('ringing') + expect(conversation.additional_attributes['call_started_at']).to be_nil + expect(message.content_attributes.dig('data', 'status')).to eq('ringing') + end + it 'no-ops when conversation not found' do expect do described_class.new(account: account, call_sid: 'UNKNOWN', call_status: 'busy').perform