Skip to content

feat(templates): add the ClassicInvoice layered invoice preset - #608

Merged
DemchaAV merged 2 commits into
feature/template-promotionfrom
feat/invoice-classic-preset
Aug 30, 2026
Merged

DemchaAV merged 2 commits into
feature/template-promotionfrom
feat/invoice-classic-preset

Conversation

@DemchaAV

Copy link
Copy Markdown
Owner

Why

The layered invoice family ships a single preset — ModernInvoice. The published standalone invoice-classic template (the letterhead-style invoice look) targets the removed 1.6.x surface (InvoiceTemplate, BusinessTheme), so on 2.x that look is unreachable through the built-in templates API: using it means vendoring two source files pinned to graph-compose:1.6.7.

What changed

  • invoice/presets/ClassicInvoice — a DocumentTemplate<InvoiceDocumentSpec> via create() / create(BrandTheme), mirroring the ModernInvoice shape: header band (company name + 28pt INVOICE title), TOTAL DUE hero strip, BILL TO / FROM party columns, the line-items table, a dedicated Summary table composed after the line items (last row emphasized via totalRow), and the notes / payment-terms footer. Long invoices paginate naturally with the table header repeated.
  • Theme surfaces read from BrandTheme.invoiceModern(), whose palette and Helvetica scale are value-identical to the ported template's theme. The deep-teal / gold inks and the geometry constants with no theme slot (17pt company name, 16pt flow spacing, 24pt page padding) stay preset-local: a new Palette slot would change the record's public constructor, so promoting the shared colours waits for a compatible opening.
  • The preset skips the Summary section when the spec carries no summary rows — the engine rejects a zero-row table — and composition is unchanged for any invoice with at least one summary row.
  • qa: InvoicePresetFixtures (canonical invoice + forty-line-item overflow) is the single fixture source for both gates. InvoiceV2VisualParityTest gains the classic_invoice row; the new ClassicInvoiceLayoutSnapshotTest freezes exact geometry for the canonical single page and the two-page overflow (page count asserted explicitly); ClassicInvoiceSmokeTest drives full layout + render through toPdfBytes(), including the empty invoice. The overflow contract is guarded at snapshot level, where the comparison is exact on every platform — not with full-page pixel baselines, whose cross-platform drift exceeds what they would guard.
  • Examples: ClassicInvoiceV2Example renders invoice-classic-v2, wired into GenerateAllExamples and the showcase metadata. CHANGELOG entry under v2.2.3 — Planned / Templates.

Verification

Full reactor gate (the eight CI modules, ./mvnw -B -ntp verify -pl :graph-compose-core,:graph-compose-render-pdf,:graph-compose-render-docx,:graph-compose-render-pptx,:graph-compose-templates,:graph-compose-testing,:graph-compose-qa,:graph-compose-coverage -am) → BUILD SUCCESS; javadoc:javadoc -pl :graph-compose-templates → 0 warnings. Invoice qa suite 14/14 green (-f qa/pom.xml: 5 smoke ClassicInvoice, 5 smoke ModernInvoice, 2 snapshot, 2 pixel rows).

Port parity, verified on one machine against the published template itself: the preset rendered with the template's own example fixture is pixel-identical to the template's approved preview — 0 / 2,173,720 differing pixels @150 DPI on the canonical page, and 0 on both pages of the forty-item overflow render; the resolved layout snapshots are node-identical (33/33 paths, zero geometry deltas > 0.01pt).

Notes

Four traits of the ported render are preserved deliberately and documented in the class Javadoc: the dark body-bold header ink on the deep-teal table-header fill; the TOTAL DUE strip carrying the invoice metadata (number, dates, status) rather than an amount; InvoiceData.title() not being rendered (the header always reads INVOICE); and the Summary table shrink-wrapping to its own rows instead of stretching to the line-items width. Changing any of these is a redesign of the ported look, not part of this port, and belongs in its own PR.

Lane: canonical (templates.invoice + qa + examples) — no engine changes.

The invoice family had a single preset. ClassicInvoice brings the
letterhead-style look - header band, TOTAL DUE hero strip, BILL TO /
FROM party columns, a dedicated Summary table after the line items,
and a notes / payment-terms footer - onto the layered stack with the
same create() / create(BrandTheme) contract as ModernInvoice, porting
the rendered layout of the published standalone invoice-classic
template. The preset reads its surfaces from BrandTheme.invoiceModern()
and skips the Summary section when the spec carries no summary rows
(the engine rejects zero-row tables).

qa: a shared InvoicePresetFixtures feeds both the pixel gate and the
new exact layout-snapshot gate (canonical single page plus a
forty-line-item overflow freezing the two-page table continuation);
ClassicInvoiceSmokeTest drives full layout and render, including the
empty invoice. Examples gain ClassicInvoiceV2Example
(invoice-classic-v2 in the showcase).
Every showcase document is either a committed preview under
assets/readme/examples or deliberately unpublished; invoice-classic-v2
now ships its preview alongside the other invoice renders.
@DemchaAV
DemchaAV changed the base branch from develop to feature/template-promotion August 30, 2026 17:57
@DemchaAV
DemchaAV merged commit 16ed4f1 into feature/template-promotion Aug 30, 2026
12 checks passed
@DemchaAV
DemchaAV deleted the feat/invoice-classic-preset branch August 30, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant