How to Sequence an ERP Data Migration So the Cutover Doesn't Blow Up

A 120-seat industrial distributor moving off QuickBooks after eight years does not have a "data problem." It has four separate data problems that happen to share a go-live date: master records, open transactions, closed history, and the PDFs nobody thought to inventory until someone asks where three years of signed proof-of-delivery documents went. Treating migration as one monolithic task is the single most common reason cutover weekends run long, and long cutover weekends are where warehouses stop shipping and sales reps stop invoicing.
This is a sequencing problem more than a technical one. Get the order and the checkpoints right and a migration into a mid-market ERP platform, whether that's NetSuite, Acumatica, or a Microsoft Dynamics tier, is a manageable multi-week project with a boring weekend. Get it wrong and the first six weeks live get spent fielding calls about invoices that don't match, inventory counts that don't tie out, and customers billed twice.
The four data domains, and why lumping them together fails
Migration plans that treat "data" as a single line item on a project timeline tend to discover the real scope about three weeks before cutover. Split it up front into four categories, because each one has a different cleansing burden, a different tolerance for error, and a different cutover-day treatment.
- Master data - customers, vendors, items and SKUs, chart of accounts, price lists, tax codes. Smallest dataset by row count, but every other domain depends on it. Get a customer ID wrong here and every open invoice tied to it inherits the error.
- Open transactions - unpaid AR invoices, unpaid AP bills, open purchase orders, open sales orders, on-hand inventory by warehouse and bin. This data has to be dead accurate on day one because it drives cash collection and what the warehouse can promise to ship.
- Closed history - paid invoices, closed POs, prior-year GL detail. Needed for audit trail and customer lookback, but it does not have to load perfectly by go-live morning; many teams load two or three years live and archive the rest to a read-only reporting database.
- Unstructured attachments - signed PODs, vendor W-9s, item spec sheets, customer tax exemption certificates. Almost always underestimated because it lives in a shared drive or as file links attached to transactions that don't export cleanly.
For the distributor scenario running through this piece: 42,000 customer records going back eight years, of which roughly 11,000 have placed an order in the trailing 24 months; 6,400 active SKUs plus another 3,100 discontinued ones still referenced on historical orders; 18,000 open AR invoices and credit memos; and about 9,200 tax exemption certificates scattered across a shared drive with no consistent naming convention. None of that fits under a single "migrate the data" task on a project Gantt chart.
Field-by-field mapping is the part nobody budgets for
The mapping document is where migrations actually die or survive, and it's tedious enough that teams routinely underscope it. Every target field needs a documented source, a transform rule, and a decision for what happens when the source is blank or malformed. A partial example from a customer master mapping:
| Source field (QuickBooks) | Target field | Transform rule |
|---|---|---|
| Customer:Job (concatenated) | Customer ID / Ship-To | Split on colon; parent becomes Customer, job becomes a linked Ship-To |
| Terms (free text, 340 distinct values) | Payment Terms Code | Map to 14 standard codes; anything unmatched flags to a manual review queue |
| Sales Tax Code | Tax Schedule | Cross-reference against current nexus states; codes for states with no current nexus remap to "exempt - review" |
| Credit Limit (blank on 61% of records) | Credit Limit | Blank defaults to $0 (credit hold), not unlimited - forces AR to actively set real limits rather than inherit open exposure |
That last row is the kind of decision that gets made badly under time pressure if it isn't made explicitly, weeks earlier, in the mapping document. Defaulting a blank credit limit to "unlimited" because it's the path of least resistance is how a distributor discovers, three weeks after go-live, that a customer with a history of slow payment just got approved for a $40,000 order.
Budget mapping time by field count, not by table count. A customer master with 60 target fields, even if only 25 are populated from the legacy system, takes longer to map correctly than a two-table integration would suggest. For the 120-seat distributor, the full mapping document across customers, vendors, items, and open transactions ran to just under 400 rows and took three people about five weeks, working part-time alongside their regular jobs.
Cleansing rules that catch the real garbage
Generic advice to clean your data before migrating is useless without rules specific enough to run as queries. The rules that actually catch problems in a distributor's legacy system tend to look like this:
- Duplicate customers from case and punctuation variants. "ABC Mfg", "ABC Mfg.", and "A.B.C. Manufacturing" as three separate customer records, each with its own order history and credit limit. A fuzzy match on normalized name plus billing zip surfaces most of these; the distributor's data had 1,140 duplicate pairs, about 2.7% of the customer file.
- Orphaned line items. Order lines referencing a parent order that was deleted rather than voided in the legacy system. These don't throw an export error; depending on the migration tool they either get silently dropped or silently attached to the wrong order.
- Negative or impossible inventory quantities. On-hand counts that went negative because the legacy system let receiving post before a shipment reversed out, or because of manual adjustments nobody documented. These need resolution against a physical count, not a zero-out, or the new system launches with inventory it doesn't actually have.
- Obsolete SKUs still referenced on open orders. Items discontinued two years ago that somehow still carry three open sales order lines, usually because a rep manually keyed an old part number. Each one needs a decision: remap to the replacement SKU, or flag for manual resolution before cutover, not during it.
- Invalid or expired tax exemption certificates. Certificates on file that expired 18 months ago but are still marked active - an audit exposure the moment the new system starts calculating tax off that flag.
Run these as scheduled queries against the legacy system starting eight to ten weeks before cutover, not as a one-time cleanup pass. New garbage accumulates daily right up until the system is frozen, and a cleanup done six weeks out is stale by go-live.
Run the migration twice before it counts
A single migration attempt followed by go-live is a bet nobody should take. The distributor ran three dry-run loads into a staging instance of the new ERP, roughly two weeks apart, each followed by a formal reconciliation against control totals pulled from the legacy system:
- AR aging total by bucket - current, 30, 60, 90+ - matched to the penny against the legacy AR aging report
- Open AP total matched the same way
- On-hand inventory value by warehouse, reconciled against the legacy inventory valuation report
- GL trial balance for the prior closed period, matched to zero variance
- Record counts by table - customers, items, open orders - matched exactly, with any variance investigated line by line rather than assumed to be duplicates
The first dry run at the distributor came back with a $340,000 discrepancy in open AR, traced to invoices with split payment terms that the transform logic wasn't handling - it was applying the full invoice amount to the current bucket instead of splitting it across the agreed installment schedule. That's a bug that's cheap to fix with three weeks of runway and expensive to discover on a Monday morning when AR can't tell a customer their actual balance. The second dry run turned up a smaller issue: 214 inventory records with an inverted unit-of-measure conversion (case to each), overstating on-hand value by roughly $28,000. The third dry run reconciled clean.
Each dry run should also test the reverse direction: can a transaction created in the new system on day one correctly reference a customer or item that only exists because of the migration? It's a different failure mode than the migration itself and worth catching before go-live rather than from a support ticket on day two.
Sequencing the cutover weekend
By the time cutover weekend arrives, if the mapping is solid and three dry runs have reconciled clean, the weekend itself should be procedural, not exploratory. A sequence that works at this scale:
- Friday, 6:00 PM - legacy system goes read-only. No new orders, no new receipts, no new payments entered anywhere. This freeze needs to be communicated to every department, not just IT, at least two weeks in advance.
- Friday 6:30 PM to Saturday 8:00 AM - final extract of all four data domains from the now-frozen legacy system, loaded into the new ERP production environment.
- Saturday 8:00 AM to 2:00 PM - reconciliation against the same control totals used in the dry runs. Not optional even though it feels redundant after three clean dry runs - this is the first extract pulling from a truly frozen system, and the last checkpoint before real customers touch the data.
- Saturday 2:00 PM to 6:00 PM - functional validation: a small group runs real transaction types end to end, creating a sales order, receiving a PO, posting a payment, running a pick ticket, and confirms the results match known-good legacy examples.
- Saturday, 6:00 PM - go/no-go decision, made against criteria agreed weeks earlier, not invented in the moment.
- Sunday - buffer day. If go/no-go passed Saturday evening, Sunday covers final user access setup, printer and label configuration in the warehouse, and a dry run of Monday morning's first shipment.
- Monday, 6:00 AM - warehouse and customer service open in the new system. IT and a rotating group of super-users stay reachable through the end of day for the first three business days.
Every hour the cutover window extends past the planned Saturday reconciliation is an hour the warehouse can't ship and AR can't collect. For a distributor doing $38,000 in average daily shipments, a slip from a planned 60-hour cutover window to a 96-hour one is worth pricing out with something like a downtime cost calculator before deciding how much cutover-weekend staffing and buffer time is worth budgeting for.
The rollback trigger you decide before, not during
Every cutover plan needs a rollback threshold agreed in writing before the weekend starts, because the people in the room Saturday evening are the wrong people to be inventing criteria under pressure. Reasonable triggers: reconciliation variance above a fixed dollar threshold (the distributor set this at $5,000 uninvestigated variance in any single control total), more than a small percentage of test transactions failing functional validation, or a critical integration - EDI to a major customer, the payment gateway - not connecting in the new environment.
Rollback itself is simple to describe and painful to execute: the legacy system comes back online as the system of record, the new ERP go-live is postponed, and the root cause gets fixed and re-tested in the next dry-run cycle rather than patched live. Having an actual rollback plan, not just a rollback intention, means keeping the legacy system fully intact and untouched through at least the first two weeks post-cutover, resisting the urge to decommission it the moment go-live looks stable. The distributor kept QuickBooks in a read-only state for 45 days after cutover before archiving it, mostly as insurance against a data gap nobody caught during reconciliation.
None of this replaces judgment on the day. But a migration sequenced this way turns cutover weekend into execution of a checklist instead of a live debugging session with the warehouse waiting on the other end of the phone.