Skip to content

Refactor/simplify company matching#70

Merged
ManukMinasyan merged 9 commits into3.xfrom
refactor/simplify-company-matching
Jan 1, 2026
Merged

Refactor/simplify company matching#70
ManukMinasyan merged 9 commits into3.xfrom
refactor/simplify-company-matching

Conversation

@ManukMinasyan
Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI review requested due to automatic review settings January 1, 2026 11:52
@ManukMinasyan ManukMinasyan merged commit 37b2c0f into 3.x Jan 1, 2026
9 of 12 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors company matching logic during imports by simplifying the matching strategy and adding ID-based matching as the highest priority. The changes remove name-based matching (which could produce ambiguous results) and introduce a clear priority hierarchy: ID match > Domain match > Create new > None.

Key changes:

  • Added company_id import column support to PeopleImporter and OpportunityImporter for explicit company associations
  • Removed name-based company matching to eliminate ambiguous match scenarios
  • Simplified duplicate detection for Tasks, Opportunities, Companies, and Notes to use ID-only matching
  • Updated preview UI to show ID, domain, and new match types instead of name and ambiguous types

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/Feature/Services/Import/CompanyMatcherTest.php Updated all tests to pass company_id parameter, added tests for ID-based matching priority, removed tests for name-based and ambiguous matching
app-modules/ImportWizard/src/Services/CompanyMatcher.php Refactored match() to accept company_id parameter, removed name-based matching logic, simplified to 4-priority system (ID > Domain > New > None)
app-modules/ImportWizard/src/Data/CompanyMatchResult.php Added isIdMatch() and isNone() methods, deprecated isNameMatch() and isAmbiguous() methods
app-modules/ImportWizard/src/Services/ImportRecordResolver.php Removed byName caching for companies and opportunities, simplified cache structure
app-modules/ImportWizard/src/Services/ImportPreviewService.php Updated to extract company_id from row data for company matching enrichment
app-modules/ImportWizard/src/Filament/Imports/PeopleImporter.php Added company_id import column with team validation, updated company_name column to skip if company_id already set
app-modules/ImportWizard/src/Filament/Imports/OpportunityImporter.php Added company_id import column with team validation, simplified resolveRecord() to ID-only matching
app-modules/ImportWizard/src/Filament/Imports/CompanyImporter.php Removed name-based duplicate detection, simplified to ID and domain-based matching only
app-modules/ImportWizard/src/Filament/Imports/TaskImporter.php Removed title-based duplicate detection, simplified to ID-only matching
app-modules/ImportWizard/src/Filament/Imports/NoteImporter.php Removed title-based duplicate detection, simplified to ID-only matching
app-modules/ImportWizard/src/Livewire/Concerns/HasCsvParsing.php Simplified error message for max rows exceeded
app-modules/ImportWizard/resources/views/livewire/partials/step-preview.blade.php Updated preview UI to show ID and domain match badges, removed name and ambiguous match displays, moved status icons to first column

Comment on lines +39 to +60
ImportColumn::make('company_id')
->label('Company Record ID')
->guess(['company_id', 'company_record_id'])
->rules(['nullable', 'string', 'ulid'])
->example('01HQWX...')
->fillRecordUsing(function (Opportunity $record, ?string $state, OpportunityImporter $importer): void {
if (blank($state) || ! $importer->import->team_id) {
return;
}

$companyId = trim($state);

// Verify company exists in current team
$company = Company::query()
->where('id', $companyId)
->where('team_id', $importer->import->team_id)
->first();

if ($company) {
$record->company_id = $company->getKey();
}
}),
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new company_id import column (lines 39-60) lacks test coverage. There should be tests verifying:

  1. When a valid company_id is provided, it associates the opportunity with that company
  2. When an invalid company_id is provided, it is ignored
  3. When a company_id from another team is provided, it is rejected
  4. When both company_id and company_name are provided, company_id takes precedence (the early return on line 68-71)

Copilot uses AI. Check for mistakes.
Comment on lines +76 to 77
// Take first match
$company = reset($domainMatches);
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When multiple companies share the same domain, the code takes the first match using reset(), but the order is non-deterministic and depends on database query order and company IDs. This could lead to inconsistent behavior across imports.

Consider either:

  1. Returning the most recently created company (add orderBy to the query)
  2. Returning the company with the lowest/highest ID for consistency
  3. Documenting that this behavior is undefined when multiple companies share a domain
Suggested change
// Take first match
$company = reset($domainMatches);
// Use deterministic ordering when multiple companies share a domain.
usort(
$domainMatches,
static fn (Company $a, Company $b): int => $a->id <=> $b->id
);
$company = $domainMatches[0];

Copilot uses AI. Check for mistakes.
*/
private function enrichRowWithCompanyMatch(array $row, string $teamId): array
{
$companyId = (string) ($row['id'] ?? '');
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The company ID is being extracted from the 'id' field instead of the 'company_id' field. Since this is called for People and Opportunity imports, the 'id' field refers to the person/opportunity ID, not the company ID.

The enrichment should extract the company_id from the row, which would be mapped from a 'company_id' or 'company_record_id' CSV column.

Suggested change
$companyId = (string) ($row['id'] ?? '');
$companyId = (string) ($row['company_id'] ?? $row['company_record_id'] ?? '');

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +62
ImportColumn::make('company_id')
->label('Company Record ID')
->guess(['company_id', 'company_record_id'])
->rules(['nullable', 'string', 'ulid'])
->example('01HQWX...')
->fillRecordUsing(function (People $record, ?string $state, PeopleImporter $importer): void {
if (blank($state) || ! $importer->import->team_id) {
return;
}

$companyId = trim($state);

// Verify company exists in current team
$company = Company::query()
->where('id', $companyId)
->where('team_id', $importer->import->team_id)
->first();

if ($company) {
$record->company_id = $company->getKey();
}
}),
Copy link

Copilot AI Jan 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new company_id import column (lines 41-62) lacks test coverage. There should be tests verifying:

  1. When a valid company_id is provided, it associates the person with that company
  2. When an invalid company_id is provided, it is ignored
  3. When a company_id from another team is provided, it is rejected
  4. When both company_id and company_name are provided, company_id takes precedence (the early return on line 71-72)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants