Skip to content

uid: T-158 title: Company payee blocks resolve from the bank-account registry (kill the 7+ hardcoded copies) status: done area: accounting created: 2026-07-02 updated: 2026-07-09 related: I-030


T-158 β€” One getCompanyPayeeBlock(), zero hardcoded payee blocks

Why (from I-030, owner-requested sweep 2026-07-02)

The full ERL payee block (bank name + clearing code + account number + FPS ID) is hardcoded in 7+ files and has drifted into live wrongness: customer-facing payment requests carry account number 802-756699-831 (wrong β€” the registry + bank statements say 802-757699-831), and receipts carry FPS ID 114418007 (ERL-DSB-S β€” a closed account). The registry (bankAccount/{id}) is correct; the copies are not. Owner's framing (verbatim): "the utilization of bank account identifier (like 'ERL-AWX-HKD') is being undermined."

What to build

  1. getCompanyPayeeBlock(bankAccountId) β€” one resolver (client + server flavors mirroring resolveBankAccountIdentifier(Server)) returning { company, bankDisplay ("Oversea-Chinese Banking Corporation (035)"), accountNumber, fpsId, fpsEmail } from the registry doc. Company name from the subsidiary profile (_metadata/config), not a string literal.
  2. Replace every hardcoded block (from I-030): coaching payment-request DEFAULT_PAYEE; ToolsApp payment-request + receipt defaults; payment-request-preview + payment-receipt-preview defaults/fallbacks; fetchSharedDoc.server.ts shared-doc payee block; coaching receipt fpsReceiverId. Default the receiving account per surface to the active registry account (today: ERL-OCBC-S) β€” configurable, not baked.
  3. Freeze semantics unchanged: already-issued/shared documents keep the values they were issued with (documents snapshot at creation); the resolver feeds new documents and previews only.
  4. Fix the scripts/migrate-coa.ts ERL-DBS-* β†’ ERL-DSB-* seed drift (or retire the script if superseded).
  5. Sweep-verify (T-156-style propagation matrix in this file) that no literal 756699, 115278533, 114418007, or "Overseas Chinese Banking" strings remain outside tests/docs/issued-document fixtures.

Not in scope

The structural ID plumbing consolidation (prefix→DB map ×4, split('-')[0] ×7, provider maps like OCBC acct#→id) — that's T-157 Phase 0, where the routing layer gets centralized as the consolidation's foundation.

Decision log

2026-07-02 β€” opened from I-030

  • βœ… Attestation (Accounting (Diagnostics)): read AGENTS.md; board checked by scope β€” no existing task covers payee-block resolution (T-156 covers display labels; this is payment-instruction data). Escalated from I-030.
  • Source: Accounting (Diagnostics) Β· https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea
  • Proposed by: Accounting (Diagnostics) (from the owner-requested hardcode sweep). Approval to open: owner directed the investigation + simultaneous Issue-1 follow-ups (2026-07-02); the fix itself is unstarted β€” any agent may pick this up, but coordinate with T-157's owner (Accounting (Infrastructure)) since Phase 0 touches neighbouring plumbing.
  • Urgency note: item 2 fixes a live wrong-account-number on customer-facing payment requests β€” worth shipping ahead of the big consolidation.
  • 2026-07-02 hotfix (Diagnostics): the owner confirmed 757699-831 is correct (verbatim in I-030); the five wrong literals were hot-fixed in place ahead of this task. Remaining here: the resolver itself, the receipts' FPS ID decision (a re-rendered receipt of an old DSB-era payment may legitimately show the closed account's FPS 114418007 β€” per-payment provenance, not a blind swap; new receipts should carry the active receiving account), the migrate-coa seed drift, and the final no-literals sweep.

2026-07-03 β€” built (in PR #838; done on merge)

  • βœ… Attestation (Accounting (Diagnostics)): read AGENTS.md. Owner: "Proceed to T-158 first", then "When you're done, please also check if any backfilling of records needed."
  • Source: Accounting (Diagnostics) Β· https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea
  • Resolver: lib/companyPayee.ts (client; fetches via /api/bank-accounts/[id] β€” Admin-SDK-backed, avoiding the client-SDK rules asymmetry β€” + subsidiary profile for the company name) and lib/companyPayee.server.ts (Admin SDK). Contract: never throws; falls back field-by-field to FALLBACK_COMPANY_PAYEE_BLOCK β€” the single hardcoded copy left in the codebase, registry-verified. Customer-facing semantics: uses official bankName, never the T-156 nickname. DEFAULT_RECEIVING_BANK_ACCOUNT_ID = 'ERL-OCBC-S' is the one named interim constant (T-157 P1 owns making it a subsidiary-profile field).
  • Consumers rewired (6): coaching payment-request page (ref-based defaults, registry-refreshed); ToolsApp payment-request + receipt sandboxes; payment-request-preview + payment-receipt-preview tools; the coaching receipt page β€” which now shows per-payment provenance: the FPS ID of the account that actually received the payment (payment.identifier β†’ registry via the extended lookupAccount, which now surfaces fpsId/fpsEmail), falling back to the current default only when the payment records no account.
  • Shared payment-request docs (fetchSharedDoc.server.ts): precedence β€” stored invoice payee fields first (issued documents are frozen history), else live registry resolution. This fixed a pre-existing wrong behavior: the old hardcoded block silently overrode the 15 DSB-era invoices' stored Dah Sing details with OCBC ones (and the wrong digit).
  • Seed drift: scripts/migrate-coa.ts ERL-DBS-* β†’ ERL-DSB-* fixed.
  • Backfill check (owner-requested) β€” verdict: NO backfill needed. Read-only scan of all 113 coaching session invoice maps (nested Students/*/Sessions; legacy top-level + collectionGroup('invoice') both empty): 0 stored docs carry the wrong 756699 number (the typo lived only in code defaults, never persisted). The only stored payee blocks are 15 Γ— the DSB-era block (Dah Sing Bank (040) / 747-018-22400-8 / FPS 114418007) β€” correct frozen history from when ERL received via Dah Sing, now honoured (not overridden) by the shared-doc renderer.
  • Literal sweep: no 756699 / 115278533 / 114418007 / "Overseas Chinese Banking" remain in live code outside the module fallback. Inert residuals, deliberately left: archived Sheets HTML/* exports (frozen documents) and lib/paymentRequest/instructionsScheme.ts template cells (verified inert β€” PaymentInstructions.getDynamicValue overrides every payee cell from live data unconditionally).
  • Verification: tsc --noEmit clean Β· vitest 583/583. Owner-on-deploy: a new coaching payment request and the Tools previews show the OCBC block from the registry; an old DSB-era shared request shows its stored Dah Sing block; a receipt for a DSB-era payment (with identifier) shows FPS 114418007, a new OCBC-era one shows 115278533.
  • Blast radius (payment documents): payee-block resolution on coaching payment requests/receipts, Tools previews, and public shared payment-request docs. No data writes; issued documents unchanged. New dependency: those surfaces now read /api/bank-accounts/[id] + the subsidiary profile at render (session-gated internal pages; the public shared-doc path uses the Admin-SDK server resolver).

2026-07-03 β€” renumbered T-154 β†’ T-158

  • βœ… Attestation (Accounting (Diagnostics)): read AGENTS.md; renumber-on-merge, scope unchanged.
  • Source: Accounting (Diagnostics) Β· https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea
  • Why: T-154 was taken on main by a parallel agent (iOS 26 Safari optimization). This task (payee-block resolver) moved whole-identity β†’ T-158, incl. in-code [T-158] markers across the resolver + consumers.

2026-07-09 β€” CLOSED (shipped, deployed, live) β€” status β†’ done

  • βœ… Attestation (Accounting (Diagnostics)): read AGENTS.md. Board checkup; stale at doing.
  • Source: Accounting (Diagnostics) Β· https://claude.ai/code/session_01G58Y71noihrYCDEDMexmea
  • Merged in PR #838 β†’ main @ c5e1cf75 (2026-07-03) and deployed the same day. The registry-resolved payee blocks are live on every consumer (coaching payment requests, ToolsApp + both previews, receipts with per-payment FPS provenance, shared docs with stored-payee-wins); the wrong-digit hotfix and the closed-account FPS fix are in production. Backfill check closed pre-merge (0 stored docs affected; 15 DSB-era blocks correctly frozen). I-030 β†’ fixed.