T 112
uid: T-112 title: Harden coaching invoice-number reconstruction fallback (point at the post-T-079 invoice map) status: done area: accounting-coaching created: 2026-06-24 updated: 2026-06-24 related: T-079, I-012
Why (owner, 2026-06-24)¶
T-079 moved studentAbbr / sessionOrdinal / sessionDate from the session doc into the per-session
invoice map. Two readers reconstruct the coaching invoice number when it is absent and still read
from sessionData.* β the pre-T-079 location:
lib/accounting/reports.server.ts:1685(AR Aging)lib/accounting/derivedJournals.ts:529(COACHING_ISSUED journal entries)
After the I-012 backfill the field is present on every live session (15/15), so the fallback no
longer fires for current data. But the fallback is still latent-buggy β any future numberless
coaching session would render as MEL-{currentYear}/UNK-001(PR) and silently mis-key downstream
allocation / AR lookups.
Owner, 2026-06-24 (verbatim, referring to the latent follow-up I flagged at the end of I-012): "take it on please"
What changed¶
New helper at lib/accounting/coachingInvoiceNumber.ts β single source of truth for "given a coaching
invoice map (post-T-079 shape) + optional parent session doc, what is the canonical number?" β wraps
generateRequestNumber from lib/paymentRequest/types.ts (the same function the live writer at
app/coaching/invoice/[sessionId]/client.tsx uses), reads studentAbbr / sessionOrdinal /
sessionDate from the invoice map first, falls back to the session doc, then to a degraded 'UNK' /
1 / currentYear default. Robust to Firestore Timestamps (toDate, _seconds, seconds), ISO
strings, plain number years.
Both reconstruction sites replaced with coachingInvoiceNumber(invoiceOrMap, sessionDataOrUndefined)
β one import, one call, no inlined template literals. Same final shape (MEL-YYYY/Abbr-NNN(PR)),
just no longer drawing from a location T-079 emptied.
Verification¶
npx tsc --noEmit -p tsconfig.json β clean for the touched files; the only remaining errors are two
pre-existing lib/wopc.server.ts as WOPCDocument casts unrelated to this change (would have failed
identically before this commit). I confirmed by filtering tsc output for the touched files:
coachingInvoiceNumber | reports.server | derivedJournals.ts β empty.
Reproduction proof β hand-traced the helper against the 15 backfilled sessions: each input shape
(map carries studentAbbr, sessionOrdinal, issuedAt) flows through the invoice-first read path
and returns the canonical MEL-{year}/{abbr}-{ordinal:03d}(PR) byte-for-byte. With invoiceNumber
present (post-backfill), the helper returns it directly β reconstruction path never runs.
No prod behavioural change is observable today (the backfill neutralised the only data that would
have exercised the buggy path); this is a latency-time fix, not a hot-path one. A future numberless
session would now reconstruct correctly instead of emitting UNK.
Decision log¶
2026-06-24 β opened + landed (latent fallback hardened)¶
- β
Attestation (Accounting (Infrastructure)): read
AGENTS.md; scope-scanned β this closes the latent follow-up I flagged at the bottom of I-012 ("Worth a small hardening pass β left separate to keep this fix minimal"). The owner approved with "take it on please". No existing task covers this (the I-012 close-out flagged it but kept the ticket scoped to the backfill). Branch signalmainπ’ Β·nightlyπ΄ β main only; mirrored totaskboard. - Source: Accounting (Infrastructure) Β· https://claude.ai/code/session_015P6KzVYsQCLgEmUjR9bMwM
- Proposed by: the owner ("take it on please"). Approved by: the owner (same prompt).
- What landed:
lib/accounting/coachingInvoiceNumber.tsβ new helper (40 LoC + docstring), one export.lib/accounting/reports.server.tsβ replace inlined fallback at line 1685 + import the helper.lib/accounting/derivedJournals.tsβ replace inlined fallback at line 529 + import the helper.- Blast radius: read-path only; no Firestore writes, no schema change, no deploy needed (no
hot-path behaviour change observable in prod, since I-012's backfill already neutralised the
buggy fallback for current data). A future numberless session would now reconstruct correctly
rather than emitting
UNK. No data files touched. - Not bundled / out of scope: the
lib/accounting/derivedJournals.ts:512legacy subcollection read (Sessions/{id}.collection('invoice')) β T-079 moved that to a map field, but this file still iterates a subcollection. That's a separate consumer-conversion gap (it would just return zero results today, not mis-emit a number), so it's T-079's area, not this fallback-hardening pass. - Update (2026-06-24, same agent): the owner directed "fix T-079" right after this landed.
Audit confirmed the client-side
derivedJournals.ts+reports.tsare a parallel implementation with zero live callers (everything goes through the*Servervariants β architecture comment atreports.server.ts:41-44says this explicitly). Deleted both under T-079's second postscript β the helper introduced here is unaffected because its live consumer isreports.server.ts:1685, not the deleted mirror.
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.
b8eed7d2026-06-24 β docs(t-111): record the board-renders-tickets fix (done) (mentions only)1eb290a2026-06-24 β refactor(T-112): harden coaching invoice-number reconstruction fallback851002f2026-06-24 β chore(T-079): delete dead client-side parallel impls (derivedJournals.ts + reports.ts) (mentions only)585b79b2026-06-24 β Merge main (1eb290a4) + renumber T-112βT-113 (second collision)a84c6e02026-06-25 β docs(T-112): restore coachingInvoiceNumber.ts provenance commenta56f4442026-06-29 β docs: refresh Accounting (Infrastructure) session ledger + T-130 review note (mentions only)