diff --git a/README.md b/README.md
index 11c439a..1890a6d 100644
--- a/README.md
+++ b/README.md
@@ -70,7 +70,7 @@ Ships raw TypeScript/TSX source (no build step) via `main`/`types` pointing stra
`InvoiceOrder.vatExempt`/`CorrectionInvoiceOrder.vatExempt` (optional, default falsy) — set by the consuming app after its own checkout confirms (live VIES lookup) that a sale qualifies as an innergemeinschaftliche Lieferung. This package never decides the exemption itself — by the time an order reaches these renderers, every item's `unitPrice` is already de-grossed (net) and `taxRatePercent` already `0`; `vatExempt` only controls **display**:
-- Visual PDF (`invoicePdf.tsx`/`correctionInvoicePdf.tsx`): the "enthält X% MwSt." annotation under Gesamt becomes "Steuerfreie innergemeinschaftliche Lieferung (§4 Nr. 1b UStG)" instead.
+- Visual PDF (`invoicePdf.tsx`/`correctionInvoicePdf.tsx`): the "enthält X% MwSt." annotation under Gesamt becomes "Steuerfreie innergemeinschaftliche Lieferung (§4 Nr. 1b UStG)" instead — rendered as its own full-width row below the summary card (same treatment as the Vorkasse/unpaid notice), not squeezed into the card's narrow right-aligned column. Moved out 2026-07-25, at the user's explicit request.
- E-invoice XML (`buildEInvoiceData.ts`): VAT category `K` + VATEX-EU-IC exemption reason — see "E-invoicing" above for exactly where those fields are (and aren't) allowed to live.
See the frontend repo's own README ("VAT exemption" section) for the actual VIES lookup, de-grossing math, and checkout UI this feeds from, and the Payload backend's README ("B2B checkout & VAT exemption") for the persisted `Orders.vatExempt`/`vatIdValidatedAt` fields and audit-trail reasoning.
diff --git a/package.json b/package.json
index 59ce3e3..7c3377e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@einfach-produktiv/invoicing",
- "version": "0.2.4",
+ "version": "0.2.5",
"private": true,
"description": "Shared invoice / correction-invoice (Stornorechnung, Gutschrift) PDF generation and VAT-breakdown math, consumed as a git dependency by both the einfach-produktiv frontend and the payload backend — not published to npm.",
"type": "module",
diff --git a/src/correctionInvoicePdf.tsx b/src/correctionInvoicePdf.tsx
index acb65cb..be6a102 100644
--- a/src/correctionInvoicePdf.tsx
+++ b/src/correctionInvoicePdf.tsx
@@ -96,6 +96,11 @@ const styles = StyleSheet.create({
vatNoteRow: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 1 },
vatNoteLabel: { fontSize: 8, color: TEXT_MUTED },
vatNoteValue: { fontSize: 8, color: TEXT_MUTED },
+ // Same fix as invoicePdf.tsx's own taxExemptionNoticeRow/Text — the
+ // §19/§4-Nr.-1b-UStG notice moved out of the narrow summaryBox card to
+ // its own full-width sibling row below it, 2026-07-25.
+ taxExemptionNoticeText: { fontSize: 9, lineHeight: 1.5, color: TEXT_MUTED, textAlign: "left" },
+ taxExemptionNoticeRow: { width: "100%", alignSelf: "stretch", marginTop: 20 },
// `fixed` (on the element, see CorrectionInvoiceDocument below) — always
// pinned to the bottom of the page regardless of content height above,
// same as invoicePdf.tsx's own footer. (One of the two pre-package
@@ -346,20 +351,19 @@ function CorrectionInvoiceDocument({ kind, order, seller }: { kind: CorrectionIn
ordering fix as invoicePdf.tsx: "enthält X% MwSt."
describes Gesamt, not Netto (which has the tax already
taken out), so it must not read as attached to Netto. */}
-
- {order.kleinunternehmer ? (
- Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.
- ) : order.vatExempt ? (
- Steuerfreie innergemeinschaftliche Lieferung (§4 Nr. 1b UStG)
- ) : (
- rateGroups.map((g) => (
+ {/* Exempt/Kleinunternehmer orders get their own full-width
+ notice below the card instead (see taxExemptionNoticeRow
+ below) — same fix as invoicePdf.tsx. */}
+ {!order.kleinunternehmer && !order.vatExempt && (
+
+ {rateGroups.map((g) => (
enthält {g.rate}% MwSt.
-{formatPrice(g.tax)}
- ))
- )}
-
+ ))}
+
+ )}
{/* Netto — same reasoning as invoicePdf.tsx's own row: a
single well-defined figure (Gesamt minus every rate's own
tax, summed) regardless of how many rates are present.
@@ -383,6 +387,17 @@ function CorrectionInvoiceDocument({ kind, order, seller }: { kind: CorrectionIn
)}
+ {/* Sibling of `summary`, not nested inside it — same fix as
+ invoicePdf.tsx's own taxExemptionNoticeRow. */}
+ {(order.kleinunternehmer || order.vatExempt) && (
+
+
+ {order.kleinunternehmer
+ ? "Gemäß § 19 UStG wird keine Umsatzsteuer berechnet."
+ : "Steuerfreie innergemeinschaftliche Lieferung (§4 Nr. 1b UStG)"}
+
+
+ )}
diff --git a/src/invoicePdf.tsx b/src/invoicePdf.tsx
index 564c80d..8510cd4 100644
--- a/src/invoicePdf.tsx
+++ b/src/invoicePdf.tsx
@@ -64,6 +64,16 @@ const styles = StyleSheet.create({
// important instruction than a one-line confirmation, needs to read as
// its own section, not crowd the summary card above it.
unpaidNoticeRow: { width: "100%", alignSelf: "stretch", marginTop: 20 },
+ // Same full-width-below-the-card treatment as unpaidNoticeText/Row
+ // above, same reasoning: the §19/§4-Nr.-1b-UStG notice used to live
+ // *inside* summaryBox (see vatNotes' own comment), where its narrow
+ // right-aligned column squeezed what's actually an important legal
+ // statement about the invoice as a whole, not a figure belonging to the
+ // Zwischensumme/Rabatt/Versand/Gesamt build-up. Moved out to its own
+ // sibling row 2026-07-25, at the user's explicit request, once the
+ // unpaid-notice fix above had already established the pattern.
+ taxExemptionNoticeText: { fontSize: 9, lineHeight: 1.5, color: TEXT_MUTED, textAlign: "left" },
+ taxExemptionNoticeRow: { width: "100%", alignSelf: "stretch", marginTop: 20 },
table: { borderRadius: 6, overflow: "hidden", borderWidth: 1, borderColor: BORDER, marginTop: 8, marginBottom: 16 },
tableHeader: { flexDirection: "row", backgroundColor: BG_MUTED, paddingVertical: 8, paddingHorizontal: 10 },
// Every item row shares this same tinted background now — no more
@@ -416,30 +426,20 @@ export function InvoiceDocument({ order, seller }: { order: InvoiceOrder; seller
Gesamt
{formatPrice(order.total)}
-
- {/* Exempt orders already have every item at 0% (see this
- file's own InvoiceOrder.vatExempt comment) — "enthält
- 0% MwSt.: 0,00 €" would be a meaningless thing to print,
- so this replaces the whole per-rate breakdown with the
- actual legal basis instead. Kept directly under Gesamt
- (not after Netto) — "enthält X% MwSt." describes what's
- contained in Gesamt; sandwiching Netto between the two
- made it read as if Netto were what contained the tax,
- which is backwards (Netto is the figure with tax
- already taken out). */}
- {order.kleinunternehmer ? (
- Gemäß § 19 UStG wird keine Umsatzsteuer berechnet.
- ) : order.vatExempt ? (
- Steuerfreie innergemeinschaftliche Lieferung (§4 Nr. 1b UStG)
- ) : (
- rateGroups.map((g) => (
+ {/* Exempt/Kleinunternehmer orders get their own full-width
+ notice below the card instead (see taxExemptionNoticeRow
+ below) — this per-rate breakdown only ever applies to the
+ normal, non-exempt case now. */}
+ {!order.kleinunternehmer && !order.vatExempt && (
+
+ {rateGroups.map((g) => (
enthält {g.rate}% MwSt.
{formatPrice(g.tax)}
- ))
- )}
-
+ ))}
+
+ )}
{/* Netto (Gesamt minus every rate's own tax, summed — a
single well-defined figure regardless of how many
distinct rates the order actually has) — businesses read
@@ -477,9 +477,26 @@ export function InvoiceDocument({ order, seller }: { order: InvoiceOrder; seller
)}
- {/* Sibling of `summary`, not nested inside it — `summary`'s own
- `alignItems: "flex-end"` is meant for the narrow paid-badge
- case, not this full-width, multi-sentence instruction. */}
+ {/* Both siblings of `summary`, not nested inside it — `summary`'s
+ own `alignItems: "flex-end"` is meant for the narrow
+ paid-badge case, not these full-width, multi-sentence
+ notices. Legal notice first, payment instruction second —
+ when both apply (an unpaid, innergemeinschaftlich-exempt
+ Vorkasse order), the tax-exemption statement is the more
+ legally foundational one of the two. Yoga (react-pdf's
+ flexbox) doesn't collapse margins the way CSS does, so
+ `summary`'s own marginBottom: 24 plus each row's own
+ marginTop: 20 below still read as two clearly separated
+ blocks, not squeezed together. */}
+ {(order.kleinunternehmer || order.vatExempt) && (
+
+
+ {order.kleinunternehmer
+ ? "Gemäß § 19 UStG wird keine Umsatzsteuer berechnet."
+ : "Steuerfreie innergemeinschaftliche Lieferung (§4 Nr. 1b UStG)"}
+
+
+ )}
{!paid && (