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:
@@ -227,7 +227,23 @@ def main():
|
||||
efectivo_like("stg_seguros", "efectivo", "INSURANCE", ins_cust, "SEGUROS 16_be", "EFECTIVO")
|
||||
|
||||
if sync_mode:
|
||||
c.executemany("INSERT INTO transactions (id,customerId,domain,typeId,transactionDate,period,reference,amount,currency,exchangeRate,checkNumber,message,outstanding,legacySourceDb,legacySourceTable,legacyId) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) ON DUPLICATE KEY UPDATE customerId=VALUES(customerId),domain=VALUES(domain),typeId=VALUES(typeId),transactionDate=VALUES(transactionDate),period=VALUES(period),reference=VALUES(reference),amount=VALUES(amount),currency=VALUES(currency),checkNumber=VALUES(checkNumber),message=VALUES(message),updatedAt=NOW(),voidedAt=NULL", tx)
|
||||
# Transaction types are rebuilt with fresh uuids each run; resolve them
|
||||
# against the rows already in the DB by English name (inserting any that
|
||||
# are new) and remap each tx's typeId onto the persisted id so the FK to
|
||||
# type_transactions holds. exchange_rates isn't referenced by tx, so it
|
||||
# is left untouched in sync.
|
||||
c.execute("SELECT id,nameEn FROM type_transactions")
|
||||
db_types = {(nm or "").upper(): i for i, nm in c.fetchall()}
|
||||
fresh_name = {tid: (en or "").upper() for tid, en, es, active in type_rows}
|
||||
new_types = []
|
||||
for tid, en, es, active in type_rows:
|
||||
if (en or "").upper() not in db_types:
|
||||
db_types[(en or "").upper()] = tid
|
||||
new_types.append((tid, en, es, active))
|
||||
if new_types:
|
||||
c.executemany("INSERT INTO type_transactions (id,nameEn,nameEs,isService) VALUES (%s,%s,%s,%s)", new_types)
|
||||
tx = [(t[0], t[1], t[2], (db_types.get(fresh_name.get(t[3])) if t[3] else None), *t[4:]) for t in tx]
|
||||
c.executemany("INSERT INTO transactions (id,customerId,domain,typeId,transactionDate,period,reference,amount,currency,exchangeRate,checkNumber,message,outstanding,legacySourceDb,legacySourceTable,legacyId) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) ON DUPLICATE KEY UPDATE customerId=VALUES(customerId),domain=VALUES(domain),typeId=VALUES(typeId),transactionDate=VALUES(transactionDate),period=VALUES(period),reference=VALUES(reference),amount=VALUES(amount),currency=VALUES(currency),checkNumber=VALUES(checkNumber),message=VALUES(message),voidedAt=NULL", tx)
|
||||
else:
|
||||
c.execute("SET FOREIGN_KEY_CHECKS=0")
|
||||
for t in ("transactions", "type_transactions", "exchange_rates"):
|
||||
@@ -255,7 +271,7 @@ def main():
|
||||
print(f" -> transactions : {count('transactions')}")
|
||||
print(f" by domain : {dict(by_dom)}")
|
||||
for src, n in by_src:
|
||||
print(f" {src:16} {n}")
|
||||
print(f" {(src or '(manual)'):16} {n}")
|
||||
print(f" -> type_transactions : {count('type_transactions')}")
|
||||
print(f" -> exchange_rates : {count('exchange_rates')}")
|
||||
print(f" orphan transactions (bad customer FK): {orphans}")
|
||||
|
||||
Reference in New Issue
Block a user