feat: add call model

This commit is contained in:
Muhsin
2026-04-08 21:21:33 +04:00
parent 00837019b5
commit 54db8dcca5
6 changed files with 131 additions and 1 deletions
+1
View File
@@ -108,6 +108,7 @@ class Conversation < ApplicationRecord
has_many :mentions, dependent: :destroy_async
has_many :messages, dependent: :destroy_async, autosave: true
has_many :calls, dependent: :destroy_async
has_one :csat_survey_response, dependent: :destroy_async
has_many :conversation_participants, dependent: :destroy_async
has_many :notifications, as: :primary_actor, dependent: :destroy_async
+26
View File
@@ -0,0 +1,26 @@
class CreateCalls < ActiveRecord::Migration[7.0]
def change
create_table :calls do |t|
t.bigint :account_id, null: false
t.bigint :inbox_id, null: false
t.bigint :conversation_id, null: false
t.bigint :message_id
t.bigint :accepted_by_agent_id
t.string :provider_call_id, null: false
t.integer :provider, null: false, default: 0
t.integer :direction, null: false
t.string :status, null: false, default: 'ringing'
t.integer :duration_seconds
t.string :end_reason
t.jsonb :meta, default: {}
t.text :transcript
t.timestamps
end
add_index :calls, [:provider, :provider_call_id], unique: true
add_index :calls, [:account_id, :conversation_id]
add_index :calls, :message_id
add_index :calls, :meta, using: :gin
end
end
+23 -1
View File
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.1].define(version: 2026_03_24_102005) do
ActiveRecord::Schema[7.1].define(version: 2026_04_08_170902) do
# These extensions should be enabled to support this database
enable_extension "pg_stat_statements"
enable_extension "pg_trgm"
@@ -261,6 +261,28 @@ ActiveRecord::Schema[7.1].define(version: 2026_03_24_102005) do
t.index ["account_id"], name: "index_automation_rules_on_account_id"
end
create_table "calls", force: :cascade do |t|
t.bigint "account_id", null: false
t.bigint "inbox_id", null: false
t.bigint "conversation_id", null: false
t.bigint "message_id"
t.bigint "accepted_by_agent_id"
t.string "provider_call_id", null: false
t.integer "provider", default: 0, null: false
t.integer "direction", null: false
t.string "status", default: "ringing", null: false
t.integer "duration_seconds"
t.string "end_reason"
t.jsonb "meta", default: {}
t.text "transcript"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id", "conversation_id"], name: "index_calls_on_account_id_and_conversation_id"
t.index ["message_id"], name: "index_calls_on_message_id"
t.index ["meta"], name: "index_calls_on_meta", using: :gin
t.index ["provider", "provider_call_id"], name: "index_calls_on_provider_and_provider_call_id", unique: true
end
create_table "campaigns", force: :cascade do |t|
t.integer "display_id", null: false
t.string "title", null: false
+24
View File
@@ -0,0 +1,24 @@
class Call < ApplicationRecord
# All valid call statuses
STATUSES = %w[ringing in_progress completed no_answer failed].freeze
# Statuses where the call is finished and won't change again
TERMINAL_STATUSES = %w[completed no_answer failed].freeze
enum :provider, { twilio: 0, whatsapp: 1 }
enum :direction, { incoming: 0, outgoing: 1 }
belongs_to :account
belongs_to :inbox
belongs_to :conversation
belongs_to :message, optional: true
belongs_to :accepted_by_agent, class_name: 'User', optional: true
has_one_attached :recording
validates :provider_call_id, presence: true
validates :provider, presence: true
validates :direction, presence: true
validates :status, presence: true, inclusion: { in: STATUSES }
scope :active, -> { where.not(status: TERMINAL_STATUSES) }
end
@@ -17,6 +17,7 @@ module Enterprise::Concerns::Account
has_many :copilot_threads, dependent: :destroy_async
has_many :companies, dependent: :destroy_async
has_many :voice_channels, dependent: :destroy_async, class_name: '::Channel::Voice'
has_many :calls, dependent: :destroy_async
has_one :saml_settings, dependent: :destroy_async, class_name: 'AccountSamlSettings'
end
+56
View File
@@ -0,0 +1,56 @@
# Voice as Twilio SMS Capability — QA Test Cases
## Prerequisites
* Enterprise edition
* `channel_voice` feature flag enabled: `Account.find(<id>).enable_features('channel_voice'); Account.find(<id>).save!`
* Twilio account with a voice-capable phone number
* Twilio API Key SID + API Key Secret
---
## Voice inbox creation
1. ~~Create Voice inbox via Voice tile with valid credentials — Channel created with voice_enabled: true, twiml_app_sid auto-provisioned, SMS and calls work~~
2. ~~Create Voice inbox with a number that doesn't support voice (e.g., short code) — Fails with "This phone number does not support voice calls" error~~
3. ~~Create Voice inbox with invalid Twilio credentials — Fails with clear error message~~
## Enable voice on existing SMS inbox
5. ~~SMS inbox (phone number, no ~~`~~api_key_sid~~`~~) → enable voice — Prompts for both ~~`~~api_key_sid~~`~~ and ~~`~~api_key_secret~~`~~, provisions TwiML app~~
6. ~~SMS inbox (phone number, has ~~`~~api_key_sid~~`~~, no ~~`~~api_key_secret~~`~~) → enable voice — Prompts for ~~`~~api_key_secret~~`~~ only, provisions TwiML app~~
7. ~~SMS inbox (phone number, has api_key_sid + api_key_secret) → enable voice — No credential fields, just toggle and update~~
8. ~~SMS inbox (messaging_service_sid only) — Voice tab not shown (voice requires phone number)~~
9. ~~Twilio WhatsApp inbox — Voice tab not shown (~~`~~medium !== 'sms'~~`~~)~~
10. ~~SMS inbox without ~~`~~channel_voice~~`~~ feature flag — Voice tab not shown~~
11. ~~Enable voice via API when ~~`~~channel_voice~~`~~ flag is off — Voice fields silently ignored~~
## Disable voice
12. ~~SMS+Voice channel → disable voice — TwiML app deleted from Twilio, ~~`~~twiml_app_sid~~`~~ cleared, ~~`~~api_key_sid~~`~~ and ~~`~~api_key_secret~~`~~ preserved, SMS still works~~
13. ~~Disable voice when Twilio API fails (e.g., TwiML app already deleted) — Local cleanup still happens, no error shown to user~~
## Re-enable voice
14. ~~Previously disabled voice channel (has api_key_sid + api_key_secret) → re-enable — No credentials needed, new TwiML app provisioned~~
15. ~~Previously disabled voice channel (has api_key_sid, api_key_secret is nil) → re-enable — Prompts for api_key_secret~~
## Voice settings tab UI
16. ~~Voice enabled + configured — Shows toggle (on) + voice webhook URLs~~
17. ~~Voice disabled + credentials exist — Shows toggle (off) only, no credential fields~~
18. ~~Voice disabled + no credentials → toggle on — Shows credential fields when toggled on~~
19. ~~Click Update button — Shows spinner while saving~~
20. ~~After update completes — Tab stays selected (doesn't jump to another tab)~~
21. ~~Voice-enabled inbox still shows Business Hours, CSAT, Help Center, and Configuration tabs~~
## Inbox list and naming
22. ~~Voice-enabled TwilioSms inbox in inbox list — Shows "Voice" as channel name~~
23. ~~Voice-disabled TwilioSms inbox in inbox list — Shows "Twilio SMS" as channel name~~
24. ~~Voice-enabled inbox icon — Shows voice icon (phone)~~
## Inbox creation UI
25. ~~Voice tile shows "Coming Soon" when ~~`~~channel_voice~~`~~ feature flag is off~~
26. ~~Voice tile is clickable when ~~`~~channel_voice~~`~~ feature flag is on~~
27. ~~Voice tile description reads "Handle voice calls and SMS with Twilio"~~