fix(migration): make Phase B additive sync actually work + verify end-to-end
The --sync path had never been run and was broken in several ways. Fixed and verified against the dev DB (two consecutive syncs, both exit 0, 32/32 assertions: stable PKs, manual-row preservation, changed-row updates, legacy-delete, no child duplication, zero FK orphans; idempotent). - policies/properties: reuse each legacy row's existing id (by provenance) BEFORE building child rows, so children no longer point at a discarded fresh uuid; rebuild legacy-owned children via scoped delete + reinsert. - customers: replace zip(customers, refs) (mispaired almost every row) with a ref-grouped id remap; names now restore and no spurious customers appear. - drop the invalid Vehicle @@unique(legacySourceTable, legacyId) — one legacy policy row carries up to 3 vehicles sharing a legacyId; handle via delete+reinsert. - upsert lookup tables (policy_types, insurance_providers, type_transactions, adjusters) by natural name and remap child FKs instead of inserting fresh uuids that nothing points at. - transactions: drop updatedAt=NOW() (no such column); guard report formatting on NULL legacySourceTable (manual rows). Same report guard in bank. - add manual-safe prune (prune_empty_customers.py --sync, in SYNC_STEPS): prune only legacy-owned empties, never manually-added customers. web: customer-detail mini tx list now strikes voided rows with an "(anulado)" tag (was the last void-UI rendering gap; /estado-cuenta already handled it). docs: RESUME.md updated — Phase B sync marked verified end-to-end, void-UI browser pass recorded. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -50,11 +50,21 @@ def main() -> None:
|
||||
ap.add_argument("--env", default="dev", help="target environment (reads deploy/.env.<env>)")
|
||||
ap.add_argument("--dry-run", action="store_true",
|
||||
help="report and write the audit CSV, but delete nothing")
|
||||
ap.add_argument("--sync", action="store_true",
|
||||
help="only prune legacy-owned empties; never touch manually-added "
|
||||
"customers (those with no customer_legacy_refs row)")
|
||||
args = ap.parse_args()
|
||||
|
||||
conn = connect(args.env)
|
||||
cur = conn.cursor()
|
||||
print(f"[prune] target env: {args.env}")
|
||||
print(f"[prune] target env: {args.env}{' (sync: legacy-owned only)' if args.sync else ''}")
|
||||
|
||||
# In sync mode a manually-added customer (no legacy ref) with no records yet
|
||||
# is a legitimate new row, not Access-era dead weight — so restrict the prune
|
||||
# to customers that carry a legacy ref.
|
||||
where = EMPTY_WHERE + (
|
||||
"\nAND EXISTS (SELECT 1 FROM customer_legacy_refs lr WHERE lr.customerId = c.id)"
|
||||
if args.sync else "")
|
||||
|
||||
cur.execute("SELECT COUNT(*) FROM customers")
|
||||
before = cur.fetchone()[0]
|
||||
@@ -67,7 +77,7 @@ def main() -> None:
|
||||
ORDER BY r.sourceSystem SEPARATOR ' | ')
|
||||
FROM customers c
|
||||
LEFT JOIN customer_legacy_refs r ON r.customerId = c.id
|
||||
WHERE {EMPTY_WHERE}
|
||||
WHERE {where}
|
||||
GROUP BY c.id
|
||||
ORDER BY c.nameMissing, c.name
|
||||
""")
|
||||
@@ -86,11 +96,18 @@ def main() -> None:
|
||||
if args.dry_run:
|
||||
print(f" dry run — {len(rows)} would be pruned, nothing deleted")
|
||||
else:
|
||||
cur.execute(f"DELETE r FROM customer_legacy_refs r JOIN customers c ON c.id = r.customerId "
|
||||
f"WHERE {EMPTY_WHERE}")
|
||||
refs_deleted = cur.rowcount
|
||||
cur.execute(f"DELETE c FROM customers c WHERE {EMPTY_WHERE}")
|
||||
deleted = cur.rowcount
|
||||
# Delete by the exact id set selected above (which already carries the
|
||||
# sync guard). Deleting via the id list avoids referencing the delete
|
||||
# target table inside its own WHERE (MySQL error 1093) and keeps refs +
|
||||
# customers on the same set regardless of delete order.
|
||||
ids = [r[0] for r in rows]
|
||||
refs_deleted = deleted = 0
|
||||
if ids:
|
||||
fmt = ",".join(["%s"] * len(ids))
|
||||
cur.execute(f"DELETE FROM customer_legacy_refs WHERE customerId IN ({fmt})", ids)
|
||||
refs_deleted = cur.rowcount
|
||||
cur.execute(f"DELETE FROM customers WHERE id IN ({fmt})", ids)
|
||||
deleted = cur.rowcount
|
||||
conn.commit()
|
||||
print(f" deleted {deleted} customers, {refs_deleted} legacy refs")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user