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:
@@ -141,6 +141,15 @@ export async function POST(request: Request) {
|
||||
// Re-price everything server-side — never trust client-submitted prices.
|
||||
const [productsBySlug, companySettings] = await Promise.all([fetchProductsBySlug(), getCompanySettings()]);
|
||||
const defaultTaxRate = companySettings?.taxRatePercent ?? 19;
|
||||
// §19 UStG — a Kleinunternehmer tenant never charges VAT on anything,
|
||||
// full stop, so every item's tax rate is forced to 0% here regardless of
|
||||
// its own catalog/company-settings default rate. Unlike the
|
||||
// intra-community exemption below, prices are NOT de-grossed — see
|
||||
// Orders.ts's own kleinunternehmer field comment and this shop's
|
||||
// Kleinunternehmer decision: catalog gross prices stay exactly what they
|
||||
// are, they simply never had a VAT component charged on top in the
|
||||
// first place.
|
||||
const kleinunternehmer = Boolean(companySettings?.kleinunternehmer);
|
||||
const items: {
|
||||
productId: number;
|
||||
productName: string;
|
||||
@@ -182,7 +191,7 @@ export async function POST(request: Request) {
|
||||
quantity: line.qty,
|
||||
unitPrice: variant?.priceOverride ?? product.price,
|
||||
imageUrl,
|
||||
taxRatePercent: product.taxRatePercent ?? defaultTaxRate,
|
||||
taxRatePercent: kleinunternehmer ? 0 : (product.taxRatePercent ?? defaultTaxRate),
|
||||
bundleContents: describeBundleContents(product),
|
||||
variantName: variant?.name ?? null,
|
||||
});
|
||||
@@ -227,8 +236,13 @@ export async function POST(request: Request) {
|
||||
// unset in that case too).
|
||||
let vatExempt = false;
|
||||
let vatIdValidatedAt: string | null = null;
|
||||
// A Kleinunternehmer never charges VAT on any sale, domestic or
|
||||
// cross-border — the intra-community exemption exists to zero-rate what
|
||||
// would otherwise be a positive-rate charge, which never applies here in
|
||||
// the first place, so the VIES lookup is skipped entirely (also saves an
|
||||
// unneeded network round-trip).
|
||||
const buyerDestinationCountry = destinationCountry(body.country, Boolean(body.hasDifferentShippingAddress), body.shippingCountry);
|
||||
if (normalizedVatId) {
|
||||
if (!kleinunternehmer && normalizedVatId) {
|
||||
const viesResult = await checkVatIdViaVies(normalizedVatId);
|
||||
if (viesResult.ok && viesResult.valid) {
|
||||
vatIdValidatedAt = new Date().toISOString();
|
||||
@@ -275,6 +289,7 @@ export async function POST(request: Request) {
|
||||
companyName: body.companyName || undefined,
|
||||
vatId: normalizedVatId,
|
||||
vatExempt,
|
||||
kleinunternehmer,
|
||||
vatIdValidatedAt,
|
||||
deliveryMethod: body.deliveryMethod,
|
||||
street: body.street,
|
||||
@@ -335,6 +350,7 @@ export async function POST(request: Request) {
|
||||
companyName: body.companyName || undefined,
|
||||
vatId: normalizedVatId,
|
||||
vatExempt,
|
||||
kleinunternehmer,
|
||||
deliveryMethod: body.deliveryMethod,
|
||||
street: body.street,
|
||||
packstationNumber: body.packstationNumber,
|
||||
@@ -393,5 +409,6 @@ export async function POST(request: Request) {
|
||||
discountCode: body.discountCode || null,
|
||||
discountAmount,
|
||||
vatExempt,
|
||||
kleinunternehmer,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user