Commit Graph

3 Commits

Author SHA1 Message Date
eab30c194a fix(audit-wave-9): PDF correctness + brand asset hardening (pdf-auditor)
Address the pdf-auditor findings that survived the 2026-05-12 PDF stack
overhaul (pdfme → react-pdf). Items C-2/C-3 (tiptap-to-pdfme bugs) were
resolved when that 571-LOC bridge was deleted; remaining items:

- **M-7 wrong-port brand fallback** — replace `'Port Nimara'` defaults
  in PDF-rendering services. `reports.service` and `expense-export`
  throw when the port row is missing (the job is FK-keyed on a real
  port, so absence = broken state, must not stamp a competitor brand).
  `record-export` uses `'(port)'` as the visible placeholder.

- **M-2 silent field drift in fill-eoi-form** — promote the
  always-silent catch in `setText` / `setCheckbox` to log a structured
  warning per missing field (mirroring the existing `setBerthRange`
  pattern). A re-cut template with drifted AcroForm field names now
  surfaces in ops logs instead of shipping with empty values.

- **M-3 form not flattened** — `fillEoiFormFields` now flattens the
  AcroForm before save. Documenso pathway flattens server-side; this
  brings the in-app pathway to parity, so the signer can't edit
  pre-filled yacht dimensions / address / berth number after the fact.

- **M-1 PDF metadata** — set Title / Author / Subject / Lang / Producer
  / Creator on the generated EOI PDF for downstream readers and a11y
  tooling.

- **M-4 noisy berth-range warnings** — downgrade per-mooring warn to
  debug; emit a single summary warn per call when any passthrough
  occurred. Multi-berth EOIs with archived/legacy moorings no longer
  spam the log on every render.

- **M-6 source PDF sha pinning** — pin
  `assets/eoi-template.pdf` sha256 via `EXPECTED_EOI_SHA256` (exported
  for tests); `loadEoiTemplatePdf` warns once per process when the
  bytes drift without an explicit hash bump. Documented the
  intentional-update workflow in `assets/README.md`.

Tests updated in `tests/unit/pdf/fill-eoi-form.test.ts` to reflect
flatten + metadata (form fields are gone after flatten; pdf-lib has no
getLanguage so we assert the other setters round-trip).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 12:07:57 +02:00
100beb9974 feat(deps): papaparse for expense CSV export
Replaces the hand-rolled `[fields].map(v => \`"\${v}"\`).join(',')`
pattern in expense-export.tsx with papaparse's Papa.unparse.

The previous version didn't handle:
- commas inside fields (would split rows mid-record)
- newlines inside fields (would terminate rows early)
- BOM for Excel-friendly encoding
- numeric/null normalization

Papa.unparse handles all of those + accepts a keyed-object row shape
that lets us define column order and get matching headers for free.

Verified: tsc clean, vitest 1315/1315.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 22:49:20 +02:00
b7e010ff80 feat(expense-export): parent-company react-pdf + pdfkit brand header
Phase 1 / commit 10 of 14 — migrates the pdfme-based parent-company
expense export to react-pdf and adds a shared brand header to the
pdfkit-based streaming expense PDF so both surfaces match the rest of
the internal-only PDF family.

parent-company-expense.tsx:
  Summary KV grid (entry count, subtotal, fee, total) + entries table
  with right-aligned EUR amounts and a totals row. Footnote rendered
  when the EUR rate lookup falls through to the 1:1 USD:EUR fallback.

expense-export.tsx (renamed .ts -> .tsx):
  - exportParentCompany now renders the react-pdf template via
    resolvePortLogo() + renderPdf()
  - dropped the inline pdfme template object (was the last pdfme caller
    in this file)
  - return type widened from Uint8Array to Buffer; caller already wraps
    in Buffer.from() so no API change downstream

expense-pdf.service.ts (the pdfkit streaming engine — unchanged):
  - addHeader() now draws a dark slate band matching the brand-kit
    header band, with the port logo letterboxed on the left and the
    document title right-aligned. Falls back to text port-name if the
    logo image is missing or can't be decoded by pdfkit
  - port + logo resolved once per export via Promise.all
  - subheader stays beneath the band in muted grey, same as before
  - streaming behavior + receipt embedding + sharp compression
    untouched — the only change is the visual treatment of the header

Old pdfme inline template deleted along with the generatePdf import.
After this commit, the only remaining pdfme imports are in:
  invoice-template.ts, tiptap-to-pdfme.ts, eoi-standard-inapp.ts, and
  document-templates.ts (lines 516-522). All four are removed in
  commits 11-12.

1319/1319 vitest green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:01:45 +02:00