feat: Add company auto-association for contacts (CW-5726 Part 2) (#12711)

## Description

Implements real-time company auto-association for contacts based on
email domains. This is **Part 2** of the company model production
rollout (CW-5726).

**Task:**
- When a contact is created with a business email, automatically create
and associate a company from the email domain
- When a contact is updated with an email for the first time (email was
previously nil), associate with a company
- Preserve existing company associations when email changes to avoid
user confusion
- Skip free email providers and disposable domains

**Dependencies:**
⚠️ Requires PR #12657 (Part 1: Backfill migration) to be merged first

**Linear ticket:**
[CW-5726](https://linear.app/chatwoot/issue/CW-5726/company-model-setting-it-up-on-production)

## Type of change

- [x] New feature (non-breaking change which adds functionality)

## How Has This Been Tested?

- Service specs: Tests business email detection, company creation,
association logic, edge cases (existing companies, free emails, nil
emails)
- Integration specs: Tests full callback flow for contact create/update
scenarios
- All tests passing: 10 examples, 0 failures
- RuboCop: 0 offenses

## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules (PR #12657 pending)

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
Vinay Keerthi
2025-11-03 20:36:13 +05:30
committed by GitHub
parent ef54f07d5b
commit 1fbdd68222
5 changed files with 228 additions and 0 deletions

View File

@@ -2,5 +2,30 @@ module Enterprise::Concerns::Contact
extend ActiveSupport::Concern
included do
belongs_to :company, optional: true
after_commit :associate_company_from_email,
on: [:create, :update],
if: :should_associate_company?
end
private
def should_associate_company?
# Only trigger if:
# 1. Contact has an email
# 2. Contact doesn't have a compan yet
# 3. Email was just set/changed
# 4. Email was previously nil (first time getting email)
email.present? &&
company_id.nil? &&
saved_change_to_email? &&
saved_change_to_email.first.nil?
end
def associate_company_from_email
Contacts::CompanyAssociationService.new.associate_company_from_email(self)
rescue StandardError => e
Rails.logger.error("Failed to associate company for contact #{id}: #{e.message}")
# Don't fail the contact save if the company association fails
end
end