Add Kleinunternehmerregelung (§19 UStG) support end-to-end
Checkout forces 0% VAT without de-grossing prices when the tenant is a Kleinunternehmer (a business decision, not just an engineering default — unlike the existing intra-community VAT exemption, which does de-gross). Snapshotted onto the order at checkout time so a later toggle of the company-settings checkbox never rewrites an already-issued invoice's tax treatment — same reasoning as the existing vatExempt field. Threaded through: checkout route, order creation/confirmation email, on-demand invoice/Storno/Gutschrift downloads, the Bestellbestätigung page, and the account order-detail page. The four storefront "inkl. X% MwSt." price hints (shop grid, cart upsell, ToDo-Karten landing page, homepage spotlight) drop that clause live when the setting is on. The company-settings Live Preview reflects the checkbox in real time too. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -327,6 +327,11 @@ thumbnails per order row (`getProductImagesByIds()` in `app/lib/payload.ts`,
|
||||
a plain product-id → image-url lookup separate from the slug-keyed
|
||||
catalog, since an order only ever snapshots a numeric product id).
|
||||
|
||||
**None of this renders at all for a Kleinunternehmer tenant** (built
|
||||
2026-07-24) — see "Kleinunternehmerregelung" below for the full list of
|
||||
touched spots and why some read a live setting and others a persisted
|
||||
per-order snapshot.
|
||||
|
||||
### Checkout state persistence
|
||||
|
||||
`app/lib/checkoutDraft.ts` — `localStorage` under `ep_checkout_draft`,
|
||||
@@ -833,6 +838,63 @@ turns out unregistered, the seller retroactively owes the VAT itself).
|
||||
live from the current catalog, which would show the wrong, VAT-inclusive
|
||||
figures for an exempt order).
|
||||
|
||||
### Kleinunternehmerregelung (§19 UStG)
|
||||
|
||||
Built 2026-07-24. `company-settings.kleinunternehmer` — a standing
|
||||
per-tenant setting (Payload backend), not a per-order decision like the
|
||||
VAT exemption above — when on, this tenant never charges VAT on anything,
|
||||
domestic or cross-border. See the Payload README's own
|
||||
"Kleinunternehmerregelung" section for the field/collection side; this one
|
||||
covers what changed here.
|
||||
|
||||
- **`api/checkout/route.ts`** forces every item's `taxRatePercent` to `0`
|
||||
when `getCompanySettings().kleinunternehmer` is on — deliberately
|
||||
**without** de-grossing `unitPrice` the way `vatExempt` above does
|
||||
(that exemption zero-rates what would otherwise be a positive-rate
|
||||
charge, so de-grossing means the buyer pays less; a Kleinunternehmer
|
||||
never charged VAT on the sale to begin with, so the catalog gross price
|
||||
already *is* the actual net charge — confirmed with the user as the
|
||||
intended business decision, not an engineering default). The VIES
|
||||
lookup/`isExemptionEligibleCountry()` check is skipped entirely in this
|
||||
branch too — there's no VAT for the intra-community rule to exempt
|
||||
either. Snapshotted onto the new order as `Orders.kleinunternehmer`
|
||||
(mirrors `vatExempt`'s own snapshot reasoning — see the Payload README).
|
||||
- **Storefront "inkl. X% MwSt." hints** — four spots read the *live*
|
||||
setting (`getKleinunternehmer()` in `app/lib/payload.ts`, same ISR-cached
|
||||
60s freshness as `getDefaultTaxRatePercent()`) and drop the MwSt. clause
|
||||
entirely when it's on, since there's no order yet at that point to
|
||||
snapshot from: `ProductGrid.tsx` (shop grid), `RelatedProducts.tsx`
|
||||
(cart's upsell row), `Pricing.tsx`/`TodoKartenHero.tsx` (ToDo-Karten
|
||||
landing page), `ProductSpotlight.tsx` (homepage). `Pricing.tsx`/
|
||||
`ProductSpotlight.tsx` keep "zzgl. Versand" on its own when the MwSt.
|
||||
clause drops; the other two had no such trailing clause to preserve.
|
||||
- **Every already-placed-order display reads the persisted snapshot
|
||||
instead** — `OrderSnapshot.kleinunternehmer` (`app/lib/order.ts`,
|
||||
written into `sessionStorage` at checkout, read by
|
||||
`BestellbestaetigungContent.tsx`) and `CustomerOrderDetail.
|
||||
kleinunternehmer` (`app/lib/customerAuth.ts`, read by
|
||||
`/konto/bestellungen/[orderNumber]`) — never the live company-settings
|
||||
value, for the identical "don't retroactively rewrite an already-issued
|
||||
invoice's tax treatment" reason `vatExempt` already established. Both
|
||||
pages replace the per-item "inkl. X% MwSt." hint and the `VatBreakdown`
|
||||
summary with "Gemäß § 19 UStG wird keine Umsatzsteuer berechnet." —
|
||||
taking precedence over the `vatExempt` note wherever both would
|
||||
otherwise apply. `CheckoutContent.tsx`'s live VIES-exemption *preview*
|
||||
is also gated off (`!kleinunternehmer && ...`) so a Kleinunternehmer
|
||||
tenant never shows a misleading "wird steuerfrei berechnet" preview for
|
||||
VAT that was never going to be charged either way.
|
||||
- **On-demand invoice/Stornorechnung/Gutschrift downloads**
|
||||
(`api/account/orders/[orderNumber]/invoice/route.ts` and its
|
||||
`correction-invoice` sibling) thread `order.kleinunternehmer` through to
|
||||
`@einfach-produktiv/invoicing`'s renderers the same way they already
|
||||
thread `vatExempt`.
|
||||
- **`app/company-settings-preview`'s Live Preview** merges the live-edited
|
||||
`kleinunternehmer` checkbox onto the fixed `SAMPLE_INVOICE_ORDER` before
|
||||
rendering (`kleinunternehmer` lives on `InvoiceOrder`, not
|
||||
`InvoiceSeller` — see the invoicing package's own README on why), so an
|
||||
admin sees the §19 UStG notice appear/disappear live as they toggle the
|
||||
field, without this preview needing its own separate mechanism.
|
||||
|
||||
### Company Settings & Live Preview
|
||||
|
||||
`company-settings` has a Live Preview button too, like `email-templates`
|
||||
|
||||
Reference in New Issue
Block a user