diff --git a/app/controllers/pager_tree/integrations/live_call_routing/twilio/v3_controller.rb b/app/controllers/pager_tree/integrations/live_call_routing/twilio/v3_controller.rb index 852b7e7..813e46b 100644 --- a/app/controllers/pager_tree/integrations/live_call_routing/twilio/v3_controller.rb +++ b/app/controllers/pager_tree/integrations/live_call_routing/twilio/v3_controller.rb @@ -27,6 +27,12 @@ def queue_status head :ok end + def call_status + ::PagerTree::Integrations.deferred_request_class.constantize.perform_later_from_request!(request) + + head :ok + end + def queue_status_deferred(deferred_request) params = deferred_request.params @@ -42,6 +48,20 @@ def queue_status_deferred(deferred_request) @integration.adapter_process_queue_status_deferred end + def call_status_deferred(deferred_request) + params = deferred_request.params + + id = params.dig("id") + @integration = find_integration(id) + + deferred_request.account_id = @integration.account_id + @integration.adapter_source_log = @integration.logs.create!(level: :info, format: :json, message: deferred_request.request) if @integration.log_incoming_requests? + @integration.adapter_incoming_request_params = params + @integration.adapter_incoming_deferred_request = deferred_request + + @integration.adapter_process_call_status_deferred + end + private def set_integration diff --git a/app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb b/app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb index 416f51f..1fd0339 100644 --- a/app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb +++ b/app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb @@ -7,6 +7,7 @@ class LiveCallRouting::Twilio::V3 < Integration {key: :api_region, type: :string, default: "ashburn.us1"}, {key: :force_input, type: :boolean, default: false}, {key: :record, type: :boolean, default: false}, + {key: :send_straight_to_voicemail, type: :boolean, default: false}, {key: :record_email, type: :string, default: ""}, {key: :banned_phone, type: :string, default: ""}, {key: :dial_pause, type: :integer}, @@ -30,6 +31,7 @@ class LiveCallRouting::Twilio::V3 < Integration validates :option_api_region, inclusion: {in: API_REGIONS} validates :option_force_input, inclusion: {in: [true, false]} validates :option_record, inclusion: {in: [true, false]} + validates :option_send_straight_to_voicemail, inclusion: {in: [true, false]} validates :option_max_wait_time, numericality: {greater_than_or_equal_to: 30, less_than_or_equal_to: 3600}, allow_nil: true validate :validate_record_emails @@ -40,6 +42,7 @@ class LiveCallRouting::Twilio::V3 < Integration self.option_api_region ||= "ashburn.us1" self.option_force_input ||= false self.option_record ||= false + self.option_send_straight_to_voicemail ||= false self.option_record_email ||= "" self.option_banned_phone ||= "" end @@ -189,6 +192,33 @@ def adapter_response_incoming return adapter_controller&.render(xml: _twiml.to_xml) end + + if adapter_alert.meta["live_call_status_callback_set"] != true + begin + # give us status updates on the call, so we can clean up if they hang up before leaving a message + _call.update( + status_callback: PagerTree::Integrations::Engine.routes.url_helpers.call_status_live_call_routing_twilio_v3_url(id, thirdparty_id: _thirdparty_id), + status_callback_method: "POST", + url: adapter_controller&.url_for || endpoint + ) + + adapter_alert.meta["live_call_status_callback_set"] = true + adapter_alert.save! + rescue ::Twilio::REST::RestError => e + if e.code == 21220 + # 21220 - Unable to update record. Call is not in-progress. Cannot redirect. + adapter_alert.logs.create!(message: "Updating the call for status callbacks failed. The caller has already hung up.") + else + adapter_alert.logs.create!(message: "Updating the call for status callbacks failed. #{e.message}") + end + + adapter_alert.logs.create!(message: "Marking alert as resolved due to call update failure.") + adapter_alert.resolve!(self, force: true) + end + + return adapter_controller&.render(xml: _twiml.to_xml) + end + # if this was attached to a router if !adapter_alert.meta["live_call_router_team_prefix_ids"].present? && routers.size > 0 && account.subscription_feature_routers? adapter_alert.logs.create!(message: "Routed to router. Attempting to get a list of teams...") @@ -235,6 +265,12 @@ def adapter_response_incoming return adapter_controller&.render(xml: _twiml.to_xml) end + if option_record && option_send_straight_to_voicemail && adapter_alert.meta["live_call_send_straight_to_voicemail"].nil? + # flag this call to send straight to voicemail + adapter_alert.meta["live_call_send_straight_to_voicemail"] = true + adapter_alert.save! + end + if !adapter_alert.meta["live_call_welcome"] && option_welcome_media.present? adapter_alert.logs.create!(message: "Play welcome media to caller.") _twiml.play(url: option_welcome_media.url) @@ -245,6 +281,17 @@ def adapter_response_incoming if selected_team adapter_alert.logs.create!(message: "Caller selected team '#{selected_team.name}'.") if _teams_size > 1 || option_force_input + if adapter_alert.meta["live_call_send_straight_to_voicemail"] == true + adapter_alert.destination_teams = [selected_team] + adapter_alert.save! + + adapter_alert.logs.create!(message: "Send caller straight to voicemail (integration option).") + + _twiml.redirect(PagerTree::Integrations::Engine.routes.url_helpers.dropped_live_call_routing_twilio_v3_url(id, thirdparty_id: _thirdparty_id), method: "POST") + + return adapter_controller&.render(xml: _twiml.to_xml) + end + adapter_alert.logs.create!(message: "Play please wait media to caller.") _twiml.play(url: option_please_wait_media_url) friendly_name = adapter_alert.id @@ -358,11 +405,20 @@ def adapter_response_dropped LiveCallRouting::Twilio::V3Mailer.with(email: email, alert: adapter_alert, from: adapter_incoming_request_params.dig("From"), recording_url: recording_url).call_recording.deliver_later end end + + if adapter_alert.meta["live_call_send_straight_to_voicemail"] == true + adapter_alert.logs.create!(message: "Call was sent straight to voicemail and caller left a message. Routing the alert.") + + # kick off the alert workflow + adapter_alert.route_later + adapter_alert.logs.create!(message: "Successfully enqueued alert team workflow.") + end elsif option_record adapter_alert.logs.create!(message: "No one is available to answer this call. Requesting voicemail recording.") _twiml.play(url: option_no_answer_media_url) _twiml.record(max_length: 60) else + # A friendly goodbye (when the integration has been configured to not record) if option_no_answer_no_record_media_url.present? adapter_alert.logs.create!(message: "No one is available to answer this call. Play media. Hangup on caller.") _twiml.play(url: option_no_answer_no_record_media_url) @@ -383,15 +439,33 @@ def adapter_process_queue_status_deferred if queue_result == "hangup" self.adapter_alert = alerts.find_by(thirdparty_id: _thirdparty_id) adapter_alert.logs.create!(message: "Caller hungup while waiting in queue.") - adapter_alert.resolve!(self) + adapter_alert.resolve!(self, force: true) queue_destroy end adapter_source_log&.save! end + def adapter_process_call_status_deferred + call_status = adapter_incoming_request_params.dig("CallStatus") + adapter_source_log&.sublog("Processing call status #{call_status}") + + if ["completed"].include?(call_status) + self.adapter_alert = alerts.find_by(thirdparty_id: _thirdparty_id) + + if adapter_alert.present? && adapter_alert.meta["live_call_send_straight_to_voicemail"] == true && !adapter_alert.additional_data.any? { |x| x["label"] == "Voicemail" } + adapter_alert.logs.create!(message: "Caller hung up without leaving a message. Marking alert as resolved.") + adapter_alert.resolve!(self, force: true) + elsif adapter_alert.present? && adapter_alert.meta["live_call_send_straight_to_voicemail"] != true && !adapter_alert.meta["live_call_queue_sid"].present? + adapter_alert.logs.create!(message: "Caller hungup before being put in a queue. Marking alert as resolved.") + adapter_alert.resolve!(self, force: true) + end + end + end + def adapter_process_outgoing return unless adapter_alert.source_id == id + return if adapter_alert.meta["live_call_send_straight_to_voicemail"] == true event = adapter_outgoing_event.event_name.to_s if event == "alert_acknowledged" diff --git a/app/views/pager_tree/integrations/live_call_routing/twilio/v3/_form_options.html.erb b/app/views/pager_tree/integrations/live_call_routing/twilio/v3/_form_options.html.erb index a342aff..b51b412 100644 --- a/app/views/pager_tree/integrations/live_call_routing/twilio/v3/_form_options.html.erb +++ b/app/views/pager_tree/integrations/live_call_routing/twilio/v3/_form_options.html.erb @@ -1,4 +1,9 @@ -
+<% + x_data = { + option_record: form.object.option_record, + } +%> +
>
<%= form.label :option_account_sid %> <%= form.text_field :option_account_sid, class: "form-control" %> @@ -78,12 +83,18 @@
- <%= form.check_box :option_record, class: "form-checkbox" %> + <%= form.check_box :option_record, class: "form-checkbox", "x-model": "option_record" %> <%= form.label :option_record, class: "inline-block" %>

