Commit Graph

14 Commits

Author SHA1 Message Date
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 c28ceb7bd3 Style Netto the same size/weight as the MwSt annotation
Validate e-invoices / mustang (push) Successful in 21s
Was using summaryLabel/summaryValue (fontSize 10, same as Zwischensumme/
Versand — the build-up rows above Gesamt), which visually competed
with Gesamt instead of reading as supplementary detail below it. Now
uses the same small/muted vatNoteLabel/vatNoteValue as the "enthält
X% MwSt." line right above it — both are detail underneath the hero
Gesamt figure, same visual tier.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 21:33:57 +00:00
Marco 3922ea12e1 Move Netto below the MwSt annotation, not above it
Validate e-invoices / mustang (push) Successful in 20s
Order was Gesamt → Netto → "enthält X% MwSt." — reads as if the
annotation describes Netto, which is backwards: Netto already has the
tax removed, Gesamt is what contains it. Reordered to Gesamt →
"enthält X% MwSt." → Netto so the annotation stays unambiguously
attached to the row it actually describes. Verified by re-rendering
and visually inspecting both the original invoice and a Storno.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 21:26:51 +00:00
Marco 31978d5ea2 Show Netto on every invoice, not just non-exempt ones
Validate e-invoices / mustang (push) Successful in 23s
Netto/Gesamt/MwSt breakdown was gated on !order.vatExempt — on a
vatExempt (innergemeinschaftliche Lieferung) invoice, Netto happens to
equal Gesamt exactly (0% tax), which read as redundant next to the
exemption note, so the row was skipped there. Turns out that's wrong:
the ask was for Netto on every invoice this shop issues, B2B/exempt
included, for layout consistency — not conditional on the tax rate.
Same fix in both invoicePdf.tsx and correctionInvoicePdf.tsx (the
latter covers both Storno and Gutschrift via the same summary code).

Verified by rendering all three cases (normal 19% invoice, exempt B2B
invoice, Storno correction) and visually inspecting the output.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 20:58:16 +00:00
Marco 942b8a8632 Add innergemeinschaftliche-Lieferung (VAT exemption) support
Validate e-invoices / mustang (push) Successful in 18s
InvoiceOrder/CorrectionInvoiceOrder gained an optional vatExempt flag
— items already carry 0% rate and de-grossed prices when true (the
caller's responsibility), this only controls display/XML mapping:

- Visual PDF: replaces the "enthält X% MwSt." annotation with
  "Steuerfreie innergemeinschaftliche Lieferung (§4 Nr. 1b UStG)" on
  both the invoice and its Storno/Gutschrift.
- EN16931 XML (buildEInvoiceData.ts): VAT category 'K' instead of 'S'
  on every line/tax-subtotal, plus BT-120/BT-121 exemption reason
  (VATEX-EU-IC) — but only on cac:TaxTotal's own TaxSubtotal.TaxCategory,
  not on InvoiceLine/AllowanceCharge's TaxCategory, which
  @e-invoice-eu/core's schema rejects those same two fields on (caught
  locally before Mustang ever saw it — a fully-rendered PDF/A-3 with a
  hand-inflated CII XML confirmed CategoryCode/ExemptionReasonCode/
  ExemptionReason land correctly).

Also surfaced (not fixed, unrelated to this change): a seller with no
iban configured throws in @e-invoice-eu/core's UBL→CII conversion —
doesn't affect this shop's own production data (iban is always set),
noting for whoever touches this next.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 18:59:19 +00:00
Marco 01374c47cd Fix invoice/correction-invoice summary: rows now actually sum to Gesamt
Validate e-invoices / mustang (push) Successful in 21s
The Netto/MwSt. rows were computed per tax rate with shipping and
discount already proportionally folded in (computeTaxBreakdown's
scale factor — tax-correct, since ancillary costs are apportioned
across rates), but Rabatt/Versand were ALSO shown as their own
separate rows on top. Adding up the visible rows never actually
matched the printed Gesamt — off by exactly the shipping (and any
discount) amount. Confirmed against a real production order
(#EP-0006-ZDX6's Stornorechnung).

Restructured both templates into a genuinely additive chain
(Zwischensumme → Rabatt → Versand → Gesamt, using the raw order
fields directly) with the per-rate tax now shown as an "enthält X%
MwSt." annotation below Gesamt — informational, not part of the sum.
Mirrors the pattern the website's own VatBreakdown component already
uses on /cart and /checkout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 18:50:23 +00:00
Marco 48da8c069a Plain-text paid badge, uniform item rows, show B2B recipient fields
Validate e-invoices / mustang (push) Successful in 22s
- The "Bereits beglichen" confirmation is now plain green Text, not a
  tinted pill/box — read as too heavy for a status note.
- Item table rows no longer alternate white/tinted (zebra striping);
  every row now shares the same tinted background for a more uniform
  table, on both the invoice and its Storno/Gutschrift corrections.
- InvoiceOrder/CorrectionInvoiceOrder gained optional companyName/
  vatId, rendered in the "An" recipient block when present — the
  buyer-side counterpart to the sellers' own fields already shown in
  the footer. Both consuming repos need their own commit to actually
  pass these through from Orders.companyName/vatId.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 18:16:27 +00:00
Marco ceedb4be05 Move paid badge outside the summary card, not just below the last row inside it
Validate e-invoices / mustang (push) Successful in 23s
The previous placement put it as one more row inside summaryBox, right after Gesamt — the user wanted it visually below/outside the tinted card entirely. Now a sibling of summaryBox instead of a child, verified by rendering to PDF.
2026-07-23 14:51:45 +00:00
Marco 6af96eb9b8 Invoice PDF layout: centered footer, clustered summary rows, more image/text spacing, relocated paid badge, stacked Packstation/Postnummer
Validate e-invoices / mustang (push) Successful in 22s
- Footer is centered, not left-aligned.
- Summary rows are grouped into visual clusters (Rabatt+Versand; each rate's own Netto/MwSt pair) with a bit of spacing between clusters, tight spacing within — makes it clearer which lines belong together.
- More space between the product image and the article text in each line item.
- "Bereits beglichen" badge moved from the top meta row to directly below "Gesamt", where it reads more naturally against the amount it confirms.
- Packstation number and Postnummer render as two separate lines instead of one "Packstation X · Postnummer Y" line.

Applied identically to both invoicePdf.tsx and correctionInvoicePdf.tsx (Stornorechnung/Gutschrift) so every generated invoice stays visually consistent — verified by rendering both to PDF and comparing.
2026-07-23 14:33:27 +00:00
Marco 4e9b4b9dd4 Add bankName back to InvoiceSeller, shown alongside IBAN/BIC
Dropped by accident when bankDetails split into iban/bic — a bank name
has no fixed format to validate, so it stays free text, but it's still
useful context next to the account numbers in the invoice footer.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-23 10:26:23 +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