Die Rechnung wies bisher gar keine Steuer aus — weder Satz und Betrag noch
den Pflichthinweis nach § 19 UStG. Der Satz kommt jetzt aus
VAT_RATE_PERCENT und steht auf PDF und HTML-Ansicht: 7 (ermaessigt, Honig
als Lebensmittel), 19 (voll) oder 0 fuer die Kleinunternehmerregelung, bei
der anstelle der Steuerzeilen der vorgeschriebene Hinweis erscheint.
DIE SHOP-PREISE SIND BRUTTOPREISE. Gegenueber Verbrauchern ist der
Gesamtpreis inklusive Umsatzsteuer anzugeben (§ 1 PAngV), und genau dieser
Betrag steht in Product.priceCents und Order.totalCents. Die Steuer wird
deshalb herausgerechnet, nicht aufgeschlagen — ein Aufschlag ergaebe einen
anderen Endpreis als den, den die Kundschaft im Warenkorb gesehen und
bezahlt hat.
Gerechnet wird auf der Gesamtsumme statt je Position, damit sich Rundungen
einzelner Zeilen nicht aufsummieren, und der Steuerbetrag ergibt sich als
Rest statt aus einer zweiten Rundung: nur so gilt immer netto + steuer ===
brutto. Eine Rechnung, die sich nicht aufaddiert, ist keine brauchbare
Rechnung. Der Versand traegt den Satz der Ware, er ist umsatzsteuerlich
eine Nebenleistung.
Der Satz wird als Invoice.vatRatePercent FESTGESCHRIEBEN statt bei jeder
Anzeige aus der Umgebung gelesen. Wechselt der Steuerstatus des Betriebs,
muessen alte Rechnungen weiterhin den damals ausgewiesenen Satz zeigen — ein
Beleg ist die Aufzeichnung eines vergangenen Vorgangs, keine Ansicht auf die
heutige Konfiguration. Bestehende Rechnungen tragen null und zeigen wie
bisher keine Steuerzeilen, weil rueckwirkend nicht feststellbar ist, welcher
Satz gegolten haette.
Ein unbrauchbarer Wert faellt auf 7% zurueck und wird laut geloggt, statt
eine bereits bezahlte Bestellung scheitern zu lassen. Damit das nicht
unbemerkt bleibt, prueft "pnpm preflight" den Wert jetzt als Blocker und
warnt zusaetzlich bei einem in Deutschland unueblichen Satz.
Der bisherige legalNote war ein Platzhalter ("Kleinunternehmerregelung /
Umsatzsteuerausweis gemaess tatsaechlichem Status ergaenzen") und ist
ersetzt, da die Angabe nun tatsaechlich erfolgt.
Bekannte Grenze, im README vermerkt: es gibt genau EINEN Satz. Honig ist
ermaessigt besteuert, Kerzen nicht — werden beide verkauft, braucht es einen
Satz je ProductCategory.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
111 lines
5.3 KiB
YAML
111 lines
5.3 KiB
YAML
# Nicht-geheime Konfiguration. Geheimnisse (Passwoerter, API-Schluessel)
|
|
# gehoeren in 02-secret.yaml, NIE hierher: eine ConfigMap ist unverschluesselt
|
|
# und in der Cluster-Oberflaeche im Klartext lesbar.
|
|
#
|
|
# ANPASSEN vor dem ersten Ausrollen: NEXT_PUBLIC_SITE_URL und SMTP_FROM. Alle
|
|
# uebrigen Werte sind sinnvolle Vorgaben und koennen so bleiben; die leeren
|
|
# ("") gehoeren zu optionalen Funktionen, die dann einfach ruhen.
|
|
#
|
|
# Zusammen mit 02-secret.yaml deckt diese Datei JEDE Umgebungsvariable ab, die
|
|
# die Anwendung liest. Die Vollstaendigkeit wird automatisch geprueft, siehe
|
|
# src/lib/k8s-manifest-env.test.ts — kommt im Code eine neue Variable dazu,
|
|
# faellt das beim naechsten "pnpm test" auf, nicht erst im Betrieb.
|
|
apiVersion: v1
|
|
kind: ConfigMap
|
|
metadata:
|
|
name: wabenhain-config
|
|
namespace: wabenhain
|
|
data:
|
|
# ---------------------------------------------------------------------
|
|
# ANPASSEN
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Oeffentliche Adresse des Shops. Einzige Quelle fuer Sitemap,
|
|
# Canonical-Tags, JSON-LD und JEDEN Link in JEDER E-Mail (src/lib/url.ts).
|
|
# Muss zum "host" in 06-ingress.yaml passen — mit https, ohne
|
|
# abschliessenden Schraegstrich.
|
|
NEXT_PUBLIC_SITE_URL: "https://wabenhain.com"
|
|
|
|
# Absenderadresse aller Transaktions-E-Mails. Sollte auf einer eigenen
|
|
# Domain liegen, sonst landen die Mails regelmaessig im Spam-Ordner.
|
|
SMTP_FROM: "Wabenhain <[email protected]>"
|
|
|
|
# ---------------------------------------------------------------------
|
|
# VORGABEN — koennen so bleiben
|
|
# ---------------------------------------------------------------------
|
|
|
|
NODE_ENV: "production"
|
|
|
|
# "postgresql" oder "mysql" — muss zu dem Datenbank-Manifest passen, das du
|
|
# tatsaechlich ausrollst (03-postgres.yaml bzw. 03-mysql.yaml), UND zum
|
|
# Schema in DATABASE_URL im Secret. Ein einzelnes Image traegt beide
|
|
# Prisma-Client-Varianten, siehe src/lib/prisma.ts.
|
|
DATABASE_PROVIDER: "postgresql"
|
|
|
|
# Port des SMTP-Anbieters. 587 ist STARTTLS (der Normalfall), 465 waere
|
|
# implizites TLS.
|
|
SMTP_PORT: "587"
|
|
|
|
# Verzeichnis fuer hochgeladene Bilder und Videos. MUSS exakt dem mountPath
|
|
# des "uploads"-Volumes in 05-app-deployment.yaml entsprechen — die App
|
|
# weiss nicht, was dort gemountet ist, sie schreibt nur in diesen Pfad.
|
|
# Fehlt der Wert, schreibt die App nach "./uploads" relativ zum
|
|
# Arbeitsverzeichnis: das trifft im Image zufaellig denselben Ort, ist aber
|
|
# nirgends zugesichert. Deshalb hier ausdruecklich setzen.
|
|
UPLOAD_DIR: "/app/uploads"
|
|
|
|
# Ablage der Rechnungs-PDFs, nach Jahr und Kunde sortiert:
|
|
# /app/invoices/2026/mustermann-erika/RE-2026-A3F9C1B2.pdf
|
|
# Muss zum mountPath des "invoices"-Volumes in 05-app-deployment.yaml
|
|
# passen. BEWUSST ein anderes Verzeichnis als UPLOAD_DIR: auf die Uploads
|
|
# zeigt eine oeffentliche Route, auf dieses Verzeichnis keine. Siehe
|
|
# src/lib/invoice-storage.ts und 04-uploads-pvc.yaml.
|
|
INVOICE_DIR: "/app/invoices"
|
|
|
|
# Umsatzsteuersatz in GANZEN Prozent, der auf der Rechnung ausgewiesen wird.
|
|
# 7 = ermaessigter Satz (Honig als Lebensmittel, Anlage 2 zum UStG)
|
|
# 19 = voller Satz
|
|
# 0 = Kleinunternehmerregelung; die Rechnung traegt dann statt der
|
|
# Steuerzeilen den Pflichthinweis nach § 19 UStG
|
|
#
|
|
# Die Shop-Preise sind BRUTTOPREISE (§ 1 PAngV). Die Steuer wird daraus
|
|
# herausgerechnet, nicht aufgeschlagen — der Endpreis bleibt also genau der,
|
|
# den die Kundschaft im Warenkorb gesehen hat.
|
|
#
|
|
# Der Satz wird bei jeder Rechnung FESTGESCHRIEBEN. Eine Aenderung hier gilt
|
|
# nur fuer kuenftige Rechnungen; bereits ausgestellte behalten ihren Satz.
|
|
VAT_RATE_PERCENT: "7"
|
|
|
|
# Threads fuer das Umrechnen hochgeladener Videos (Admin -> Inhalte ->
|
|
# Hero-Video). NICHT ohne Not erhoehen: ffmpeg teilt sich das Speicherlimit
|
|
# dieses Pods mit dem Node-Prozess, und jeder zusaetzliche Thread haelt
|
|
# eigene Bildpuffer — bei 4K-Material rund 12 MB je Bild. Zu viele Threads
|
|
# bringen den Pod beim Video-Upload um. Siehe src/lib/video-transcode.ts.
|
|
VIDEO_TRANSCODE_THREADS: "2"
|
|
|
|
# "sandbox", bis die DHL-Versandmarken getestet sind, danach "production".
|
|
# Wirkt nur, wenn die DHL-Zugangsdaten im Secret gesetzt sind.
|
|
DHL_ENVIRONMENT: "sandbox"
|
|
|
|
# ---------------------------------------------------------------------
|
|
# OPTIONAL — leer lassen, solange die Funktion nicht genutzt wird
|
|
# ---------------------------------------------------------------------
|
|
|
|
# Oeffentlicher Teil von Cloudflare Turnstile (CAPTCHA). Der geheime
|
|
# Gegenpart TURNSTILE_SECRET_KEY steht im Secret — beide setzen oder beide
|
|
# leer lassen. Leer = kein CAPTCHA-Widget, die Formulare bleiben nutzbar.
|
|
NEXT_PUBLIC_TURNSTILE_SITE_KEY: ""
|
|
|
|
# Google-Eintrag, aus dem die echten Rezensionen auf der Startseite kommen.
|
|
# Die Place-ID ist oeffentlich (sie steckt in jeder Google-Maps-Adresse des
|
|
# Eintrags) und darf deshalb hier stehen; der abrechenbare
|
|
# GOOGLE_PLACES_API_KEY liegt im Secret. Leer = statische Beispiel-Stimmen.
|
|
GOOGLE_PLACE_ID: ""
|
|
|
|
# Adresse der ApiCore-API fuer den Stammdaten-Sync (Ernten, Produkte,
|
|
# Chargen — siehe src/lib/apicore-client.ts), mit dem globalen Praefix
|
|
# "api", z.B. "https://apicore.example.org/api". Nur die Adresse; die
|
|
# Zugangsdaten des Sync-Kontos liegen im Secret. Leer = der Sync-Knopf im
|
|
# Admin meldet einen Konfigurationsfehler.
|
|
APICORE_API_URL: ""
|