Commit Graph

14 Commits

Author SHA1 Message Date
Marco ed96d36b54 Fix font path breaking every PDF render inside the Payload backend (v0.2.8)
Validate e-invoices / mustang (push) Successful in 23s
fonts.ts resolved the vendored Liberation Sans .ttf files via
`new URL('./assets/fonts/...', import.meta.url).pathname` — Next.js's
Turbopack/webpack bundler statically rewrites exactly that expression
into its own hashed static-asset system. Harmless in the frontend's own
build, but the Payload backend is also a Next.js app consuming this
package as a node_modules git dependency, where the hash referenced in
the compiled server code didn't match what was actually emitted to disk
— every invoice/correction-invoice admin download failed with
ENOENT: .../LiberationSans-Regular.<hash>.ttf. Not a cache issue,
reproduced identically after a from-scratch --no-cache rebuild. Fixed by
resolving the path via fileURLToPath(import.meta.url) + path.join
instead, which Turbopack does not special-case.
2026-07-30 10:48:42 +00:00
Marco d6e83db5cf Embed Liberation Sans in invoice PDFs for PDF/A-3 compliance (v0.2.6)
Validate e-invoices / mustang (push) Successful in 41s
An E-Rechnung checker flagged that generated invoices don't embed their
fonts. Both invoicePdf.tsx and correctionInvoicePdf.tsx used react-pdf's
built-in Helvetica/Helvetica-Bold, which is never embedded — PDF/A-3
(already required by this package's own Factur-X/ZUGFeRD pipeline) has
no exemption for standard fonts. Vendors Liberation Sans (SIL OFL-1.1,
metrically identical to Helvetica) and registers it via Font.register in
a new fonts.ts, imported by both PDF modules.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-28 23:37:28 +00:00
Marco bcdf8d2889 Move §19/§4-Nr.-1b-UStG notice out of the summary card
Validate e-invoices / mustang (push) Successful in 22s
Same fix invoicePdf.tsx's own unpaid-notice row already got: the
exemption/Kleinunternehmer notice used to live inside summaryBox's
narrow, right-aligned column, squeezing what's actually a full
sentence. Now a full-width sibling row below the card, same treatment
in both invoicePdf.tsx and correctionInvoicePdf.tsx, and ordered ahead
of the unpaid-notice row when both apply.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 19:48:08 +00:00
Marco b3bd5c3f07 Fix unpaid-Vorkasse notice layout: full page width, more top spacing
Validate e-invoices / mustang (push) Successful in 22s
Reused paidBadgeRow, which lives inside summary's narrow flex-end
column meant for the short one-line paid confirmation — the longer
instruction shrank to content width and read as squeezed under the
summary card. Moved to its own full-width sibling row, left-aligned.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 16:58:35 +00:00
Marco 8dc56c9783 Fix isPaidImmediately's exact-match regression, add unpaid Vorkasse instructions
Validate e-invoices / mustang (push) Successful in 41s
isPaidImmediately() matched paymentMethodTitle with an exact !== check
against the literal "Überweisung" — the consuming shop renamed its
Vorkasse row to "Überweisung (Vorkasse)" the same day, which would have
silently made every unpaid Vorkasse invoice show "✓ Bereits beglichen".
Changed to startsWith("Überweisung") — same design, tolerant of a
suffix qualifier.

Also adds an explicit instruction for the unpaid case (previously the
absence of the paid confirmation was the only signal, no actual text):
pay to the listed bank details, order processed after payment received
(~1-2 business days).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-25 16:11:44 +00:00
Marco ceaa437724 Drop the Netto row for Kleinunternehmer invoices
Validate e-invoices / mustang (push) Successful in 24s
Netto = Gesamt is structural under §19 UStG (no tax component ever
existed), not the coincidental 0%-this-one-sale case vatExempt orders
are in — showing it right under the §19 notice just duplicated Gesamt.
At the user's own request after reviewing a real invoice.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 15:18:36 +00:00
Marco 439165c98d Make InvoiceSeller.vatId optional for Kleinunternehmer sellers
Validate e-invoices / mustang (push) Successful in 18s
Payload's company-settings.vatId is no longer hard-required (a
Kleinunternehmer often has no USt-IdNr. at all) — this package's
InvoiceSeller.vatId follows suit, with every renderer now omitting the
USt-IdNr. line/field entirely instead of printing/serializing an empty
value.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 13:09:30 +00:00
Marco 51ae8a9c0a Add Kleinunternehmerregelung (§19 UStG) support
Validate e-invoices / mustang (push) Successful in 43s
InvoiceOrder/CorrectionInvoiceOrder gain an optional order-level
kleinunternehmer flag, snapshotted per order (same pattern as vatExempt)
so a later toggle of the tenant's setting never rewrites an
already-issued invoice. Takes precedence over vatExempt.

