+ {{ t('COMPANIES.DETAIL.ATTRIBUTES.NO_ATTRIBUTES') }} +
++ {{ t('COMPANIES.DETAIL.ATTRIBUTES.EMPTY_STATE') }} +
+ diff --git a/app/javascript/dashboard/components-next/Companies/CompanyDetail/CompanyProfileCard.vue b/app/javascript/dashboard/components-next/Companies/CompanyDetail/CompanyProfileCard.vue new file mode 100644 index 000000000..1ffbf0c18 --- /dev/null +++ b/app/javascript/dashboard/components-next/Companies/CompanyDetail/CompanyProfileCard.vue @@ -0,0 +1,203 @@ + + + ++ {{ t('COMPANIES.DETAIL.AVATAR.UPDATING') }} +
+{{ $t('BULK_ACTION.AGENT_LIST_LOADING') }}
-- {{ - $t('BULK_ACTION.ASSIGN_CONFIRMATION_LABEL', { - conversationCount, - conversationLabel, - }) - }} - - {{ selectedAgent.name }} - - ? -
-- {{ - $t('BULK_ACTION.UNASSIGN_CONFIRMATION_LABEL', { - conversationCount, - conversationLabel, - }) - }} -
-+ {{ t('COMPANIES.DETAIL.EMPTY_STATE.SUBTITLE') }} +
+This is an HTML message with an inline image.
+
+
+
+
+------=_NextPart_001_0002--
+
+------=_NextPart_000_0001
+Content-Type: image/jpeg;
+ filename="image001.jpg"
+Content-Transfer-Encoding: base64
+Content-ID:
')
+
+ expect(helper_instance.send(:body_references_cid?, 'image001.jpg@test')).to be true
+ end
+ end
+
+ describe '#upload_inline_image' do
+ let(:mail_attachment) do
+ {
+ original: OpenStruct.new(cid: 'image001.jpg@test'),
+ blob: get_blob_for('spec/assets/avatar.png', 'image/png')
+ }
+ end
+ let(:helper_instance) { mailbox_helper_obj.new(conversation, processed_mail) }
+
+ it 'replaces percent-encoded CID references in HTML content' do
+ allow(Rails.application.routes.url_helpers).to receive(:url_for).and_return('/fake-image-url')
+ helper_instance.instance_variable_set(:@html_content, '
')
+
+ helper_instance.send(:upload_inline_image, mail_attachment)
+
+ html_content = helper_instance.instance_variable_get(:@html_content)
+ expect(html_content).to include('/fake-image-url"')
+ expect(html_content).not_to include('cid:')
+ end
+ end
+
+ describe '#add_attachments_to_message' do
+ let(:mail) { create_inbound_email_from_fixture('cid_inline_images_without_disposition.eml').mail }
+ let(:processed_mail) { MailPresenter.new(mail) }
+ let(:conversation) { create(:conversation) }
+ let(:helper_instance) { mailbox_helper_obj.new(conversation, processed_mail) }
+
+ before do
+ helper_instance.send(:create_message)
+ end
+
+ it 'detects inline image attachment by cid reference when Content-Disposition is missing' do
+ allow(Rails.application.routes.url_helpers).to receive(:url_for).and_return('/fake-image-url')
+ helper_instance.send(:add_attachments_to_message)
+
+ message = conversation.messages[0]
+
+ expect(message.attachments.count).to eq(0)
+
+ html_content = message.content_attributes[:email][:html_content][:full]
+
+ expect(html_content).to include('/fake-image-url"')
+ expect(html_content).not_to include('cid:')
+ end
+ end
end
diff --git a/spec/models/custom_attribute_definition_spec.rb b/spec/models/custom_attribute_definition_spec.rb
index 648296b69..c529fc609 100644
--- a/spec/models/custom_attribute_definition_spec.rb
+++ b/spec/models/custom_attribute_definition_spec.rb
@@ -42,6 +42,17 @@ RSpec.describe CustomAttributeDefinition do
cad = build(:custom_attribute_definition, account: account, attribute_key: 'key()')
expect(cad).not_to be_valid
end
+
+ it 'allows company custom attributes' do
+ cad = build(:custom_attribute_definition, account: account, attribute_model: 'company_attribute')
+ expect(cad).to be_valid
+ end
+
+ it 'rejects company custom attributes that conflict with standard company fields' do
+ cad = build(:custom_attribute_definition, account: account, attribute_model: 'company_attribute', attribute_key: 'domain')
+ expect(cad).not_to be_valid
+ expect(cad.errors[:attribute_key]).to be_present
+ end
end
end
diff --git a/spec/services/labels/destroy_service_spec.rb b/spec/services/labels/destroy_service_spec.rb
new file mode 100644
index 000000000..7d06b72d0
--- /dev/null
+++ b/spec/services/labels/destroy_service_spec.rb
@@ -0,0 +1,102 @@
+require 'rails_helper'
+
+describe Labels::DestroyService do
+ let(:account) { create(:account) }
+ let(:conversation) { create(:conversation, account: account) }
+ let(:label) { create(:label, account: account) }
+ let(:contact) { conversation.contact }
+ let(:label_deleted_at) { Time.zone.parse('2026-05-07 10:00:00 UTC') }
+
+ before do
+ conversation.label_list.add(label.title)
+ conversation.label_list.add('billing')
+ conversation.save!
+
+ contact.label_list.add(label.title)
+ contact.label_list.add('vip')
+ contact.save!
+
+ set_label_tagging_created_at(conversation, label_deleted_at - 1.minute)
+ set_label_tagging_created_at(contact, label_deleted_at - 1.minute)
+ end
+
+ describe '#perform' do
+ it 'removes label from associated conversations and contacts' do
+ described_class.new(
+ label_title: label.title,
+ account_id: account.id,
+ label_deleted_at: label_deleted_at
+ ).perform
+
+ expect(conversation.reload.label_list).to eq(['billing'])
+ expect(conversation.cached_label_list).to eq('billing')
+ expect(contact.reload.label_list).to eq(['vip'])
+ end
+
+ it 'removes label associations after the label record is destroyed' do
+ label_title = label.title
+ label.destroy!
+
+ described_class.new(
+ label_title: label_title,
+ account_id: account.id,
+ label_deleted_at: label_deleted_at
+ ).perform
+
+ expect(conversation.reload.label_list).to eq(['billing'])
+ expect(conversation.cached_label_list).to eq('billing')
+ expect(contact.reload.label_list).to eq(['vip'])
+ end
+
+ it 'does not remove labels from other accounts' do
+ other_account = create(:account)
+ other_conversation = create(:conversation, account: other_account)
+ other_conversation.label_list.add(label.title)
+ other_conversation.save!
+ set_label_tagging_created_at(other_conversation, label_deleted_at - 1.minute)
+
+ described_class.new(
+ label_title: label.title,
+ account_id: account.id,
+ label_deleted_at: label_deleted_at
+ ).perform
+
+ expect(other_conversation.reload.label_list).to eq([label.title])
+ end
+
+ it 'does not dispatch conversation or contact update events' do
+ expect(Rails.configuration.dispatcher).not_to receive(:dispatch)
+
+ described_class.new(
+ label_title: label.title,
+ account_id: account.id,
+ label_deleted_at: label_deleted_at
+ ).perform
+ end
+
+ it 'does not remove label associations created after the label was deleted' do
+ other_conversation = create(:conversation, account: account)
+ other_conversation.label_list.add(label.title)
+ other_conversation.save!
+ set_label_tagging_created_at(other_conversation, label_deleted_at + 1.minute)
+
+ described_class.new(
+ label_title: label.title,
+ account_id: account.id,
+ label_deleted_at: label_deleted_at
+ ).perform
+
+ expect(conversation.reload.label_list).to eq(['billing'])
+ expect(conversation.cached_label_list).to eq('billing')
+ expect(contact.reload.label_list).to eq(['vip'])
+ expect(other_conversation.reload.label_list).to eq([label.title])
+ end
+ end
+
+ def set_label_tagging_created_at(record, created_at)
+ ActsAsTaggableOn::Tagging
+ .joins(:tag)
+ .find_by!(context: 'labels', taggable: record, tags: { name: label.title })
+ .update!(created_at: created_at)
+ end
+end