<%== t(".option_record_hint_html") %>

-
+
+ <%= form.check_box :option_send_straight_to_voicemail, class: "form-checkbox" %> + <%= form.label :option_send_straight_to_voicemail, class: "inline-block" %> +

<%== t(".option_send_straight_to_voicemail_hint_html") %>

+
+ +
<%= form.label :option_record_emails_list %> <%= form.text_field :option_record_emails_list, class: "form-control", data: { tagify_target: "input" } %>

<%== t(".option_record_emails_list_hint_html") %>

diff --git a/app/views/pager_tree/integrations/live_call_routing/twilio/v3/_show_options.html.erb b/app/views/pager_tree/integrations/live_call_routing/twilio/v3/_show_options.html.erb index c4f81ec..9ecc125 100644 --- a/app/views/pager_tree/integrations/live_call_routing/twilio/v3/_show_options.html.erb +++ b/app/views/pager_tree/integrations/live_call_routing/twilio/v3/_show_options.html.erb @@ -138,6 +138,17 @@
+<% if integration.option_record %> +
+
+ <%= t("activerecord.attributes.pager_tree/integrations/live_call_routing/twilio/v3.option_send_straight_to_voicemail") %> +
+
+ <%= render partial: "shared/components/badge_enabled", locals: { enabled: integration.option_send_straight_to_voicemail } %> +
+
+<% end %> +
<%= t("activerecord.attributes.pager_tree/integrations/live_call_routing/twilio/v3.option_no_answer_media") %> @@ -189,23 +200,25 @@
-
-
- <%= t("activerecord.attributes.pager_tree/integrations/live_call_routing/twilio/v3.option_record_emails_list") %> -
-
-
- <% integration.option_record_emails.each do |email| %> -

