From 4ea22f7f369d40812819f5d19c846eddfdbbd349 Mon Sep 17 00:00:00 2001 From: Sojan Date: Sun, 4 May 2025 05:08:55 -0700 Subject: [PATCH] chore: voice call components working correctly --- app/services/voice/incoming_call_service.rb | 79 ++++++++++++--------- app/services/voice/outgoing_call_service.rb | 59 ++++++++------- 2 files changed, 80 insertions(+), 58 deletions(-) diff --git a/app/services/voice/incoming_call_service.rb b/app/services/voice/incoming_call_service.rb index a6f63d5b5..073732634 100644 --- a/app/services/voice/incoming_call_service.rb +++ b/app/services/voice/incoming_call_service.rb @@ -9,18 +9,27 @@ module Voice begin find_inbox create_contact - create_conversation - create_voice_call_message + + # Use a transaction to ensure the conversation and voice call message are created together + # This ensures the voice call message is created before any auto-assignment activity messages + ActiveRecord::Base.transaction do + create_conversation + create_voice_call_message + end + + # Create activity message separately, after the voice call message + create_activity_message + twiml = generate_twiml_response - + Rails.logger.info("✅ INCOMING CALL: Successfully processed for call_sid=#{caller_info[:call_sid]}") - return twiml + twiml rescue StandardError => e Rails.logger.error("❌ INCOMING CALL ERROR: #{e.message}") Rails.logger.error("❌ INCOMING CALL BACKTRACE: #{e.backtrace[0..5].join("\n")}") - + # Return a simple error TwiML - return error_twiml(e.message) + error_twiml(e.message) end end @@ -37,25 +46,25 @@ module Voice def find_inbox # Find the inbox for this phone number @inbox = account.inboxes - .where(channel_type: 'Channel::Voice') - .joins('INNER JOIN channel_voice ON channel_voice.id = inboxes.channel_id') - .where('channel_voice.phone_number = ?', caller_info[:to_number]) - .first - + .where(channel_type: 'Channel::Voice') + .joins('INNER JOIN channel_voice ON channel_voice.id = inboxes.channel_id') + .where('channel_voice.phone_number = ?', caller_info[:to_number]) + .first + raise "Inbox not found for phone number #{caller_info[:to_number]}" unless @inbox.present? - + Rails.logger.info("📥 FOUND INBOX: inbox_id=#{@inbox.id} for phone=#{caller_info[:to_number]}") end def create_contact # Normalize the phone number phone_number = caller_info[:from_number].strip - + # Find or create the contact @contact = account.contacts.find_or_create_by!(phone_number: phone_number) do |c| c.name = "Contact from #{phone_number}" end - + Rails.logger.info("👤 CONTACT: contact_id=#{@contact.id} name=#{@contact.name} phone=#{@contact.phone_number}") end @@ -65,11 +74,11 @@ module Voice contact_id: @contact.id, inbox_id: @inbox.id ) - + # Set source_id if not already set @contact_inbox.source_id ||= caller_info[:from_number] @contact_inbox.save! - + Rails.logger.info("📬 CONTACT INBOX: id=#{@contact_inbox.id} source_id=#{@contact_inbox.source_id}") # Create a new conversation with call details @@ -117,17 +126,22 @@ module Voice } } } - - # Create the message + + # Create the voice call message only - this ensures it appears first @voice_call_message = Messages::MessageBuilder.new( @contact, @conversation, message_params ).perform - + Rails.logger.info("✉️ VOICE CALL MESSAGE: id=#{@voice_call_message.id} content_type=#{@voice_call_message.content_type}") - - # Create an activity message for the incoming call + + # Broadcast call notification + broadcast_call_status + end + + # Create activity message separately after the voice call message + def create_activity_message activity_message = Messages::MessageBuilder.new( nil, @conversation, @@ -136,11 +150,8 @@ module Voice message_type: :activity } ).perform - + Rails.logger.info("📝 ACTIVITY MESSAGE: id=#{activity_message.id}") - - # Broadcast call notification - broadcast_call_status end def broadcast_call_status @@ -159,19 +170,19 @@ module Voice } } ) - - Rails.logger.info("📢 BROADCAST: Sent incoming_call notification") + + Rails.logger.info('📢 BROADCAST: Sent incoming_call notification') end def generate_twiml_response conference_name = @conversation.additional_attributes['conference_sid'] - + response = Twilio::TwiML::VoiceResponse.new response.say(message: 'Thank you for calling. Please wait while we connect you with an agent.') - + callback_url = "#{base_url}/api/v1/accounts/#{account.id}/channels/voice/webhooks/conference_status" Rails.logger.info("🔗 CONFERENCE CALLBACK URL: #{callback_url}") - + response.dial do |dial| dial.conference( conference_name, @@ -186,7 +197,7 @@ module Voice participantLabel: "caller-#{caller_info[:call_sid].last(8)}" ) end - + Rails.logger.info("📞 TWIML: Generated conference TwiML for #{conference_name}") response.to_s end @@ -195,7 +206,7 @@ module Voice response = Twilio::TwiML::VoiceResponse.new response.say(message: 'We are experiencing technical difficulties with our phone system. Please try again later.') response.hangup - + Rails.logger.info("❌ ERROR TWIML: Generated error TwiML due to: #{message}") response.to_s end @@ -203,7 +214,7 @@ module Voice def base_url url = ENV.fetch('FRONTEND_URL', "https://#{params['host_with_port']}") Rails.logger.info("🌐 BASE URL: Using #{url}") - url.gsub(/\/$/, '') # Remove trailing slash if present + url.gsub(%r{/$}, '') # Remove trailing slash if present end end -end \ No newline at end of file +end diff --git a/app/services/voice/outgoing_call_service.rb b/app/services/voice/outgoing_call_service.rb index 1fc584646..1dab3e458 100644 --- a/app/services/voice/outgoing_call_service.rb +++ b/app/services/voice/outgoing_call_service.rb @@ -4,9 +4,18 @@ module Voice def process find_voice_inbox - create_conversation - initiate_call - create_voice_call_message + + # Create conversation and voice message in a single transaction + # This ensures the voice call message is created before any auto-assignment activity messages + ActiveRecord::Base.transaction do + create_conversation + initiate_call + create_voice_call_message + end + + # Add the activity message separately, after the voice call message + create_activity_message + broadcast_to_agent @conversation end @@ -15,8 +24,8 @@ module Voice def find_voice_inbox @voice_inbox = account.inboxes.find_by(channel_type: 'Channel::Voice') - raise "No Voice channel found" if @voice_inbox.blank? - raise "Contact has no phone number" if contact.phone_number.blank? + raise 'No Voice channel found' if @voice_inbox.blank? + raise 'Contact has no phone number' if contact.phone_number.blank? end def create_conversation @@ -28,7 +37,7 @@ module Voice inbox: @voice_inbox, call_sid: nil # This will be set after call is initiated ).perform - + # Create conference name for outbound call @conference_name = @conversation.additional_attributes['conference_sid'] end @@ -36,19 +45,19 @@ module Voice def initiate_call # Initiate the call using the channel's implementation @call_details = @voice_inbox.channel.initiate_call( - to: contact.phone_number, + to: contact.phone_number, conference_name: @conference_name, agent_id: user.id # Pass the agent ID to track who initiated the call ) - + # Update conversation with call details updated_attributes = @conversation.additional_attributes.merge({ - 'call_sid' => @call_details[:call_sid], - 'call_status' => 'in-progress', - 'requires_agent_join' => true, - 'agent_id' => user.id # Store the agent ID who initiated the call - }) - + 'call_sid' => @call_details[:call_sid], + 'call_status' => 'in-progress', + 'requires_agent_join' => true, + 'agent_id' => user.id # Store the agent ID who initiated the call + }) + @conversation.update!(additional_attributes: updated_attributes) end @@ -75,24 +84,26 @@ module Voice } } } - - # Create the message + + # Create just the voice call message - this ensures it appears first in the conversation @widget_message = Messages::MessageBuilder.new( user, @conversation, message_params ).perform - - # Create an activity message for the outgoing call + + # Update last activity timestamp + @conversation.update(last_activity_at: Time.current) + end + + # Create the activity message in a separate method + def create_activity_message message_service = Voice::MessageUpdateService.new( conversation: @conversation, call_sid: @call_details[:call_sid] ) - + message_service.create_activity_message("Outgoing call to #{contact.name || contact.phone_number}") - - # Update last activity timestamp - @conversation.update(last_activity_at: Time.current) end def broadcast_to_agent @@ -116,7 +127,7 @@ module Voice } } ) - + # Broadcast the conversation and message ActionCableBroadcastJob.perform_later( @conversation.account_id, @@ -128,4 +139,4 @@ module Voice ) end end -end \ No newline at end of file +end