Conversation
…n with industry standards
There was a problem hiding this comment.
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 |
| 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(); | ||
| } | ||
| }), |
There was a problem hiding this comment.
The new company_id import column (lines 39-60) lacks test coverage. There should be tests verifying:
- When a valid company_id is provided, it associates the opportunity with that company
- When an invalid company_id is provided, it is ignored
- When a company_id from another team is provided, it is rejected
- When both company_id and company_name are provided, company_id takes precedence (the early return on line 68-71)
| // Take first match | ||
| $company = reset($domainMatches); |
There was a problem hiding this comment.
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:
- Returning the most recently created company (add orderBy to the query)
- Returning the company with the lowest/highest ID for consistency
- Documenting that this behavior is undefined when multiple companies share a domain
| // 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]; |
| */ | ||
| private function enrichRowWithCompanyMatch(array $row, string $teamId): array | ||
| { | ||
| $companyId = (string) ($row['id'] ?? ''); |
There was a problem hiding this comment.
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.
| $companyId = (string) ($row['id'] ?? ''); | |
| $companyId = (string) ($row['company_id'] ?? $row['company_record_id'] ?? ''); |
| 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(); | ||
| } | ||
| }), |
There was a problem hiding this comment.
The new company_id import column (lines 41-62) lacks test coverage. There should be tests verifying:
- When a valid company_id is provided, it associates the person with that company
- When an invalid company_id is provided, it is ignored
- When a company_id from another team is provided, it is rejected
- When both company_id and company_name are provided, company_id takes precedence (the early return on line 71-72)
No description provided.