Skip to content

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 signal main 🟒 Β· nightly πŸ”΄ β†’ main only; mirrored to taskboard.
  • 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:512 legacy 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.ts are a parallel implementation with zero live callers (everything goes through the *Server variants β€” architecture comment at reports.server.ts:41-44 says this explicitly). Deleted both under T-079's second postscript β€” the helper introduced here is unaffected because its live consumer is reports.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.

  • b8eed7d 2026-06-24 β€” docs(t-111): record the board-renders-tickets fix (done) (mentions only)
  • 1eb290a 2026-06-24 β€” refactor(T-112): harden coaching invoice-number reconstruction fallback
  • 851002f 2026-06-24 β€” chore(T-079): delete dead client-side parallel impls (derivedJournals.ts + reports.ts) (mentions only)
  • 585b79b 2026-06-24 β€” Merge main (1eb290a4) + renumber T-112β†’T-113 (second collision)
  • a84c6e0 2026-06-25 β€” docs(T-112): restore coachingInvoiceNumber.ts provenance comment
  • a56f444 2026-06-29 β€” docs: refresh Accounting (Infrastructure) session ledger + T-130 review note (mentions only)