Skip to content

Accounting Inspect/Close modal shares ONE live source of truth with the Bank tab

Goal (owner, 2026-06-14)

"Make it so that they share one single source of truth." Reproduced live: the GCP linker matched tx mjaAuF1u9moedF1NTmCB (2025-02-09 HK$0.75 β†’ invoice 202501); the Bank Transactions tab showed it matched, but the Month-end Checklist inspect-period modal still showed it unmatched. Two different client data paths; only one was live.

Root cause

  • Bank tab β†’ React Query (useTransactions), invalidated live by the Firestore onSnapshot in lib/firestore/useAccountingLiveSync.ts β†’ updated. βœ“
  • Inspect/Close modal β†’ its OWN one-shot fetch() into local state (inspectReadiness, periodTxs) in components/accounting/periods/PeriodsTab.tsx. Only refreshed on open or on the 'periods:changed' window event β€” which fires ONLY on periodCloses changes, not on transaction matches. So the modal kept a frozen snapshot. βœ— (stale, not wrong data)

Fix (single source of truth = the shared live React Query layer)

The modal no longer fetches on its own. It now reads the SAME caches everything else does: 1. lib/accounting/hooks/useTransactions.ts β€” added optional { enabled } (backward-compat; only caller BankTransactionsTab passes one arg). 2. lib/accounting/hooks/useReports.ts β€” new usePeriodReadiness() + reportKeys.periodReadiness, keyed UNDER the ['accounting','reports'] prefix so the existing live-sync invalidation (fired by useAccountingLiveSync on every tx / GL change) refreshes readiness automatically. 3. components/accounting/periods/PeriodsTab.tsx β€” deleted the manual loadPeriodTransactions + inspectReadiness/periodTxs/periodTxsLoading/periodTxsError state + the lazy-load effect. Readiness now = usePeriodReadiness({candidateMonth: inspectMonth}); the period's tx list = useTransactions({startDate,endDate,subsidiaryId,limit:0}, {enabled: inspect && !close}). inspectMonth is the only local UI state left. Alert mark-read now patches the shared readiness cache via queryClient.setQueryData(reportKeys.periodReadiness(...)). Also removed 2 pre-existing unused antd imports (Col, Row).

Result: a match landing anywhere (auto-linker, another tab, another device) flips the row in the modal in real time β€” same invalidation, same cache, no second fetch path to drift.

Verification

  • tsc --noEmit clean (exit 0, project-wide). eslint clean on the 3 touched files.
  • NOT yet verified in a live browser β€” the Accounting page is behind NextAuth/Google SSO, so a headless preview can't reach the modal. Now SHIPPED to prod, so verify ON PROD: open Inspect for 2025-02, confirm the GCP rows show matched (and watch one flip live if re-matched).
  • SHIPPED 2026-06-14: commit b39e6adb β†’ nightly β†’ main (merge 3cb2a75d). Production deploying.

Remaining / follow-ups

  • Live-verify the exact repro (open Inspect for 2025-02, confirm the GCP txs show matched).
  • Commit (bundle with the GCP lookback fix in pages/api/gcp-billing/auto-link.ts, still uncommitted).
  • Optional "every corner" sweep: Reports/Transactions/COA/Journals were ALREADY live (React Query); audit Receipts/Reimbursements tabs (different collection records/receipts β€” may be a manual path like this one was). Reuse the same hook pattern if so. Relates to T-037 live initiative.

Log

  • 2026-06-14 created + implemented (owner repro of modal-vs-bank-tab disagreement). Static checks green; live-verify + commit pending.
  • 2026-06-14 dead-code audit of touched files (clean: no commented-out code, no unused symbols, new exports consumed). Committed as 2 commits (13701a7a GCP lookback Β· b39e6adb single-source-of-truth); promoted mainβ†’nightly (b39e6adb) then nightlyβ†’main (merge 3cb2a75d, preserves PR #724/#725). Prod deploying. REMAINING: live-verify on prod β†’ then done.
  • 2026-06-14 REOPENED β€” owner reported mjaAuF1u9moedF1NTmCB STILL unmatched in the modal on BOTH prod & preview after the single-source change. The single-source fix was necessary but NOT the root cause. REAL ROOT CAUSE: linkTransactionToBilling (and addBillingLink, unlinkTransactionFromBilling) write the gl billing allocation but NEVER set transaction.status β€” so a GCP-linked charge keeps status:'unmatched'. Bank tab reads the derived linkedBilling (β†’ shows matched); readiness/modal read stored status (β†’ unmatched). status MUST be written (it's a Firestore-queried field β€” listTransactions .where transaction.status), so it can't be derive-only. Fix (owner approved 1+2+3):
    1. computeTransactionStatus() canonical helper in transactionAdapter.ts (income match → matched/ partial; else accountCode/billing → categorized; else unmatched). Receipts NOT counted (receipt path doesn't set status). 2. persistRecomputedStatus() choke-point; all 3 billing paths now route through it. 3. Backfilled the 5 GCP txs (unmatched→categorized) — ran a one-off script (dry-first, scoped to billing-linked drift) against prod tebs-erl; verified 0 drift across all 176 txns (helper agreed with stored status on 171, fixed the 5). DATA fix is LIVE in prod Firestore now (the 5 read 'categorized' = resolved → modal stops flagging them). CODE fix (prevent recurrence on future links) is tsc+lint clean, UNCOMMITTED. REMAINING: commit + promote (nightly→main) the code fix; live-confirm modal on prod.
  • 2026-06-14 SHIPPED the code fix: commit bea88e3d β†’ nightly β†’ main (merge d723a858, same local-merge flow as 3cb2a75d, preserves history; gh auth still broken so no PR API). Prod deploying. Data fix (5 GCP txs β†’ categorized) already live from the backfill. DONE pending owner's live-confirm of the Inspect modal on prod.

2026-06-30 β€” reconciled to done (board cleanup, verified)

  • βœ… Read AGENTS.md. Verdict: DONE, deep-verified against the repo before flipping. Shipped in two parts, both on main: the single-source-of-truth change (b39e6adb) plus the real root cause β€” billing-link never wrote transaction.status, fixed via computeTransactionStatus/persistRecomputedStatus (bea88e3d) β€” plus a scoped prod backfill of the 5 GCP txs (verified 0 drift across 176). Blast radius: accounting Inspect/Close modal + Bank tab status parity. Only the owner's live-modal confirm was ever pending. Confirmed the shipping commit(s) are on origin/main.
  • Authorized by the owner (2026-06-30, verbatim): "please take a deeper look into the repo and make sure that the tasks are truly done, then mark done for those tasks." This file carried no Source: signature (predates the convention) β†’ unassigned bucket β†’ reconciled after verification, not deferred.
  • Source (edit): EOP Local Assistance (fork) Β· session edb0abc7-6373-449b-8e31-379fd38d391b