Skip to content

Backfill invoice detail.payment.tx[] reverse trail from existing bank-tx matches

Why (owner, 2026-06-23)

T-078 (PR #763) added detail.payment.tx[] on invoice docs β€” the reverse audit pointer that lets an invoice show which bank tx paid it, even read in isolation. It's the companion to the canonical gl['4000'] / gl['4001'] allocation on the bank-tx side. The runtime match/unmatch paths maintain it going forward, but every match written BEFORE T-078 shipped never got the reverse pointer β€” so those invoices had an empty detail.payment.tx. Surfaced during the tx 9zVOyZ6AggDJaSpKqD25 diagnosis (its two invoices ERL-2025-010-0423 / ERL-2025-019-0624 both read empty). Owner asked to backfill the trail across all pre-T-078 matches.

What shipped

  • scripts/backfill-invoice-payment-tx-trail.ts β€” walks every bank tx (tebs-erl accounting/transactions/entries), decodes each via the canonical V1 adapter (readTransactionAsV1 β€” no logic drift vs runtime), and for each matched invoice / coaching invoice stamps the txId into the invoice's trail via FieldValue.arrayUnion: Β· Project invoices (tebs-erl): projects/{year}/projects/{projectId}/invoice/{invoiceNumber} β†’ detail.payment.tx Β· Coaching invoices (tebs-mel): Students/{abbr}/Sessions/{sessionId} β†’ invoice.detail.payment.tx Paths/fields mirror the runtime helpers (syncProjectInvoiceTxTrail / syncCoachingInvoiceTxTrail). Backup-first, idempotent, halt-on-error, --dry-run, --skip-coaching.
  • Run on prod 2026-06-23: 46 trail additions β€” 31 project + 15 coaching, 0 invoices not found, 0 sessions not found. Re-run dry-run confirms idempotency (0 remaining). Backups under scripts/migration-backups/ (gitignored).

Scope / safety

Additive audit data only β€” does NOT touch gl allocations, status, amounts, or display. The bank-tx side stays canonical; this only restores the reverse pointer. arrayUnion + pre-read dedup β†’ safe to re-run.

Verification

  • Dry-run before apply: 46 planned, all docs/sessions resolved.
  • Apply: 46 written.
  • Re-run dry-run: 0 needed (idempotent).
  • tx 9zVOyZ6AggDJaSpKqD25 now appears in both ERL-2025-010-0423 and ERL-2025-019-0624 trails.

Log

  • 2026-06-23 created + shipped. Script written to repo convention (getAdminFirestore via .env.local in prod); run here by mapping the session's GOOGLE_ creds β†’ FIREBASE_ADMIN_. Backfill applied to prod. Source: Accounting (Diagnostics) Β· https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea

Commit index (backfilled 2026-07-01, best-effort Β· Coaching (Diagnostic))

Candidate related commits, auto-backfilled from git on main: commits whose message references this task's UID or a PR number it cites. Not verified β€” this squash-merged history can't yield a precise per-task list, so rows tagged (mentions only) name the task in passing (may be tangential) and untagged work commits may be missing. Treat as a starting point: verify, prune tangential rows, and append any real ones per the AGENTS.md "record every related SHA" policy.

  • (no git-discoverable commit references this UID or its PRs β€” append real SHAs here as identified.)