Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4dba113ce |
@@ -1,8 +1,8 @@
|
||||
class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Accounts::Conversations::BaseController
|
||||
# assigns agent/team to a conversation
|
||||
# assigns agent/team/bot to a conversation
|
||||
def create
|
||||
if params.key?(:assignee_id)
|
||||
set_agent
|
||||
set_assignee
|
||||
elsif params.key?(:team_id)
|
||||
set_team
|
||||
else
|
||||
@@ -12,18 +12,26 @@ class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Account
|
||||
|
||||
private
|
||||
|
||||
def set_agent
|
||||
@agent = Current.account.users.find_by(id: params[:assignee_id])
|
||||
@conversation.assignee = @agent
|
||||
def set_assignee
|
||||
@assignee = case params[:assignee_type]
|
||||
when 'AgentBot'
|
||||
Current.account.agent_bots.find_by(id: params[:assignee_id])
|
||||
else
|
||||
Current.account.users.find_by(id: params[:assignee_id])
|
||||
end
|
||||
|
||||
@conversation.assignee = @assignee
|
||||
@conversation.save!
|
||||
render_agent
|
||||
render_assignee
|
||||
end
|
||||
|
||||
def render_agent
|
||||
if @agent.nil?
|
||||
def render_assignee
|
||||
if @assignee.nil?
|
||||
render json: nil
|
||||
elsif @assignee.is_a?(AgentBot)
|
||||
render json: @assignee.webhook_data
|
||||
else
|
||||
render partial: 'api/v1/models/agent', formats: [:json], locals: { resource: @agent }
|
||||
render partial: 'api/v1/models/agent', formats: [:json], locals: { resource: @assignee }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -50,6 +50,18 @@ class AgentBotListener < BaseListener
|
||||
process_webhook_bot_event(inbox.agent_bot, payload)
|
||||
end
|
||||
|
||||
def assignee_changed(event)
|
||||
conversation = extract_conversation_and_account(event)[0]
|
||||
return unless conversation.assigned_to_bot?
|
||||
|
||||
agent_bot = conversation.assigned_bot
|
||||
return if agent_bot.blank? || agent_bot.outgoing_url.blank?
|
||||
|
||||
event_name = __method__.to_s
|
||||
payload = conversation.webhook_data.merge(event: event_name)
|
||||
process_webhook_bot_event(agent_bot, payload)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def connected_agent_bot_exist?(inbox)
|
||||
|
||||
@@ -24,6 +24,7 @@ class AgentBot < ApplicationRecord
|
||||
has_many :agent_bot_inboxes, dependent: :destroy_async
|
||||
has_many :inboxes, through: :agent_bot_inboxes
|
||||
has_many :messages, as: :sender, dependent: :nullify
|
||||
has_many :assigned_conversations, as: :assignee, class_name: 'Conversation', dependent: :nullify
|
||||
belongs_to :account, optional: true
|
||||
enum bot_type: { webhook: 0 }
|
||||
|
||||
|
||||
+42
-18
@@ -6,6 +6,7 @@
|
||||
# additional_attributes :jsonb
|
||||
# agent_last_seen_at :datetime
|
||||
# assignee_last_seen_at :datetime
|
||||
# assignee_type :string
|
||||
# cached_label_list :text
|
||||
# contact_last_seen_at :datetime
|
||||
# custom_attributes :jsonb
|
||||
@@ -31,22 +32,23 @@
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# conv_acid_inbid_stat_asgnid_idx (account_id,inbox_id,status,assignee_id)
|
||||
# index_conversations_on_account_id (account_id)
|
||||
# index_conversations_on_account_id_and_display_id (account_id,display_id) UNIQUE
|
||||
# index_conversations_on_assignee_id_and_account_id (assignee_id,account_id)
|
||||
# index_conversations_on_campaign_id (campaign_id)
|
||||
# index_conversations_on_contact_id (contact_id)
|
||||
# index_conversations_on_contact_inbox_id (contact_inbox_id)
|
||||
# index_conversations_on_first_reply_created_at (first_reply_created_at)
|
||||
# index_conversations_on_id_and_account_id (account_id,id)
|
||||
# index_conversations_on_inbox_id (inbox_id)
|
||||
# index_conversations_on_priority (priority)
|
||||
# index_conversations_on_status_and_account_id (status,account_id)
|
||||
# index_conversations_on_status_and_priority (status,priority)
|
||||
# index_conversations_on_team_id (team_id)
|
||||
# index_conversations_on_uuid (uuid) UNIQUE
|
||||
# index_conversations_on_waiting_since (waiting_since)
|
||||
# conv_acid_inbid_stat_asgnid_idx (account_id,inbox_id,status,assignee_id)
|
||||
# index_conversations_on_account_id (account_id)
|
||||
# index_conversations_on_account_id_and_display_id (account_id,display_id) UNIQUE
|
||||
# index_conversations_on_assignee_id_and_account_id (assignee_id,account_id)
|
||||
# index_conversations_on_assignee_type_and_assignee_id (assignee_type,assignee_id)
|
||||
# index_conversations_on_campaign_id (campaign_id)
|
||||
# index_conversations_on_contact_id (contact_id)
|
||||
# index_conversations_on_contact_inbox_id (contact_inbox_id)
|
||||
# index_conversations_on_first_reply_created_at (first_reply_created_at)
|
||||
# index_conversations_on_id_and_account_id (account_id,id)
|
||||
# index_conversations_on_inbox_id (inbox_id)
|
||||
# index_conversations_on_priority (priority)
|
||||
# index_conversations_on_status_and_account_id (status,account_id)
|
||||
# index_conversations_on_status_and_priority (status,priority)
|
||||
# index_conversations_on_team_id (team_id)
|
||||
# index_conversations_on_uuid (uuid) UNIQUE
|
||||
# index_conversations_on_waiting_since (waiting_since)
|
||||
#
|
||||
|
||||
class Conversation < ApplicationRecord
|
||||
@@ -59,6 +61,7 @@ class Conversation < ApplicationRecord
|
||||
include SortHandler
|
||||
include PushDataHelper
|
||||
include ConversationMuteHelpers
|
||||
include Events::Types
|
||||
|
||||
validates :account_id, presence: true
|
||||
validates :inbox_id, presence: true
|
||||
@@ -68,6 +71,7 @@ class Conversation < ApplicationRecord
|
||||
validates :custom_attributes, jsonb_attributes_length: true
|
||||
validates :uuid, uniqueness: true
|
||||
validate :validate_referer_url
|
||||
validate :validate_assignee_belongs_to_account
|
||||
|
||||
enum status: { open: 0, resolved: 1, pending: 2, snoozed: 3 }
|
||||
enum priority: { low: 0, medium: 1, high: 2, urgent: 3 }
|
||||
@@ -96,7 +100,7 @@ class Conversation < ApplicationRecord
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :inbox
|
||||
belongs_to :assignee, class_name: 'User', optional: true, inverse_of: :assigned_conversations
|
||||
belongs_to :assignee, polymorphic: true, optional: true
|
||||
belongs_to :contact
|
||||
belongs_to :contact_inbox
|
||||
belongs_to :team, optional: true
|
||||
@@ -195,6 +199,14 @@ class Conversation < ApplicationRecord
|
||||
dispatcher_dispatch(CONVERSATION_UPDATED, previous_changes)
|
||||
end
|
||||
|
||||
def assigned_to_bot?
|
||||
assignee_type == 'AgentBot'
|
||||
end
|
||||
|
||||
def assigned_bot
|
||||
assignee if assigned_to_bot?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def execute_after_update_commit_callbacks
|
||||
@@ -273,7 +285,8 @@ class Conversation < ApplicationRecord
|
||||
CONVERSATION_RESOLVED => -> { saved_change_to_status? && resolved? },
|
||||
CONVERSATION_STATUS_CHANGED => -> { saved_change_to_status? },
|
||||
CONVERSATION_READ => -> { saved_change_to_contact_last_seen_at? },
|
||||
CONVERSATION_CONTACT_CHANGED => -> { saved_change_to_contact_id? }
|
||||
CONVERSATION_CONTACT_CHANGED => -> { saved_change_to_contact_id? },
|
||||
ASSIGNEE_CHANGED => -> { saved_change_to_assignee_id? }
|
||||
}.each do |event, condition|
|
||||
condition.call && dispatcher_dispatch(event, status_change)
|
||||
end
|
||||
@@ -309,6 +322,17 @@ class Conversation < ApplicationRecord
|
||||
self['additional_attributes']['referer'] = nil unless url_valid?(additional_attributes['referer'])
|
||||
end
|
||||
|
||||
def validate_assignee_belongs_to_account
|
||||
return unless assignee
|
||||
|
||||
case assignee
|
||||
when User
|
||||
errors.add(:assignee, 'must belong to the same account') unless assignee.accounts.include?(account)
|
||||
when AgentBot
|
||||
errors.add(:assignee, 'must belong to the same account') unless assignee.account_id == account_id || assignee.system_bot?
|
||||
end
|
||||
end
|
||||
|
||||
# creating db triggers
|
||||
trigger.before(:insert).for_each(:row) do
|
||||
"NEW.display_id := nextval('conv_dpid_seq_' || NEW.account_id);"
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ class User < ApplicationRecord
|
||||
has_many :accounts, through: :account_users
|
||||
accepts_nested_attributes_for :account_users
|
||||
|
||||
has_many :assigned_conversations, foreign_key: 'assignee_id', class_name: 'Conversation', dependent: :nullify, inverse_of: :assignee
|
||||
has_many :assigned_conversations, as: :assignee, class_name: 'Conversation', dependent: :nullify
|
||||
alias_attribute :conversations, :assigned_conversations
|
||||
has_many :csat_survey_responses, foreign_key: 'assigned_agent_id', dependent: :nullify, inverse_of: :assigned_agent
|
||||
has_many :conversation_participants, dependent: :destroy_async
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
class MakeConversationAssigneePolymorphic < ActiveRecord::Migration[7.1]
|
||||
def change
|
||||
add_column :conversations, :assignee_type, :string
|
||||
add_index :conversations, [:assignee_type, :assignee_id]
|
||||
|
||||
# Update existing records to use User type
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
execute "UPDATE conversations SET assignee_type = 'User' WHERE assignee_id IS NOT NULL"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
+3
-1
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema[7.1].define(version: 2025_07_10_145708) do
|
||||
ActiveRecord::Schema[7.1].define(version: 2025_07_14_205206) do
|
||||
# These extensions should be enabled to support this database
|
||||
enable_extension "pg_stat_statements"
|
||||
enable_extension "pg_trgm"
|
||||
@@ -585,11 +585,13 @@ ActiveRecord::Schema[7.1].define(version: 2025_07_10_145708) do
|
||||
t.bigint "sla_policy_id"
|
||||
t.datetime "waiting_since"
|
||||
t.text "cached_label_list"
|
||||
t.string "assignee_type"
|
||||
t.index ["account_id", "display_id"], name: "index_conversations_on_account_id_and_display_id", unique: true
|
||||
t.index ["account_id", "id"], name: "index_conversations_on_id_and_account_id"
|
||||
t.index ["account_id", "inbox_id", "status", "assignee_id"], name: "conv_acid_inbid_stat_asgnid_idx"
|
||||
t.index ["account_id"], name: "index_conversations_on_account_id"
|
||||
t.index ["assignee_id", "account_id"], name: "index_conversations_on_assignee_id_and_account_id"
|
||||
t.index ["assignee_type", "assignee_id"], name: "index_conversations_on_assignee_type_and_assignee_id"
|
||||
t.index ["campaign_id"], name: "index_conversations_on_campaign_id"
|
||||
t.index ["contact_id"], name: "index_conversations_on_contact_id"
|
||||
t.index ["contact_inbox_id"], name: "index_conversations_on_contact_inbox_id"
|
||||
|
||||
@@ -4,10 +4,15 @@ require 'rails_helper'
|
||||
|
||||
shared_examples_for 'assignment_handler' do
|
||||
describe '#update_team' do
|
||||
let(:conversation) { create(:conversation, assignee: create(:user)) }
|
||||
let(:conversation) { create(:conversation) }
|
||||
let(:assignee) { create(:user, account: conversation.account, role: :agent) }
|
||||
let(:agent) do
|
||||
create(:user, email: 'agent@example.com', account: conversation.account, role: :agent, auto_offline: false)
|
||||
end
|
||||
|
||||
before do
|
||||
conversation.update!(assignee: assignee)
|
||||
end
|
||||
let(:team) do
|
||||
create(:team, account: conversation.account, allow_auto_assign: false)
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user