Skip to content

Why (owner, 2026-06-19)

Two threads that converged: 1. The Transaction-column filter on the Bank Transactions tab (added in #761) didn't work and wasn't inclusive of GL accounts. 2. Root cause runs deeper: coaching-matched txs are not GL-code-addressable. Project invoices write gl['4000'] (InvoiceAllocations), but coaching writes only the named keys gl.coaching / gl.coachingInvoices (in GL_RESERVED_KEYS), so getAccountCodeFromGL() returns nothing and 0 txs have gl['4001']. Every GL-code-based consumer (filters, aggregation, invoicePaymentStatus.ts, reconciliation.server.ts, derivedJournals.server.ts) has to special-case coaching.

Scope / fix (PR #762)

  • Filter render β€” added filterMode: "tree" so antd renders the nested Direction / GL submenu (was empty).
  • V1 adapter β€” BankTransaction.effectiveGlCodes: string[] = union of GL codes a tx posts to via derived journals: accountCode + 4000 (matchedInvoices) + 4001 (matchedCoaching*). Works for legacy data (derives from the named-key arrays), so no migration needed for reads.
  • Filter list + predicate β€” derived from union(transactions.effectiveGlCodes); predicate is record.effectiveGlCodes.includes(code); "Uncategorized" is effectiveGlCodes.length === 0. Falls back to accountCode when the union list is absent (legacy flat V1 rows + optimistic patches) β€” fix for Codex P2.
  • Write path (the structural fix) β€” buildCoachingGl4001Marker mirrors gl['4000']'s InvoiceAllocations shape (coaching invoices keyed by invoice number; payments keyed payment:<paymentId>). All four coaching match/unmatch paths in lib/accounting/transactions.ts now write / refresh / clear gl['4001'] alongside the named keys. Marker is array-valued so getAccountCodeFromGL's skip rule still keeps accountCode undefined β†’ GL_REVENUE_RESTRICTED (4000/4001 never directly assigned) holds, exactly like project invoices.
  • Backfill β€” scripts/backfill-coaching-gl4001.ts stamps gl['4001'] on existing coaching-matched docs (backup-first, --dry-run, idempotent, halt-on-error; reuses the exported runtime marker fn β€” no logic drift). dotenv ordering fix (Codex P2) lands here too.

Status

Consolidated into a single PR against nightly after the stacked-PR chain (#762–#766) hit merge conflicts from nightly's parallel T-073/T-074 additions. Codex P1/P2 fixes from

762 + #763 reviews folded in. tsc + 114-test accounting suite clean.

Remaining

  • Owner-merge the consolidated PR.
  • Run scripts/backfill-coaching-gl4001.ts (--dry-run first) once merged.
  • Follow-on (β†’ T-080): retire the named-key special-cases in invoicePaymentStatus.ts / reconciliation.server.ts / derivedJournals.server.ts now that gl['4001'] exists. Not blocking.

T-043 (WPP audit β€” flagged the named-key coaching special-cases) Β· T-076 (stale stored invoice paymentStatus β€” sibling GL/integrity cleanup).

Log

  • 2026-06-19 created (mid-work). Filter fix grew into the coaching GL-code-addressability fix. Write path + adapter + filter + backfill shipped to #762; awaiting owner merge + prod backfill run.
  • 2026-06-20 PR #762 marked ready for review. CI failures are Vercel account-level block (not actionable from branch); same block applies across the whole stack. tsc + vitest accounting suites (114) clean.
  • 2026-06-20 Codex P2 on #762: accountCode fallback in GL filter helper + dotenv ordering fix in the backfill script. Both folded into the consolidated PR.
  • 2026-06-20 RENUMBERED T-074 β†’ T-075 after the stacked-PR merge conflict surfaced nightly's parallel T-074 (DNS cutover). Consolidated PR opens against nightly.
  • 2026-06-20 status flipped to done β€” Accounting [Infrastructure Development] (housekeeping; the work itself shipped via PR #767 and the T-080 cleanup chain). Source: Accounting [Infrastructure Development] Β· https://claude.ai/code/session_015P6KzVYsQCLgEmUjR9bMwM (Source line retrofitted 2026-06-21 β€” the convention postdates this entry.)
  • 2026-06-23 read-side projection reversed at owner request β€” see T-093. This PR's "leave tx.accountCode blank on income txs" choice (preserved here, not introduced β€” the underlying skip rule on getAccountCodeFromGL is older) made every UI / Excel-export site that reads tx.accountCode render blank for income txs. Owner did not recall agreeing to that semantic and confirmed it should be reversed. T-093 adds a one-place fallback in the V1 adapter (lib/accounting/transactionAdapter.ts, toV1) so accountCode is populated to "4000" (project-invoice matches) or "4001" (coaching matches) when no direct assignment exists. Kept from this PR: the filter render fix, the coaching gl["4001"] write + backfill (owner specifically asked for those β€” structurally clean). Reversed from this PR: only the read-side projection. Write-side block on direct manual assignment of 4000/4001 (isRevenueRestricted in the assign-account API) stays in place. 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.

  • 1a11702 2026-06-29 β€” docs(tickets): open I-019 β€” project #2026-003 stuck "Pending" (invoice issued={} β†’ fixed) (mentions only)
  • fe5c5ea 2026-06-29 β€” docs(tasks): open T-135 β€” self-describing project/invoice final status (mirror of derived, symmetric on unmatch) (mentions only)
  • 3843eea 2026-06-30 β€” feat(accounting): T-135 self-describing project/invoice status mirror (symmetric on match + unmatch) (mentions only)