- <%= link_to email, "mailto:#{email}" %> +<% if integration.option_record %> +

+
+ <%= t("activerecord.attributes.pager_tree/integrations/live_call_routing/twilio/v3.option_record_emails_list") %> +
+
+
+ <% integration.option_record_emails.each do |email| %> +

+ <%= link_to email, "mailto:#{email}" %> +

+ <% end %> + - <% end %> - -
-
-
+
+ +
+<% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml index 0afe42e..5ab7fc4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -99,6 +99,7 @@ en: option_no_answer_no_record_media_hint_html: "An optional .mp3 recording to be played when no one answers if you select to not record voicemails (default: \"No one is available to answer this call. Goodbye.\")" option_force_input_hint_html: "Force the caller to select a team (even if the integration only has one team)" option_record_hint_html: "Record a voicemail when no one acknowledges the call" + option_send_straight_to_voicemail_hint_html: "Send the caller straight to voicemail without ringing any team members (only applies if 'Record a voicemail' is enabled). The alert will be routed after the caller leaves a voicemail." option_record_emails_list_hint_html: "List of email addresses to notify when a voicemail has been recorded" option_banned_phones_list_hint_html: "List of phone numbers that are banned from calling the integration" option_max_wait_time_hint_html: "The maximum amount of time (in seconds) that the caller will wait before transferring the call to the voicemail. If set to nil, it will wait indefinitely." @@ -263,6 +264,7 @@ en: option_no_answer_no_record_media: "No Answer (No Voicemail) Recording" option_force_input: "Force Caller Input" option_record: "Voicemail" + option_send_straight_to_voicemail: "Send Straight to Voicemail" option_record_emails_list: "Voicemail Emails" option_banned_phones_list: "Banned Phones" option_max_wait_time: "Max Wait Time (seconds)" diff --git a/config/routes.rb b/config/routes.rb index b175e81..987efd1 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,6 +5,7 @@ member do get :music post :queue_status + post :call_status post :dropped end end