diff --git a/app/jobs/inboxes/bulk_auto_assignment_job.rb b/app/jobs/inboxes/bulk_auto_assignment_job.rb new file mode 100644 index 000000000..9e808648b --- /dev/null +++ b/app/jobs/inboxes/bulk_auto_assignment_job.rb @@ -0,0 +1,47 @@ +class Inboxes::BulkAutoAssignmentJob < ApplicationJob + queue_as :scheduled_jobs + include BillingHelper + + def perform + Account.feature_assignment_v2.find_each do |account| + if should_skip_auto_assignment?(account) + Rails.logger.info("Skipping auto assignment for account #{account.id}") + next + end + + account.inboxes.where(enable_auto_assignment: true).find_each do |inbox| + process_assignment(inbox) + end + end + end + + private + + def process_assignment(inbox) + allowed_agent_ids = inbox.member_ids_with_assignment_capacity + + if allowed_agent_ids.blank? + Rails.logger.info("No agents available to assign conversation to inbox #{inbox.id}") + return + end + + assign_conversations(inbox, allowed_agent_ids) + end + + def assign_conversations(inbox, allowed_agent_ids) + unassigned_conversations = inbox.conversations.unassigned.open.limit(Limits::AUTO_ASSIGNMENT_BULK_LIMIT) + unassigned_conversations.find_each do |conversation| + ::AutoAssignment::AgentAssignmentService.new( + conversation: conversation, + allowed_agent_ids: allowed_agent_ids + ).perform + Rails.logger.info("Assigned conversation #{conversation.id} to agent #{allowed_agent_ids.first}") + end + end + + def should_skip_auto_assignment?(account) + return false unless ChatwootApp.chatwoot_cloud? + + default_plan?(account) + end +end diff --git a/config/schedule.yml b/config/schedule.yml index c45d395bf..b5b21b4ce 100644 --- a/config/schedule.yml +++ b/config/schedule.yml @@ -46,3 +46,10 @@ delete_accounts_job: cron: '0 1 * * *' class: 'Internal::DeleteAccountsJob' queue: scheduled_jobs + +# executed every 15 minutes +# to assign unassigned conversations for all inboxes +bulk_auto_assignment_job: + cron: '*/15 * * * *' + class: 'Inboxes::BulkAutoAssignmentJob' + queue: scheduled_jobs diff --git a/lib/limits.rb b/lib/limits.rb index 5a50f2f8f..7a2371207 100644 --- a/lib/limits.rb +++ b/lib/limits.rb @@ -5,6 +5,7 @@ module Limits OUT_OF_OFFICE_MESSAGE_MAX_LENGTH = 10_000 GREETING_MESSAGE_MAX_LENGTH = 10_000 CATEGORIES_PER_PAGE = 1000 + AUTO_ASSIGNMENT_BULK_LIMIT = 100 def self.conversation_message_per_minute_limit ENV.fetch('CONVERSATION_MESSAGE_PER_MINUTE_LIMIT', '200').to_i diff --git a/spec/jobs/inboxes/bulk_auto_assignment_job_spec.rb b/spec/jobs/inboxes/bulk_auto_assignment_job_spec.rb new file mode 100644 index 000000000..5e7e3d7cc --- /dev/null +++ b/spec/jobs/inboxes/bulk_auto_assignment_job_spec.rb @@ -0,0 +1,93 @@ +require 'rails_helper' + +RSpec.describe Inboxes::BulkAutoAssignmentJob do + let(:account) { create(:account, custom_attributes: { 'plan_name' => 'Startups' }) } + let(:agent) { create(:user, account: account, role: :agent, auto_offline: false) } + let(:inbox) { create(:inbox, account: account) } + let!(:conversation) { create(:conversation, account: account, inbox: inbox, assignee: nil, status: :open) } + let(:assignment_service) { double } + + describe '#perform' do + before do + allow(assignment_service).to receive(:perform) + end + + context 'when inbox has inbox members' do + before do + create(:inbox_member, user: agent, inbox: inbox) + account.enable_features!('assignment_v2') + inbox.update!(enable_auto_assignment: true) + end + + it 'assigns unassigned conversations in enabled inboxes' do + allow(AutoAssignment::AgentAssignmentService).to receive(:new).with( + conversation: conversation, + allowed_agent_ids: [agent.id] + ).and_return(assignment_service) + + described_class.perform_now + expect(AutoAssignment::AgentAssignmentService).to have_received(:new).with( + conversation: conversation, + allowed_agent_ids: [agent.id] + ) + end + + it 'skips inboxes with auto assignment disabled' do + inbox.update!(enable_auto_assignment: false) + allow(AutoAssignment::AgentAssignmentService).to receive(:new) + + described_class.perform_now + + expect(AutoAssignment::AgentAssignmentService).not_to have_received(:new).with( + conversation: conversation, + allowed_agent_ids: [agent.id] + ) + end + + context 'when account is on default plan in chatwoot cloud' do + before do + account.update!(custom_attributes: {}) + InstallationConfig.create(name: 'CHATWOOT_CLOUD_PLANS', value: [{ 'name' => 'default' }]) + allow(ChatwootApp).to receive(:chatwoot_cloud?).and_return(true) + end + + it 'skips auto assignment' do + allow(Rails.logger).to receive(:info) + expect(Rails.logger).to receive(:info).with("Skipping auto assignment for account #{account.id}") + + allow(AutoAssignment::AgentAssignmentService).to receive(:new) + expect(AutoAssignment::AgentAssignmentService).not_to receive(:new) + + described_class.perform_now + end + end + end + + context 'when inbox has no members' do + before do + account.enable_features!('assignment_v2') + inbox.update!(enable_auto_assignment: true) + end + + it 'does not assign conversations' do + allow(Rails.logger).to receive(:info) + expect(Rails.logger).to receive(:info).with("No agents available to assign conversation to inbox #{inbox.id}") + + described_class.perform_now + end + end + + context 'when assignment_v2 feature is disabled' do + before do + account.disable_features!('assignment_v2') + end + + it 'skips auto assignment' do + allow(AutoAssignment::AgentAssignmentService).to receive(:new) + expect(AutoAssignment::AgentAssignmentService).not_to receive(:new) + + described_class.perform_now + end + end + end +end