A legacy-data migration that treats itself as a copy operation — read the old rows, write new ones — will always look finished before it actually is. Omnitech CRM's migration off a long-running legacy Perfex CRM installation was built around a different premise: migrating legacy data is a reconciliation problem, and the migration's job is to say clearly what it could not resolve, not just what it moved.
01Reading the work
Why a migration script can't just copy rows
A legacy system's identifiers, relationships, and access rules rarely map cleanly onto a new schema built with different assumptions. Copying rows verbatim either forces the new schema to inherit the old system's inconsistencies, or silently drops the records that don't fit — and a silent drop during a CRM migration can mean a lost customer record or a support ticket with no owner. Reconciliation means the migration has to actively match legacy identifiers against the new schema and decide, record by record, whether that match is safe to make automatically.
02Reading the work
Transactional and idempotent, on purpose
Omnitech CRM's migrators are transactional and idempotent: a failure partway through does not leave the target database half-migrated, and the same migration step can be re-run safely without duplicating already-migrated records. This mirrors the same idempotency discipline used in reliable API design more broadly — Laravel's database transaction handling is the concrete mechanism used here to make a multi-step migration step atomic (cited below).
03Reading the work
Reporting what needs a human decision
The migration explicitly reports the records it cannot confidently reconcile, rather than guessing and moving on. That report is the actual deliverable of a reconciliation-first migration: it turns an unknown number of silent data problems into a known, reviewable list. Preserving all eight legacy roles from the outgoing Perfex CRM installation followed the same principle — role definitions were reconciled against the new permission system rather than discarded and rebuilt, so existing staff access rules carried over instead of being silently reset.
04Reading the work
What this generalizes to
A legacy migration is finished when it can say precisely what it migrated, what it could not resolve automatically, and what a human needs to decide — not when the row counts match. That standard applies whether the legacy system is a CRM, an e-commerce platform, or a decade of spreadsheets: reconciliation reporting, not a bigger import script, is what actually prevents quiet data loss.