- Visual PDF: "enthält X% MwSt." becomes the §19 UStG notice.
- E-invoice XML: new UNTDID 5305 category "E" with a free-text exemption
  reason (no VATEX code — §19 UStG has none, BR-E-10 allows text alone).
- New Mustang CI fixture + buildEInvoiceData unit test for the "E" path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-24 12:51:13 +00:00
Marco 70c39f8b3a Document the Netto row and its universal-scope fix
Validate e-invoices / mustang (push) Successful in 22s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 21:07:04 +00:00
Marco 83ece1efef Document VAT exemption support and today's invoice-layout fixes
Validate e-invoices / mustang (push) Successful in 22s
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 19:19:26 +00:00
Marco 29ea9fafd8 Phase 4: Mustang EN16931/PDF-A-3 validation in CI
Validate e-invoices / mustang (push) Failing after 1m30s
Adds a Gitea Actions workflow that generates realistic e-invoice fixtures (multi-VAT-rate original invoice, Storno, Gutschrift) and validates them against the reference Mustang validator on every push, catching a broken EN16931 mapping before it reaches production instead of relying on manual spot-checks.
2026-07-23 12:12:51 +00:00
Marco 63e3a02212 Phase 3: actual ZUGFeRD/Factur-X e-invoice generation
renderInvoiceEInvoice()/renderCorrectionInvoiceEInvoice() produce a
Factur-X-EN16931 hybrid PDF/A-3 (existing @react-pdf/renderer PDF +
embedded EN16931 XML) via @e-invoice-eu/core, instead of a plain PDF.
The plain renderInvoicePdf()/renderCorrectionInvoicePdf() stay unchanged
and are still what Live Preview uses — this only adds a post-processing
step for the actual send/download paths.

buildEInvoiceData()/buildCorrectionEInvoiceData() map InvoiceOrder/
CorrectionInvoiceOrder + InvoiceSeller into the library's raw UBL-shaped
Invoice object, reusing computeTaxBreakdown() for the tax math — one
implementation feeding both the human-readable and machine-readable side
of the same document. Verified end-to-end: rendered a sample invoice and
correction invoice, inflated the embedded XML stream out of the resulting
PDF/A-3 by hand (the library has no attachment-reading API to check
against), confirmed correct CrossIndustryInvoice XML, EN16931 guideline
reference, per-rate tax breakdown matching the order totals exactly, and
payment means with the IBAN from Phase 2 — not just "it didn't throw."

Found only through actually running it (not documented anywhere): every
EN16931 amount field requires a sibling `*@currencyID` key via the
library's runtime ajv validation, invisible in its TypeScript types.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 11:28:03 +00:00
Marco 62821ddc8a Phase 2: structured iban/bic instead of free-text bankDetails
InvoiceSeller.bankDetails becomes iban/bic (EN16931 wants discrete
PaymentMeans data, not a paragraph a human formatted by hand). The
footer's bank-details line also drops the misleading "(für Überweisung)"
qualifier — it was never actually conditional on the order's payment
method (only on whether the field was set at all), just worded as if it
were. Now shown whenever iban or bic is set, regardless of payment
method.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 10:06:12 +00:00
Marco 9336cfd0ba Initial commit: shared invoice/correction-invoice PDF generation
Consolidates code previously hand-duplicated between the einfach-produktiv
frontend and payload backend repos into one canonical implementation,
consumed as a git dependency by both instead of being kept in sync by eye.

While merging the two correction-invoice copies, found and fixed three
real drifts between them:
- variantName was silently dropped in the backend's emailed Stornorechnung/
  Gutschrift, but present in the frontend's re-download copy
- the backend's correction-invoice footer wasn't position:fixed, unlike
  the original invoice and the frontend's copy
- the backend used a numeric date format (03.07.2025) while the original
  invoice and the frontend's copy both used a spelled-out month (03. Juli
  2025) — a re-download didn't visually match what was emailed

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 08:33:12 +00:00