How-To: Add a Legacy-Import Mapping Type β
The legacy importer translates messy free-text from the old system into clean references in the new schema. Each "mapping type" is one column being normalized β e.g. doctor names β doctors.id, free-text nationality β ISO codes like phl.
Source of truth: LegacyImportMappingController::TYPE_CONFIG in app/Http/Controllers/Admin/LegacyImportMappingController.php.
Two kinds of mappings β
| Kind | Resolves to | Examples | Backing |
|---|---|---|---|
fk | A row in another table ({model}_id) | doctor, procedure, medicine, bill_item, insurance, surgery_location | An Eloquent model |
value | A canonical string code | nationality (phl), occupation (ACC) | A Support\* class |
Use fk when the canonical value is a record. Use value when the canonical value is a string code listed in a Support\* class (e.g. App\Support\Nationalities::CODES).
Add an fk mapping type β
Example: adding a referring_clinic mapping that resolves to referring_clinics.id.
// app/Http/Controllers/Admin/LegacyImportMappingController.php
private const TYPE_CONFIG = [
// β¦existing typesβ¦
'referring_clinic' => [
'kind' => 'fk',
'label' => 'Referring Clinics',
'model' => \App\Models\ReferringClinic::class,
'display' => 'name', // column used as the label
'affected_table' => 'patient_referrals', // (informational) where the FK lives
'affected_field' => 'referring_clinic_id',
],
];The typeahead options endpoint (legacy-import.mappings.target-options) reads display to render labels and primary key to map to IDs β no extra wiring needed.
Add a value mapping type β
Example: adding civil_status (single-character codes).
- Create the Support class:
// app/Support/CivilStatuses.php
namespace App\Support;
class CivilStatuses
{
public const CODES = ['S', 'M', 'W', 'D', 'A', 'P'];
public const LABELS = [
'S' => 'Single',
'M' => 'Married',
'W' => 'Widowed',
'D' => 'Divorced',
'A' => 'Annulled',
'P' => 'Separated',
];
public static function label(string $code): string
{
return self::LABELS[strtoupper($code)] ?? $code;
}
public static function selectOptions(): array
{
return collect(self::LABELS)
->map(fn ($label, $code) => ['code' => $code, 'label' => $label])
->values()
->all();
}
}- Register the mapping type:
'civil_status' => [
'kind' => 'value',
'label' => 'Civil Status',
'model' => null,
'display' => null,
'affected_table' => null,
'affected_field' => 'civil_status',
],Teach the typeahead endpoint about the new value source. Look for the
valuebranch intargetOptions()β add a case for the new type that returns options from your Support class.Seed initial mappings so common free-text variants are pre-resolved:
// database/seeders/CivilStatusMappingSeeder.php
$mappings = [
'single' => 'S',
'unmarried' => 'S',
'married' => 'M',
'widow' => 'W',
'widower' => 'W',
'divorced' => 'D',
'separated' => 'P',
'annulled' => 'A',
];
foreach ($mappings as $source => $target) {
LegacyImportMapping::updateOrCreate(
['type' => 'civil_status', 'source_value' => $source],
['target_value' => $target, 'resolved' => true, 'auto_created' => false],
);
}- Use the mapping in the importer:
// app/Services/Import/PatientImporter.php
$civilStatus = LegacyImportMapping::resolve('civil_status', $row['civil_status'])
?? null;Add a permission gate β
The data-mappings page has its own permission (data-mappings.access) so non-admin staff can clean up mappings without touching the rest of the admin panel. Make sure your role assignments include it β see Add a Permission.
Test the mapping type β
it('resolves civil status free-text to a canonical code via mapping', function () {
LegacyImportMapping::factory()->create([
'type' => 'civil_status',
'source_value' => 'separated',
'target_value' => 'P',
'resolved' => true,
]);
expect(LegacyImportMapping::resolve('civil_status', 'separated'))->toBe('P');
});Source-byte encoding gotcha β
The legacy MS Access export is Windows-1252, not UTF-8. The importer decodes source bytes so characters like Γ± and Γ© survive into the mapping table.
If your new column might contain accented characters, run the importer end-to-end with a sample CSV that includes them before declaring victory. See commit 714e506 fix(legacy-import): decode Windows-1252 source bytes so accents survive.
Checklist β
- [ ] Type slug is
snake_caseand matches the new column - [ ]
kind,label,model/displaypopulated correctly - [ ] For
valuetypes: Support class withCODES,LABELS,label(),selectOptions() - [ ]
targetOptions()returns options for the new type - [ ] Seeded with common free-text variants
- [ ] Importer calls
LegacyImportMapping::resolve(...)for the column - [ ] Tested with an accented-character sample if applicable
