From 0d490640f200754d10081fe2e39ed015baa11f89 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Tue, 16 Dec 2025 14:43:15 +0530 Subject: [PATCH] feat: Conversation workflow backend changes (#13070) Extracted the backend changes from https://github.com/chatwoot/chatwoot/pull/13040 - Added the support for saving `conversation_required_attributes` in account - Delete `conversation_required_attributes` if custom attribute deleted. Co-authored-by: Vinay Keerthi <11478411+stonecharioteer@users.noreply.github.com> --- app/controllers/api/v1/accounts_controller.rb | 3 +- app/models/account.rb | 8 ++- app/models/custom_attribute_definition.rb | 8 +++ .../custom_attribute_definition_spec.rb | 49 +++++++++++++++++++ 4 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 spec/models/custom_attribute_definition_spec.rb diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb index 773126755..57062a5b2 100644 --- a/app/controllers/api/v1/accounts_controller.rb +++ b/app/controllers/api/v1/accounts_controller.rb @@ -92,7 +92,8 @@ class Api::V1::AccountsController < Api::BaseController end def settings_params - params.permit(:auto_resolve_after, :auto_resolve_message, :auto_resolve_ignore_waiting, :audio_transcriptions, :auto_resolve_label) + params.permit(:auto_resolve_after, :auto_resolve_message, :auto_resolve_ignore_waiting, :audio_transcriptions, :auto_resolve_label, + conversation_required_attributes: []) end def check_signup_enabled diff --git a/app/models/account.rb b/app/models/account.rb index 06767939d..bcfebf39a 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -37,7 +37,11 @@ class Account < ApplicationRecord 'auto_resolve_message': { 'type': %w[string null] }, 'auto_resolve_ignore_waiting': { 'type': %w[boolean null] }, 'audio_transcriptions': { 'type': %w[boolean null] }, - 'auto_resolve_label': { 'type': %w[string null] } + 'auto_resolve_label': { 'type': %w[string null] }, + 'conversation_required_attributes': { + 'type': %w[array null], + 'items': { 'type': 'string' } + } }, 'required': [], 'additionalProperties': true @@ -55,7 +59,7 @@ class Account < ApplicationRecord attribute_resolver: ->(record) { record.settings } store_accessor :settings, :auto_resolve_after, :auto_resolve_message, :auto_resolve_ignore_waiting - store_accessor :settings, :audio_transcriptions, :auto_resolve_label + store_accessor :settings, :audio_transcriptions, :auto_resolve_label, :conversation_required_attributes has_many :account_users, dependent: :destroy_async has_many :agent_bot_inboxes, dependent: :destroy_async diff --git a/app/models/custom_attribute_definition.rb b/app/models/custom_attribute_definition.rb index 09d415401..4154da3b8 100644 --- a/app/models/custom_attribute_definition.rb +++ b/app/models/custom_attribute_definition.rb @@ -45,6 +45,7 @@ class CustomAttributeDefinition < ApplicationRecord belongs_to :account after_update :update_widget_pre_chat_custom_fields after_destroy :sync_widget_pre_chat_custom_fields + after_destroy :cleanup_conversation_required_attributes private @@ -56,6 +57,13 @@ class CustomAttributeDefinition < ApplicationRecord ::Inboxes::UpdateWidgetPreChatCustomFieldsJob.perform_later(account, self) end + def cleanup_conversation_required_attributes + return unless conversation_attribute? && account.conversation_required_attributes&.include?(attribute_key) + + account.conversation_required_attributes = account.conversation_required_attributes - [attribute_key] + account.save! + end + def attribute_must_not_conflict model_keys = attribute_model.to_sym == :conversation_attribute ? :conversation : :contact return unless attribute_key.in?(STANDARD_ATTRIBUTES[model_keys]) diff --git a/spec/models/custom_attribute_definition_spec.rb b/spec/models/custom_attribute_definition_spec.rb new file mode 100644 index 000000000..1359c2967 --- /dev/null +++ b/spec/models/custom_attribute_definition_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe CustomAttributeDefinition do + describe 'callbacks' do + describe '#cleanup_conversation_required_attributes' do + let(:account) { create(:account) } + let(:attribute_key) { 'test_attribute' } + let!(:custom_attribute) do + create(:custom_attribute_definition, + account: account, + attribute_key: attribute_key, + attribute_model: 'conversation_attribute') + end + + context 'when conversation attribute is in required attributes list' do + before do + account.update!(conversation_required_attributes: [attribute_key, 'other_attribute']) + end + + it 'removes the attribute from conversation_required_attributes when destroyed' do + expect { custom_attribute.destroy! } + .to change { account.reload.conversation_required_attributes } + .from([attribute_key, 'other_attribute']) + .to(['other_attribute']) + end + end + + context 'when attribute is contact_attribute' do + let!(:contact_attribute) do + create(:custom_attribute_definition, + account: account, + attribute_key: attribute_key, + attribute_model: 'contact_attribute') + end + + before do + account.update!(conversation_required_attributes: [attribute_key]) + end + + it 'does not modify conversation_required_attributes when destroyed' do + expect { contact_attribute.destroy! } + .not_to(change { account.reload.conversation_required_attributes }) + end + end + end + end +end