# Legacy Import Officer Assignment Documentation

## Challenge Overview

During legacy CSV import, each row contains:

- `ID Number` — the borrower's national ID.
- `Officer _id` — the officer's national ID.

The import workflow must:

1. Import or update the legacy user record for the row's `ID Number`.
2. Import or update the associated loan data.
3. After the user exists, assign the officer referenced by `Officer _id`.

If the officer assignment runs before the user is created or committed, the import fails to link the user to the officer.

## Root Cause

The failure was not in the officer lookup itself. The actual issue was that the target user record for `ID Number` was missing from `users.national_id` at assignment time.

That happened because the legacy user import and officer assignment were being handled in the same logical process, but the officer assignment was still attempted before the import pipeline had fully established the target user record. The following corrections were implemented to ensure officer assignment succeeds:

- Header normalization was hardened so CSV column names like `Officer _id` (and variants with extra spaces/underscores) reliably normalize to `officer_id`.
- Officer assignment is deferred until after all rows are processed by collecting pending assignments during row processing and applying them in a single pass once users/loans are created.
- Officer lookup explicitly restricts results to `users.role = 'officer'` when resolving the officer by `national_id`.
- A post-import helper script (`scripts/assign_officers_from_csv.php`) was added to validate and apply assignments independently when needed.

## Correct Mapping

The expected mapping is:

- CSV `ID Number` → target user `users.national_id`
- CSV `Officer _id` → officer user `users.national_id` where `role = 'officer'`
- Assignment target: update `users.assigned_officer_id` on the target user to the officer's `id`

Example from the sample CSV row:

- target user national_id = `32924429`
- officer national_id = `4571647975`
- officer user expected: `John Officer` (`officer1@lendcore.com`, id `17`)

## Main Scripts

### `import_with_allocations.php`

This wrapper script drives the legacy import using `MasterLegacyImportService`.
It supports:

- dry run mode by default
- `--run` or `-r` to perform the import
- `--atomic` or `-a` to wrap the full import in a database transaction

This is the main operational entry point for importing legacy CSV rows into the application.

### `app/Services/MasterLegacyImportService.php`

This service is the central legacy importer.

Key behavior:

- Creates or updates the user record from `ID Number`.
- Creates or updates the loan record for the imported user.
- Collects officer assignment requests while processing rows.
- Performs officer assignment after all rows are imported.
- Maintains atomic import support so the whole import can roll back on failure.

Key implementation notes:

- Header normalization adds tolerant mappings (for example `officer__id` → `officer_id`) to avoid missed columns due to spacing or punctuation.
- The importer stores pending assignments in-memory (`pendingOfficerAssignments`) and runs `assignPendingOfficerAssignments()` after the CSV is fully processed.
- `assignPendingOfficerAssignments()` resolves the officer via `resolveOfficerByNationalId()` which enforces `role = 'officer'` and reports failures to the import `stats` for later review.

### `scripts/assign_officers_from_csv.php`

This helper script was added for post-import validation and assignment.

It supports:

- dry-run mode by default
- `--csv=path/to/file.csv`
- `--apply` to perform updates

It is useful when officer assignment must be validated or applied after the legacy import has completed.

Example usage (dry-run):

```bash
php scripts/dry_run_import_atomic.php
```

Example usage to apply assignments after an import (non-dry):

```bash
php scripts/assign_officers_from_csv.php --csv="path/to/file.csv" --apply
```

### `scripts/delete_specific_user_cleanup.php`

This cleanup script removes non-admin and non-officer users from the DB.
It is relevant because deleting imported users before the officer assignment step will break the import mapping.

## Recommended Atomic Flow

1. Run the user/loan import using `import_with_allocations.php --run --atomic`.
2. Confirm the legacy users now exist in `users.national_id`.
3. If needed, run `scripts/assign_officers_from_csv.php --csv=... --apply` to set `assigned_officer_id`.

## Notes

- The import must create the borrower user before the officer assignment step.
- Officer lookup is based on `users.national_id` and `role = 'officer'`.
- If a CSV user is missing from the database, the assignment cannot succeed until that user is imported.
- Atomic mode is recommended to keep the import and assignment consistent.

## Corrections Verified

- A dry-run of the repaired importer was executed from the repository root using `php scripts/dry_run_import_atomic.php`. Results from a recent run:
	- Rows processed: `363`
	- Imported: `321`
	- Skipped: `42`
	- Officer assignment stats: `315 assigned`, `0 errors`

- The output shows the deferred assignment logic correctly resolved and applied officer assignments in dry-run mode. Several unrelated `CSV payable mismatch` warnings were recorded for review, but they do not block officer assignment.

## Recommendations / Next Steps

1. For production imports use `import_with_allocations.php --run --atomic` to ensure a single-transaction import and assignment.
2. If assignments must be re-applied or validated after import, run `php scripts/assign_officers_from_csv.php --csv=your.csv --apply`.
3. Review the import `stats.errors` output after dry-runs to inspect any `Officer lookup failed` or CSV mismatch entries; add additional logging if desired.
4. Optionally, extend `assignPendingOfficerAssignments()` to write a CSV report of unmatched officer national IDs for operational follow-up.
