Skip to content

WPP — render every statement as XLSX with shared Bank-Tx letterhead

Why (owner, 2026-06-25)

"4) WPP should render xlsx instead of csv, and 5) Put the Bank tx xlsx header to all the WPP xlsx as well"

The Working Paper Pack endpoint (/api/accounting/working-paper-pack) shipped 11 CSV files inside a zip (one per primary statement + supporting schedule). The auditor opens the zip in Excel / Numbers — the moment they do, the CSVs are de-facto XLSX-displayed anyway. The user wants the WPP to ship native XLSX with the same Bank-Tx-style letterhead band (logo + subsidiary identity + statement title) every other owner-facing export now carries (T-124 sibling).

What shipped

lib/accounting/wppXlsx.ts (new)

One module mirroring wppCsv.ts's 11 *ToCsv builders with parallel *ToXlsx builders. Each takes the same generator output the matching CSV builder takes plus a LetterheadSubsidiary | null and returns a Buffer ready to drop into the zip.

Internally, every *ToXlsx follows the same pattern:

csv ← <existing>ToCsv(arg)
rows ← csvToRows(csv)              # CSV-quote-aware split + per-line parse
                                   # (CRLF + escaped "" handled; ready for
                                   # any future multi-line cell content)
ws.addXlsxLetterhead({…})
ws.writeRows(rows, starting at headerRowIdx)
return wb.xlsx.writeBuffer()

Numeric-looking cells are promoted back to real numbers so the auditor can SUM / pivot them in Excel; everything else stays text. Account codes (12010-100 etc.) fail the strict numeric regex so they retain their leading characters.

This keeps wppCsv.ts as the single source of truth for content + ordering — verification path (re-render → text-diff) is unchanged.

pages/api/accounting/working-paper-pack.ts

  • Imports swap wppCsvwppXlsx. The 11 CSV emit lines under csv/*.csv become xlsx/*.xlsx emit lines, generated in parallel via Promise.all (each XLSX build is independent).
  • fetchSubsidiaryInfo(subsidiaryId) is called once for the letterhead identity block; .catch(() => null) falls back to a logo-only letterhead if the metadata fetch fails — pack still ships.
  • README cover sheet + manifest.json entries updated: file names, format strings, and the explanatory text now describe XLSX.

File names

Old New
csv/trial-balance.csv xlsx/trial-balance.xlsx
csv/profit-and-loss.csv xlsx/profit-and-loss.xlsx
csv/balance-sheet.csv xlsx/balance-sheet.xlsx
csv/cash-flow.csv xlsx/cash-flow.xlsx
csv/socie.csv xlsx/socie.xlsx
csv/ar-aging.csv xlsx/ar-aging.xlsx
csv/related-party.csv xlsx/related-party.xlsx
csv/ir56m-candidates.csv xlsx/ir56m-candidates.xlsx
csv/notes.csv xlsx/notes.xlsx
csv/fixed-assets.csv xlsx/fixed-assets.xlsx
csv/journals.csv xlsx/journals.xlsx

Why this and not "ship both formats"

Two reasons:

  1. The CSV builders remain importable from wppCsv.ts — anything needing raw CSV (a downstream pipeline, a future PDF generator, future as-filed snapshot writer) still calls them directly. The only public surface that no longer ships CSV is the zip.
  2. Shipping both formats doubles the zip size and clutters the auditor's working directory. The XLSX IS the deliverable now; no need to also ship the CSV behind it.

Why this and not "build the XLSX from scratch per statement"

wppCsv.ts already nails the per-statement layout (section headers, spacers, totals, prior-period column logic). Re-implementing every statement's row assembly in XLSX would duplicate that surface — and it would drift. Reading the CSV back is one extra parse but the output is byte-identical content (everything below row 8).

Decision log

2026-06-25 — T-125 opened + shipped

  • Attestation (Records (Infrastructure)): read AGENTS.md; task is a sibling of T-124 (same shared-letterhead unification) and of T-122 (#7) / T-123 (#6). No board entry covered the WPP CSV→XLSX conversion.
  • Source: Records (Infrastructure) · https://claude.ai/code/session_018RDB37kCqfouHdygVXTAtD
  • Owner direction (verbatim, 2026-06-25): quoted above.
  • Tests + tsc: npx tsc --noEmit clean. Live verification left to the owner — Accounting page → Working Paper Pack → confirm the downloaded zip contains xlsx/ (not csv/), each XLSX opens in Excel / Numbers with the shared letterhead band on rows 1–8 and the same statement rows below, and the manifest + README reflect XLSX.
  • Blast radius: wppCsv.ts is unchanged — anything that imports the CSV builders still works. The WPP endpoint's external shape stays a zip with Content-Type application/zip; only the file extensions inside change.

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.

  • 99f9c36 2026-06-25 — feat(records,wpp): unified Bank-Tx letterhead (T-124) + WPP CSV → XLSX (T-125)
  • a98745b 2026-06-25 — Merge main + renumber T-122 → T-126 (renumber-on-merge, parallel-agent T-122/123/124/125 collision) (mentions only)
  • 64b1a84 2026-06-30 — feat(T-136): WPP — accounting number format, single tabbed workbook, journals.csv (mentions only)