Zum Inhalt springen

Einfache Rechnungsstellung (§14 UStG-konform, Skonto, Wiederkehrend, OP-Liste, Mahnwesen)

Live in Produktion

Feinkonzept — Einfache Rechnungsstellung (§3.5)

Abschnitt betitelt „Feinkonzept — Einfache Rechnungsstellung (§3.5)“

Scope-Notiz: Werkszeit ersetzt keine Finanzbuchhaltung. DATEV/lexoffice/sage bleibt FiBu-Kern. Werkszeit stellt die Ausgangsrechnungs-Voraufzeichnung aus Zeit-/Material-/Aufmaß-/Spesen-Buchungen, führt die OP-Liste, schreibt Mahnungen und übergibt Buchungsstapel an die FiBu (§3.10). Eingangsrechnungs-Verarbeitung, Anlagenbuchhaltung, USt-Voranmeldung — liegen bewusst außerhalb.


feature_id: kern/05-rechnungsstellung
title: Einfache Rechnungsstellung (§14 UStG-konform, Skonto, Wiederkehrend, OP-Liste, Mahnwesen)
funktionsumfang_ref: §3.5
roadmap_horizont: MVP # Rechnungsstellung + OP-Liste Day 1, Mahnwesen V1
plattformen:
mobile: aus # Erstellung rein Web; Touch-Signatur auf Stundennachweis-PDF ist in §3.4 verortet
web: vollständig
desktop: aus
owner_rolle: Buchhaltung
modul_gate_flag: module.kern.billing
compliance_flags:
gobd: true # §§145–147 AO, Rechnungsausgangsbuch unveränderlich, 10 Jahre Aufbewahrung
arbzg: false
vob: false # VOB-Abschlagsrechnungen sind §3.5 des Bau-Feinkonzepts, nicht hier
dsgvo: true # Rechnungsempfänger = personenbezogene Daten (Inhaber Einzelunternehmen)
bfsg: false # Buchhaltungsrolle ist Admin-Web, nicht ESS-Pflichtkreis des BFSG
betrvg: false
stvg: false
weitere: ["§14 UStG", "§14a UStG / EN 16931 / XRechnung 3.0", "§288 BGB (Verzugszinsen)", "§286 BGB (Verzug)", "§247 BGB (Basiszinssatz)"]
referenzkunde:
status: TBD
name: "zu klären mit Sales — Design-Partner-Slot Buchhaltung"
quelle: "Interview-Protokoll Sales-Discovery"
estimate_eng_tage: 42 # 24 Grund-Rechnung/OP + 10 Mahnwesen + 8 Wiederkehrend
abhängigkeiten:
- kern/04-projekte-kunden # Debitor + UStID + Zahlungsbedingungen
- kern/03-zeiterfassung # Buchungs-Basis für "Rechnung aus Zeit"
- kern/06-spesen-reisemanagement # Weiterberechenbare Spesen
- kern/10-datev-integration # EXTF-Export der Rechnungs-Buchungen
- kern/11-audit-log # Rechnungs-Freigaben, Stornos, Mahn-Läufe sind Audit-pflichtig

Marktrealität (DE-Handwerk). Ein SHK-Betrieb mit 25–80 Mitarbeitern schreibt pro Monat typischerweise 30–120 Ausgangsrechnungen. Die Positionen kommen aus vier Quellen: erfassten Stunden, verbrauchtem Material, Aufmaß-Abrechnungen und weiterberechenbaren Spesen (Anfahrt, Kilometerpauschale, Tagegeld). Heute ist der Prozess in der Mehrzahl der Betriebe ein Medienbruch-Rennen: Monteur notiert Stunden auf Zettel, Bauleiter trägt sie in Excel, Buchhalter kopiert sie nach Sage/Lexware/Agenda, ruft die Material-Entnahmen aus dem Zentrallager ab, verdichtet, rundet, exportiert PDF, druckt, frankiert, versendet. Fehler entstehen an jeder Medienübergang: Tippfehler bei Stundensatz, vergessene Zuschläge (Nacht/Samstag), überholte Skonto-Vereinbarung, veraltete §14-UStG-Pflichtangaben, fehlende Leistungs-Datums-Angabe bei Teilleistung, handgeschriebene Rechnungsnummer (Kreissprung bricht).

Der rechtliche Rückenwind für Digitalisierung ist seit 01.01.2025 konkret: E-Rechnungs-Empfangspflicht für alle inländischen B2B-Umsätze (Wachstumschancengesetz, §14 UStG). Die Erstellungs-Pflicht greift gestaffelt bis 01.01.2028 — der Markt zieht schon jetzt mit, weil große Auftraggeber (ÖV, Industriekunden) XRechnung als Einlieferungsbedingung nennen. Werkszeit muss ab MVP EN 16931-taugliche Rechnungen erzeugen können; die eigentliche XRechnung/ZUGFeRD-Ausleitung ist in V1 (§3.5 des Bau-Feinkonzepts, über Mustang-Lambda).

Schmerzpunkt der Alt-App. Die Alt-App hatte ein invoices.js-Modul und eine XRechnung-Rudimentär-Route, aber keine Rechnungsnummern-Kreise pro Mandant, keine echte OP-Liste (nur einen paidAt-Bool), kein Mahnwesen mit Textbausteinen, und keine Storno-Behandlung mit Korrekturrechnung — einfaches Löschen war möglich (LESSONS-LEARNED §5, §7). Für GoBD ist das ein direkter Audit-Mangel: Rechnungen sind Grundaufzeichnungen, jede Rechnungsnummer muss lückenlos und unveränderlich sein (§146 Abs. 4 AO). Wir korrigieren das mit Append-only-Rechnungskopf + Hash-Chain pro Rechnungsnummernkreis.

Erwarteter Outcome. Reduktion der Rechnungs-Erstellungszeit von ≥20 min pro Rechnung (manuell aus Excel) auf ≤3 min pro Rechnung (Ein-Klick „Rechnung aus Zeit KW 16“). Entsprechend: Durchschnittliches Zahlungsziel-DSO (Days Sales Outstanding) um ≥7 Tage verkürzen, weil die OP-Liste einen automatisierten 3-stufigen Mahnlauf fährt. Referenz-Metrik: Bei 60 Rechnungen/Monat × 17 min Zeitersparnis = 17 Arbeitsstunden/Monat Buchhalter-Zeit.


Rolle Aktion Scope Plattform
Buchhaltung Rechnung anlegen, freigeben, stornieren, Mahnlauf ausführen all 🌐
Manager (Bauleitung) Rechnungsvorschlag aus Projekt erzeugen, zur Freigabe einreichen team 🌐
Admin Rechnungsnummernkreise, Skonto-Profile, Mahntexte, Kontenrahmen konfigurieren all 🌐
Mitarbeiter — (kein Zugriff auf Rechnungen)

Persona-Skizzen:

  • Frau Müller (Buchhaltung, 56, shk-gebruder-schmidt) — seit 22 Jahren DATEV-erfahren, kennt jede Buchungsnummer auswendig. Erwartet: klare Bildschirmmasken ohne Modal-Akrobatik, Tab-fähige Formulare, Zahlen rechtsbündig, Mono-Font, Kontoblatt-Denken. Fürchtet: „bunten Startup-Kram“, der ihre Routine bricht. Nutzt einen 27“-Monitor, Tastaturkürzel (Enter = Speichern, ESC = Abbrechen) sind Pflicht.
  • Sabine Maier (Manager, 45, musterbetrieb-maler) — stellt den Rechnungsvorschlag für ihr Projekt „Sanierung Heizungsanlage Bgm. Müller-Schule“ zusammen, reicht ihn zur Freigabe ein. Will keinen Kontenrahmen sehen.
  • Thomas Schmidt (Bauleitung, 52, shk-gebruder-schmidt) — prüft vor Versand die Leistungsbeschreibung auf dem Rechnungs-PDF aus Kundensicht.

US-01 [MVP] Als Buchhaltung (Frau Müller) möchte ich aus freigegebenen Zeit-
und Material-Buchungen eines Projekts per Ein-Klick eine §14-UStG-
konforme Rechnung erzeugen, um die KW-Abrechnung gegenüber der
Meiergroup GmbH in unter 3 min zu versenden.
US-02 [MVP] Als Admin möchte ich pro Mandant/Geschäftsjahr einen
Rechnungsnummernkreis mit lückenloser Fortlaufnummer konfigurieren,
um die GoBD-Anforderung §146 Abs. 4 AO zu erfüllen.
US-03 [MVP] Als Buchhaltung möchte ich in der OP-Liste alle offenen Rechnungen
nach Altersklassen (0-30 / 31-60 / >60 Tage) aggregiert sehen, um
den Liquiditätsstand gegenüber der Geschäftsführung zu berichten.
US-04 [V1] Als Buchhaltung möchte ich Skonto-Regeln pro Kunde (3 % bei 7
Tagen) und pro Rechnung (Override) pflegen, um die zwischen
Meiergroup und uns vereinbarten Zahlungskonditionen
rechtsverbindlich abzubilden.
US-05 [V1] Als Admin möchte ich ein 3-stufiges Mahnwesen mit konfigurierbaren
Texten und Fristen definieren (Zahlungserinnerung freundlich
nach 5 Tagen Überfälligkeit, 1. Mahnung mit Mahngebühr nach 14
Tagen, 2. Mahnung mit Inkasso-Androhung nach 30 Tagen),
um §286 BGB-Verzug sauber zu dokumentieren.
US-06 [V1] Als Buchhaltung möchte ich wiederkehrende Rechnungen
(Wartungsvertrag SHK-Anlage, monatliche Pauschale 89,00 €)
automatisch zum 1. eines Monats erzeugen lassen, um manuelle
Nacharbeit zu eliminieren.
US-07 [V1] Als Buchhaltung möchte ich eine fehlerhaft versandte Rechnung
stornieren können, wobei das System automatisch eine
Korrekturrechnung (Storno-Beleg) mit Verweis auf die
ursprüngliche Rechnungsnummer erzeugt, um GoBD-
Unveränderlichkeit zu wahren.
US-08 [V1] Als Buchhaltung möchte ich jede freigegebene Rechnung als
Buchungssatz in den DATEV-Export-Stapel legen lassen, damit der
Steuerberater sie im EXTF-Format abholen kann (§3.10).

nicht zutreffend, weil Rechnungserstellung ausschließlich Admin-/Buchhaltungs-Arbeit auf dem Großbildschirm ist (FUNKTIONSUMFANG.md §1, Verteilungs-Matrix). Mobile zeigt nur als Teil von §3.4 (Projekt-Details) den Rechnungs-Status zum Kunden („3 offene Rechnungen, 2.840,00 €“) — das ist kein Feature dieses Feinkonzepts.

Rechnungserstellung:

  • F-W-01 Rechnung anlegen über Rechnungen → Neu oder Projekt → Rechnung aus freigegebenen Buchungen.
  • F-W-02 Positions-Import aus: (a) freigegebenen Zeit-Buchungen mit Stundensatz-Lookup aus Kunde/Projekt/Mitarbeiter-Matrix, (b) Material-Buchungen aus §4.6 Autolager (V2, MVP: manuelle Positionen), (c) Aufmaß-Positionen aus §4.4 REB 23.003 (V1), (d) Spesen aus §3.6 (über Projekt- oder Bewirtungs-Tag „weiterberechenbar“).
  • F-W-03 Pflichtfeld-Prüfung live gegen §14 Abs. 4 UStG — siehe §10.1. Speichern als „Entwurf“ auch mit fehlenden Pflichtangaben erlaubt, „Freigeben“ blockiert, bis alle Pflichtfelder gesetzt sind.
  • F-W-04 Kleinbetragsrechnung (§33 UStDV, Gesamtbetrag ≤ 250 € brutto) mit reduzierten Pflichtangaben wird automatisch erkannt und separat validiert.
  • F-W-05 Rechnungsnummern-Vergabe atomar bei Freigabe (nicht bei Entwurf) — PostgreSQL UPDATE invoice_number_series SET next = next + 1 RETURNING next in Transaction-Lock. Keine Lücken, keine Doppelvergabe.
  • F-W-06 PDF-Generierung serverseitig via Typst (TECH-STACK §5.1) mit Firmenkopf, Logo, §14-UStG-Pflichtblock im Fußbereich, Skonto-Klausel, Bankverbindung, USt-ID, HR-Nummer, Geschäftsführer, Gerichtsstand.
  • F-W-07 XRechnung 3.0 / ZUGFeRD 2.3-Ausleitung über Mustang-Lambda (V1, §3.5-Bau). MVP: EN 16931-kompatibler Datenblock wird bereits persistiert, aber nur als PDF versendet.

OP-Liste:

  • F-W-08 Separate Ansicht Rechnungen → Offene Posten mit Altersstruktur 0-30 / 31-60 / >60 Tage, sortiert nach Fälligkeit, Spalten: RE-Nr., Kunde, Datum, Fälligkeit, Brutto, Offen, Alter, Status-Pill. Summen-Footer pro Altersklasse.
  • F-W-09 Zahlungseingang buchen (Teilzahlung möglich) — Datum, Betrag, Zahlart (Überweisung/Bar/Karte/SEPA/Skonto-Abzug), Bemerkung, Buchungskonto. Skonto wird als eigener Teilzahlungs-Typ erfasst, nicht als Forderungsverzicht.
  • F-W-10 SEPA-Rückläufer erfassen (Teilzahlung rückbuchen + Rücklastschrift-Gebühr als separater Posten).
  • F-W-11 CSV-/PDF-Export der OP-Liste für Steuerberater.

Mahnwesen:

  • F-W-12 Mahnvorschlagslauf (täglich 06:00 Uhr als BullMQ-Job oder On-Demand): Jeder offene OP wird gegen die 3-Stufen-Fristen geprüft, eine Vorschlagsliste erzeugt. Buchhaltung prüft, selektiert, löst Mahn-Versand aus (kein Auto-Versand ohne menschliche Freigabe — LESSONS-LEARNED §5).
  • F-W-13 Mahntext pro Stufe aus Admin-Template (Handlebars-artige Platzhalter: {{kundenname}}, {{rechnungsnummer}}, {{faelligkeit}}, {{offener_betrag}}, {{mahngebuehr}}, {{verzugszinsen}}).
  • F-W-14 Verzugszinsen-Berechnung nach §288 BGB: 9 % über Basiszinssatz (§247 BGB) für B2B-Geschäfte, 5 % für B2C. Basiszinssatz-Tabelle wird halbjährlich gepflegt (Admin, Audit-pflichtig).
  • F-W-15 Mahngebühren-Staffel: Stufe 1: 0,00 € (Zahlungserinnerung, keine Mahngebühr), Stufe 2: 5,00 €, Stufe 3: 10,00 € — konfigurierbar.
  • F-W-16 Mahn-PDF wird als eigener Beleg mit eigener Mahnnummer (separater Nummernkreis) archiviert. Rechnung bleibt unverändert.

Storno/Korrekturrechnung:

  • F-W-17 Rechnung stornieren erzeugt automatisch eine Korrekturrechnung (§14 Abs. 4 UStG Satz 1 Nr. 10 — ausdrückliche Kennzeichnung als Korrektur), mit Verweis-Feld bezieht_sich_auf = rechnung.nummer, Beträge in negativer Vorzeichen. Original bleibt bestehen und versendbar (GoBD-Unveränderlichkeit).
  • F-W-18 Storno-Begründung ist Pflichtfeld (Textfeld, min. 10 Zeichen). Audit-Log-Eintrag invoice.stornoed.

Wiederkehrende Rechnungen:

  • F-W-19 Wiederkehrende Rechnungen als eigene Ansicht: Vorlage mit Kunde, Positionen, Turnus (täglich/wöchentlich/monatlich/quartalsweise/jährlich), Start/Ende, Freigabe-Modus (auto-freigeben vs. Entwurf-mit-Push-an-Buchhaltung).
  • F-W-20 Cron-Job (02:00 Uhr) erzeugt für jede fällige Vorlage eine Rechnung. Vorlagenänderungen gelten erst ab der nächsten Ausführung.
  • F-W-21 Preisanpassung pro Vorlage (Indexklausel, VPI-Anpassung) nur als manuell auszulösende Aktion — keine heimliche Änderung.
  • F-X-01 Rechnungs-PDF-Download und -Ansicht auch mobil möglich (read-only, über printing / pdfx Flutter-Packages) — etwa wenn Frau Müller im Urlaub kurz per Mobile freigibt. Erstellung/Versand bleibt Web.
  • F-X-02 Push-Benachrichtigung an Buchhaltung bei eingegangener Zahlung (via Bank-Webhook in V2), bei abgelaufener Skonto-Frist (Warnung an Debitor — V2) und bei Mahnfälligkeit.
  • F-A-01 Rechnungsnummernkreise pro Mandant und Geschäftsjahr: Präfix (z. B. RE-2026-), Startnummer, aktueller Stand, Reset-Regel (jährlich/fortlaufend). Änderung am aktuellen Kreis ist nicht erlaubt, nur neue Kreise lassen sich anlegen (GoBD-Rigidität).
  • F-A-02 Nummernkreise auch für Storno/Korrektur, Mahnungen, Gutschriften — jeweils separat.
  • F-A-03 USt-Konfiguration: Standard-Sätze 19 %/7 %/0 %, §13b UStG-Kennzeichen (Reverse Charge bei Bauleistungen — siehe §3.5-Bau-Feinkonzept), Kleinunternehmer-Regelung (§19 UStG) pro Mandant setzbar.
  • F-A-04 Skonto-Profile: Name, Tage, Prozent, Gültigkeit je Kundengruppe/Kunde. Beispiele: „Standard-Skonto 2 % 10 Tage / 30 Tage netto“, „Premium-Kunde 3 % 7 Tage“.
  • F-A-05 Mahn-Konfiguration: Anzahl Stufen (fix 3 MVP), Fristen (Tage Überfälligkeit), Texte (Handlebars), Mahngebühren, Verzugszinsen-Flag.
  • F-A-06 Bankkonten-Stammdaten (IBAN, BIC, Bank), Standard-Bankkonto pro Mandant — landet auf der Rechnung.
  • F-A-07 Kontenrahmen-Mapping: welches Sachkonto (SKR03/SKR04) je Leistungsart (Zeit, Material, Spesen, Fahrtkosten) — landet im DATEV-Export (§3.10).
Anforderung-ID MVP V1 V1.5 V2
F-W-01 bis F-W-06
F-W-07 (XRechnung/ZUGFeRD)
F-W-08 bis F-W-11 (OP-Liste, Zahlungseingang)
F-W-12 bis F-W-16 (Mahnwesen)
F-W-17, F-W-18 (Storno)
F-W-19 bis F-W-21 (Wiederkehrend)
F-W-02c (Aufmaß-Import)
F-W-02b (Material aus Autolager)
F-A-01 bis F-A-07
F-X-02 (Push Zahlungseingang via Bank-Webhook)

HTML-Hero-Mockup: 05-rechnungsstellung.html — Mobile-Ansicht (read-only Rechnungs-Status) + Web-Ansicht (Rechnung erstellen, OP-Liste).

┌────────────────────────────────────────────────────────────────────────────────┐
│ Werkszeit · shk-gebruder-schmidt Frau Müller · Buchhaltung [Profil ▾] │
├──────────┬─────────────────────────────────────────────────────────────────────┤
│ Sidebar │ Rechnung RE-2026-0147 · Entwurf 🛡 GoBD · §14 UStG │
│ ─────────│ ─────────────────────────────────────────────────────────────────── │
│ Zeit │ Kunde: [Meiergroup GmbH ▼] UStID: DE123456789 │
│ Projekte │ Projekt: [Sanierung Heizungsanlage Bgm.Müller-Schule ▼] │
│▶Rechnung│ Adresse: Lindenstraße 3, 80331 München (Debitor: 10042) │
│ DATEV │ ─────────────────────────────────────────────────────────────────── │
│ Stamm │ Leistungszeitraum: [12.04.2026] – [18.04.2026] (KW 16) │
│ │ Rechnungsdatum: [19.04.2026] Fälligkeit: [19.05.2026, 30 d] │
│ │ Skonto: [2 % / 10 Tage] (aus Kunden-Profil) │
│ │ ─────────────────────────────────────────────────────────────────── │
│ │ Positionen: [+ Zeit] [+ Material] │
│ │ Nr. │ Bezeichnung │ Menge │ EP │ USt │ Summe │
│ │ 001 │ Monteurstunden Hannes Krüger │ 42,25 │ 68,00 │ 19% │ 2.873,00 │
│ │ 002 │ Monteurstunden Mehmet Yılmaz │ 45,25 │ 65,00 │ 19% │ 2.941,25 │
│ │ 003 │ Fahrtkostenpauschale 4 Tage │ 4,00 │ 45,00 │ 19% │ 180,00 │
│ │ 004 │ Material lt. Liste Anlage 1 │ 1,00 │812,40 │ 19% │ 812,40 │
│ │ ─────────────────────────────────────────────────────────────────── │
│ │ Zwischensumme netto 6.806,65 € USt 19 % 1.293,26 € Brutto 8.099,91│
│ │ │
│ │ [Entwurf speichern] [Vorschau PDF] [🔓 Freigeben & Versenden]│
└──────────┴─────────────────────────────────────────────────────────────────────┘

6.2 Web — Pflichtfeld-Prüfung §14 UStG (Grenzfall)

Abschnitt betitelt „6.2 Web — Pflichtfeld-Prüfung §14 UStG (Grenzfall)“
┌────────────────────────────────────────────────────────────────────────────────┐
│ 🔴 Freigabe blockiert — §14 Abs. 4 UStG │
├────────────────────────────────────────────────────────────────────────────────┤
│ Diese Rechnung kann nicht freigegeben werden. Es fehlen Pflichtangaben: │
│ │
│ • USt-Identifikationsnummer des Leistungsempfängers (B2B-EU-Umsatz) │
│ • Leistungsdatum bei Position 004 (Material lt. Liste Anlage 1) │
│ • Steuernummer oder USt-ID des Rechnungsausstellers │
│ │
│ Die Rechnungsnummer wird erst bei erfolgreicher Freigabe vergeben │
│ (GoBD-konform, lückenlos). │
│ │
│ [Zurück zum Entwurf] [Schließen]│
└────────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────────┐
│ Rechnungen · Offene Posten · Stand 19.04.2026 [Periode ▾] [Export CSV ⤓] │
├────────────────────────────────────────────────────────────────────────────────┤
│ KPI · Offene Summe: 42.187,53 € · 0-30 T: 22.840 € · 31-60 T: 12.470 € · >60: 6.877│
│ │
│ ☐ RE-Nr. Kunde Datum Fällig Alter Offen │
│ ☐ RE-2026-0147 Meiergroup GmbH 19.04.2026 19.05.2026 0 8.099,91 │
│ ☐ RE-2026-0139 Bauhof Köln KG 28.03.2026 27.04.2026 22 4.590,00 │
│ ☐ RE-2026-0134 Schmidt Immobilien 14.03.2026 13.04.2026 36* 2.880,00 │ 🟡 1.Mahnung fällig
│ ☐ RE-2026-0121 Weber & Partner GbR 02.02.2026 04.03.2026 76* 6.877,53 │ 🔴 2.Mahnung überfällig
│ │
│ [Zahlungseingang buchen] [Mahnvorschlag anzeigen] [An DATEV (EXTF) senden] │
└────────────────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────────┐
│ Mahnvorschlag · 19.04.2026 │
├────────────────────────────────────────────────────────────────────────────────┤
│ 4 Vorschläge — Buchhaltung entscheidet pro Zeile │
│ │
│ ☑ RE-2026-0134 Schmidt Immobilien Stufe 1 Erinnerung 2.880,00 + 0,00 Geb.│
│ ☑ RE-2026-0128 Hauser Bau GmbH Stufe 2 1. Mahnung 1.450,00 + 5,00 Geb.│
│ ☑ RE-2026-0121 Weber & Partner GbR Stufe 3 2. Mahnung 6.877,53 + 10,00 + Zins│
│ ☐ RE-2026-0118 (Stundung vereinbart) Stufe 0 — — │
│ │
│ Verzugszinsen Weber & Partner (B2B): 9 % + Basiszinssatz 3,62 % = 12,62 % p.a.│
│ auf 6.877,53 € × 76 Tage / 360 = 183,11 € │
│ │
│ [Abbrechen] [3 Mahnungen senden]│
└────────────────────────────────────────────────────────────────────────────────┘

6.5 Mobile — Rechnungs-Status im Projekt (read-only)

Abschnitt betitelt „6.5 Mobile — Rechnungs-Status im Projekt (read-only)“
┌────────────────────────────────┐
│ ← Projekt · Sanierung Heizung │
├────────────────────────────────┤
│ 🛡 GoBD · DSGVO │
│ │
│ Meiergroup GmbH │
│ Baustelle Lehrer Allee 7 │
│ │
│ ─── Rechnungen ─── │
│ │
│ RE-2026-0147 19.04.2026 │
│ 8.099,91 € [Offen] 📄 │
│ │
│ RE-2026-0128 28.02.2026 │
│ 4.590,00 € [Bezahlt] 📄 │
│ │
│ Offen gesamt: 8.099,91 € │
│ │
├────────────────────────────────┤
│ [Zeit] [Plan] [Doku] [Mehr] │
└────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────────┐
│ Stornierung RE-2026-0147 │
├────────────────────────────────────────────────────────────────────────────────┤
│ Achtung · GoBD-Unveränderlichkeit │
│ Die Original-Rechnung wird NICHT gelöscht. Sie erstellen eine │
│ Korrekturrechnung (Storno-Beleg), die auf die Original-Nr. verweist. │
│ │
│ Stornogrund (Pflichtfeld): │
│ ┌────────────────────────────────────────────────────────────────────────────┐ │
│ │ Positionsmenge Pos. 004 war falsch (812,40 € statt 612,40 €). Neue │ │
│ │ Rechnung mit korrekter Menge folgt. │ │
│ └────────────────────────────────────────────────────────────────────────────┘ │
│ │
│ Wird erzeugt: Storno-Beleg SK-2026-0003 · 19.04.2026 │
│ Verweist auf: RE-2026-0147 │
│ Beträge negativ, USt negativ │
│ │
│ [Abbrechen] [Storno erzeugen] │
└────────────────────────────────────────────────────────────────────────────────┘

apps/api/src/db/schema/invoices.ts
export const invoiceNumberSeriesTable = pgTable('invoice_number_series', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
kind: text('kind').notNull(), // 'invoice' | 'storno' | 'mahnung' | 'credit_note'
fiscalYear: integer('fiscal_year').notNull(), // 2026
prefix: text('prefix').notNull(), // 'RE-2026-' | 'SK-2026-' | 'MA-2026-'
nextNumber: integer('next_number').notNull(), // vergibt per atomic UPDATE ... RETURNING
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
}, (t) => ({
tenantKindYear: uniqueIndex('invoice_series_unique').on(t.tenantId, t.kind, t.fiscalYear),
}));
export const invoicesTable = pgTable('invoices', {
id: uuid('id').primaryKey().defaultRandom(), // UUID v7 für Idempotenz
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
invoiceNumber: text('invoice_number'), // NULL solange Entwurf, gesetzt bei Freigabe
kind: text('kind').notNull(), // 'invoice' | 'storno' | 'mahnung' | 'credit_note' | 'recurring_template'
customerId: uuid('customer_id').notNull().references(() => customersTable.id),
projectId: uuid('project_id').references(() => projectsTable.id),
status: text('status').notNull(), // 'draft' | 'issued' | 'sent' | 'partial' | 'paid' | 'overdue' | 'stornoed' | 'cancelled'
issueDate: date('issue_date'), // Rechnungsdatum (§14 UStG)
serviceFromDate: date('service_from_date'), // Leistungsdatum von (§14 UStG)
serviceToDate: date('service_to_date'), // Leistungsdatum bis
dueDate: date('due_date'), // Fälligkeit
netAmount: numeric('net_amount', { precision: 12, scale: 2 }).notNull().default('0.00'),
vatAmount: numeric('vat_amount', { precision: 12, scale: 2 }).notNull().default('0.00'),
grossAmount: numeric('gross_amount', { precision: 12, scale: 2 }).notNull().default('0.00'),
paidAmount: numeric('paid_amount', { precision: 12, scale: 2 }).notNull().default('0.00'),
skontoPercent: numeric('skonto_percent', { precision: 4, scale: 2 }),
skontoDays: integer('skonto_days'),
skontoDeadline: date('skonto_deadline'), // vorgerechnet bei Freigabe
paymentTermsDays: integer('payment_terms_days').notNull().default(30),
currency: text('currency').notNull().default('EUR'),
reverseCharge: boolean('reverse_charge').notNull().default(false), // §13b UStG
eInvoiceFormat: text('e_invoice_format'), // 'xrechnung_3_0' | 'zugferd_2_3' | 'pdf_only'
pdfS3Key: text('pdf_s3_key'), // S3 Object Lock Compliance-Mode
xmlS3Key: text('xml_s3_key'), // XRechnung-XML bei V1
stornoRef: uuid('storno_ref').references((): AnyPgColumn => invoicesTable.id), // bei Storno-Beleg: Verweis auf Original
cancellationReason: text('cancellation_reason'), // bei Storno Pflicht
recurringParentId: uuid('recurring_parent_id').references((): AnyPgColumn => invoicesTable.id),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
createdBy: uuid('created_by').notNull().references(() => usersTable.id),
issuedAt: timestamp('issued_at', { withTimezone: true }), // Freigabe-Zeitpunkt
issuedBy: uuid('issued_by').references(() => usersTable.id),
// GoBD Hash-Chain (append-only nach Freigabe)
hashPrev: bytea('hash_prev'),
hashSelf: bytea('hash_self'),
}, (t) => ({
tenantIdx: index('invoice_tenant_idx').on(t.tenantId, t.issueDate.desc()),
numberIdx: uniqueIndex('invoice_number_unique').on(t.tenantId, t.invoiceNumber).where(sql`invoice_number IS NOT NULL`),
customerIdx: index('invoice_customer_idx').on(t.tenantId, t.customerId),
statusIdx: index('invoice_status_idx').on(t.tenantId, t.status).where(sql`status IN ('issued','sent','partial','overdue')`),
}));
export const invoiceItemsTable = pgTable('invoice_items', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull(),
invoiceId: uuid('invoice_id').notNull().references(() => invoicesTable.id, { onDelete: 'cascade' }),
position: integer('position').notNull(),
description: text('description').notNull(),
quantity: numeric('quantity', { precision: 10, scale: 3 }).notNull(),
unit: text('unit').notNull(), // 'h' | 'Stk' | 'm2' | 'pauschal'
unitPrice: numeric('unit_price', { precision: 10, scale: 2 }).notNull(),
vatRate: numeric('vat_rate', { precision: 4, scale: 2 }).notNull(), // 19.00 / 7.00 / 0.00
lineTotal: numeric('line_total', { precision: 12, scale: 2 }).notNull(),
sourceKind: text('source_kind'), // 'time_entry' | 'material' | 'aufmass' | 'expense' | 'manual'
sourceId: uuid('source_id'), // optional, für Rückverfolgung auf Zeit-/Material-/Aufmaß-/Spesen-Buchung
serviceDate: date('service_date'), // §14 Abs. 4 Nr. 6 UStG — Leistungsdatum pro Position
accountSkr: text('account_skr'), // z. B. '8400' Erlöse 19% SKR03 / '4400' SKR04
costCenter: text('cost_center'),
});
export const invoicePaymentsTable = pgTable('invoice_payments', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull(),
invoiceId: uuid('invoice_id').notNull().references(() => invoicesTable.id),
paymentDate: date('payment_date').notNull(),
amount: numeric('amount', { precision: 12, scale: 2 }).notNull(),
method: text('method').notNull(), // 'bank_transfer' | 'sepa' | 'cash' | 'card' | 'skonto_deduction' | 'sepa_return'
reference: text('reference'), // Bankauszug-Referenz
recordedBy: uuid('recorded_by').notNull().references(() => usersTable.id),
recordedAt: timestamp('recorded_at', { withTimezone: true }).notNull().defaultNow(),
hashPrev: bytea('hash_prev'),
hashSelf: bytea('hash_self'),
});
export const dunningRunsTable = pgTable('dunning_runs', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull(),
runDate: date('run_date').notNull(),
runBy: uuid('run_by').notNull().references(() => usersTable.id),
itemsTotal: integer('items_total').notNull(),
itemsSent: integer('items_sent').notNull(),
});
export const dunningNoticesTable = pgTable('dunning_notices', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull(),
runId: uuid('run_id').references(() => dunningRunsTable.id),
invoiceId: uuid('invoice_id').notNull().references(() => invoicesTable.id),
dunningNumber: text('dunning_number').notNull(), // 'MA-2026-0042'
level: integer('level').notNull(), // 1, 2, 3
sentDate: date('sent_date').notNull(),
feeAmount: numeric('fee_amount', { precision: 10, scale: 2 }).notNull().default('0.00'),
interestAmount: numeric('interest_amount', { precision: 10, scale: 2 }).notNull().default('0.00'),
pdfS3Key: text('pdf_s3_key'),
});
export const recurringInvoiceTemplatesTable = pgTable('recurring_invoice_templates', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull(),
customerId: uuid('customer_id').notNull(),
cadence: text('cadence').notNull(), // 'daily'|'weekly'|'monthly'|'quarterly'|'yearly'
nextRunDate: date('next_run_date').notNull(),
active: boolean('active').notNull().default(true),
autoIssue: boolean('auto_issue').notNull().default(false),
templateJson: jsonb('template_json').notNull(), // Positionen + Bedingungen
});

RLS-Policy-Sketch:

ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY invoices_tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant_id')::uuid);
CREATE POLICY invoices_role_billing ON invoices
FOR ALL
USING (
tenant_id = current_setting('app.tenant_id')::uuid
AND current_setting('app.role') IN ('admin', 'buchhaltung')
);
CREATE POLICY invoices_role_manager_team ON invoices
FOR SELECT
USING (
tenant_id = current_setting('app.tenant_id')::uuid
AND current_setting('app.role') = 'manager'
AND project_id IN (
SELECT id FROM projects
WHERE manager_user_id = current_setting('app.user_id')::uuid
OR id IN (SELECT project_id FROM project_managers WHERE user_id = current_setting('app.user_id')::uuid)
)
);
-- Invoice-Nummer-Vergabe als atomische Funktion mit Advisory-Lock:
CREATE OR REPLACE FUNCTION assign_invoice_number(p_tenant uuid, p_kind text, p_fiscal_year int)
RETURNS text LANGUAGE plpgsql AS $$
DECLARE v_prefix text; v_next int;
BEGIN
PERFORM pg_advisory_xact_lock(hashtext(p_tenant::text || p_kind || p_fiscal_year::text));
UPDATE invoice_number_series
SET next_number = next_number + 1
WHERE tenant_id = p_tenant AND kind = p_kind AND fiscal_year = p_fiscal_year
RETURNING prefix, next_number - 1 INTO v_prefix, v_next;
IF NOT FOUND THEN RAISE EXCEPTION 'Kein Nummernkreis konfiguriert für %/%/%', p_tenant, p_kind, p_fiscal_year; END IF;
RETURN v_prefix || lpad(v_next::text, 4, '0');
END $$;

ER-Bezug. Referenziert tenants, users, customers (§3.4), projects (§3.4), time_entries (§3.1 für source_id bei Position-Kind time_entry), expenses (§3.6), aufmass_items (§4.4, V1).


Methode Pfad Auth-Scope Rate-Limit Idempotenz Beschreibung
POST /v1/kern/invoices invoices:write Standard Pflicht Entwurf anlegen
GET /v1/kern/invoices invoices:read:<scope> Standard Liste (Filter: Status, Kunde, Datum)
GET /v1/kern/invoices/{id} invoices:read:<scope> Standard Detail inkl. Positionen
PATCH /v1/kern/invoices/{id} invoices:write Standard Pflicht Entwurf ändern; gesperrt nach Freigabe
POST /v1/kern/invoices/{id}/issue invoices:issue Privileged Pflicht Freigabe → Nummernvergabe + PDF + Versand
POST /v1/kern/invoices/{id}/storno invoices:storno Privileged Pflicht Storno-Beleg erzeugen (inkl. Begründung)
POST /v1/kern/invoices/{id}/payments invoices:payment Standard Pflicht Zahlungseingang buchen
GET /v1/kern/invoices/open-items invoices:read:all Standard OP-Liste aggregiert
POST /v1/kern/invoices/dunning/propose invoices:dunning Privileged Pflicht Mahnvorschlagslauf generieren
POST /v1/kern/invoices/dunning/send invoices:dunning Privileged Pflicht Selektierte Mahnungen versenden
POST /v1/kern/invoices/recurring-templates invoices:recurring Standard Pflicht Wiederkehrende Vorlage anlegen
POST /v1/kern/invoices/{id}/export/datev-extf invoices:export Privileged Pflicht Buchungssatz an DATEV-Stapel (§3.10)
GET /v1/kern/invoices/{id}/pdf invoices:read:<scope> Standard PDF-Download (presigned S3-URL, 5 min gültig)
GET /v1/kern/invoices/{id}/xrechnung invoices:read:<scope> Standard XRechnung-XML (V1)

OpenAPI-Schema-Skizze:

paths:
/v1/kern/invoices/{id}/issue:
post:
operationId: issueInvoice
x-werkszeit-scope: invoices:issue
x-werkszeit-rate-limit: privileged
parameters:
- in: header
name: Idempotency-Key
required: true
schema: { type: string, format: uuid }
responses:
'200':
description: Rechnung freigegeben, Nummer vergeben, PDF erzeugt.
content:
application/json:
schema: { $ref: '#/components/schemas/Invoice' }
'409':
description: §14-UStG-Pflichtangabe fehlt ODER Idempotency-Konflikt
content:
application/problem+json:
schema: { $ref: '#/components/schemas/MissingMandatoryFieldsError' }

Webhook-Events:

  • invoice.issued
  • invoice.sent
  • invoice.paid / invoice.partial_paid
  • invoice.stornoed
  • invoice.dunning_sent (mit level: 1|2|3)
  • invoice.overdue (täglicher Tick nach Skonto-Ablauf bzw. nach Fälligkeit)

Profil Begründung Mechanik
Online-only Rechnungserstellung ist ein Admin-Vorgang auf dem Großbildschirm. Nummernvergabe muss atomar gegen Server geschehen. Offline-Rechnung mit lokal vergebener Nummer würde GoBD-Lückenlosigkeit bedrohen. UI blockt „Freigeben“ bei connectivity == none. Entwurf-Speicherung lokal im Browser-IndexedDB (Drift Web) für kurze Netz-Aussetzer ist erlaubt; beim Wiederherstellen der Verbindung wird der Entwurf an Server synchronisiert.

Mobile Ansicht (read-only Rechnungs-Status im Projekt) ist Best-effort-offline via Drift-Cache; OP-Liste und Mahnwesen sind Online-only.

Konflikt-Strategie. Nicht zutreffend auf Rechnungs-Ebene, weil keine konkurrierende Mobile-Erstellung. Bei Zahlungseingangs-Buchung (zwei Kolleg:innen buchen dieselbe Zahlung auf dieselbe Rechnung) → zweite Buchung wird 409-abgelehnt, wenn der Idempotency-Key identisch ist, oder als Teilzahlung akzeptiert (Summe darf Offenstand nicht überschreiten — sonst 422).

Foto-/Datei-Sync. PDF- und XML-Artefakte werden server-seitig erzeugt und auf S3 mit Object Lock abgelegt; Client holt presigned URLs.


Live-Validierung gegen folgende Pflichtangaben bei Freigabe (Entwurf darf unvollständig sein):

  1. Vollständiger Name und Anschrift des Rechnungsausstellers (Mandant-Stammdaten).
  2. Vollständiger Name und Anschrift des Leistungsempfängers (Kunde aus §3.4).
  3. Steuernummer oder USt-Identifikationsnummer des Rechnungsausstellers (§14 Abs. 4 Nr. 2).
  4. USt-Identifikationsnummer des Leistungsempfängers bei §3a Abs. 2 UStG, innergemeinschaftlicher Lieferung/Leistung oder Reverse Charge.
  5. Ausstellungsdatum (§14 Abs. 4 Nr. 3).
  6. Fortlaufende, einmalig vergebene Rechnungsnummer (§14 Abs. 4 Nr. 4) — per atomic assign_invoice_number() (§7).
  7. Menge und handelsübliche Bezeichnung je Position (§14 Abs. 4 Nr. 5).
  8. Leistungsdatum oder Leistungszeitraum (§14 Abs. 4 Nr. 6) — Pflicht pro Position bei unterschiedlichen Ausführungsdaten, sonst rechnungsweit.
  9. Nettoentgelt aufgegliedert nach Steuersätzen, USt-Satz und USt-Betrag oder Hinweis auf Steuerbefreiung (§14 Abs. 4 Nr. 7 und 8).
  10. Bei im Voraus vereinbarter Entgeltminderung: Hinweis darauf (§14 Abs. 4 Nr. 7 letzter Halbsatz) — das ist der Skonto-Hinweis. Beispiel-Text: „Bei Zahlung innerhalb 10 Tagen 2 % Skonto.“
  11. Bei Kleinunternehmerregelung (§19 UStG): Hinweis „Gemäß §19 UStG wird keine Umsatzsteuer erhoben“ — kein USt-Ausweis.
  12. Bei §13b UStG (Reverse Charge, Bauleistung): Hinweis „Steuerschuldnerschaft des Leistungsempfängers“ — kein USt-Ausweis, USt-ID des Empfängers Pflicht.
  13. Bei Storno-Beleg (§14 Abs. 4 Satz 1 Nr. 10 — Kennzeichnung als Korrektur): Rechnungsnummer und Datum der Original-Rechnung im Dokument.

Kleinbetragsrechnung (§33 UStDV, ≤ 250 € brutto): Ziffern 1, 3, 5, 7, 9 und Gesamtbrutto genügen; wir prüfen den reduzierten Satz.

Ein fehlendes Pflichtfeld produziert einen 409 Problem+JSON mit Liste der fehlenden Felder (keine textuelle Interpretation — maschinenlesbar).

EN-16931-Kernsemantik wird von MVP an persistiert (Feldnamen und Kardinalitäten gemäß EN 16931-1:2017+A1:2019). XRechnung 3.0 / ZUGFeRD 2.3 werden in V1 über das Mustang-Lambda (TECH-STACK §5.2, ADR-0002) erzeugt. Die Ausgangs-Pipeline wird am KoSIT-Validator geprüft; ein roter Validator-Befund blockt Versand. Peppol-AP-Anbindung: V2, für innereuropäische öffentliche Auftraggeber.

nicht zutreffend in diesem Feinkonzept. Abschlags- und Schlussrechnungen nach VOB/B §14 inkl. Kumulationslogik liegen im Bau-Feinkonzept (§3.5-Bau, Roadmap V1).

  • Art. 5 Datenminimierung: Kundenstamm führt nur die für Rechnungsstellung notwendigen Daten (Firmenname, Anschrift, USt-ID, Debitorennummer, Ansprechpartner, Bankverbindung). Keine Marketing-Attribute.
  • Art. 6 Abs. 1 lit. b (Vertrag) als Rechtsgrundlage für die Rechnungsdaten der eigenen Mandanten-Kunden. Für den B2B-Rechnungsempfänger ebenfalls lit. b (Vertragsanbahnung/Durchführung).
  • Art. 17 Löschung kollidiert mit §147 AO — Rechnungen unterliegen 10 Jahren Aufbewahrung. Strategie: Logisches Löschen unmöglich (GoBD). Ausschließlich Maskierung personenbezogener Felder nach gesetzlicher Aufbewahrungsfrist; bis dahin Zugriff technisch auf Buchhaltungs-Rolle beschränkt.
  • Art. 20 Datenexport: Rechnungs-JSON + PDF pro Betroffenem. Ein B2B-Kunde kann seinen kompletten Rechnungs-Bestand gegenüber dem Mandanten exportieren lassen.
  • Art. 30 Verarbeitungsverzeichnis: Eintrag „Ausgangsrechnungen — Rechtsgrundlage Vertrag + §147 AO — Aufbewahrung 10 Jahre — Kategorien: Name, Anschrift, USt-ID, Bankverbindung, Rechnungsinhalt“.
  • DSFA: nicht pflichtig (Standard-Buchhaltungsverarbeitung, keine Profiling-Elemente, keine besondere Kategorie nach Art. 9).

nicht zutreffend, weil die Rechnungsstellungs-UI ausschließlich von der Buchhaltungs-/Admin-Rolle im Web benutzt wird (FUNKTIONSUMFANG §3.7 Barrierefreiheit bezieht sich auf ESS-Flows). Trotzdem hält die UI die allgemeinen Semantics- und Kontrast-Regeln der Design-System-Komponenten (Werkszeit DS §Token, Pills, Table) ein.

nicht zutreffend. Rechnungsstellung ist keine Mitarbeiter-Leistungs- oder Verhaltenskontrolle.

  • §288 BGB / §247 BGB: Verzugszinsen B2B 9 % über Basiszinssatz, B2C 5 %. Basiszinssatz wird halbjährlich (01.01., 01.07.) aktualisiert als tenant-übergreifende Referenzdaten, Audit-pflichtig.
  • §286 BGB: Verzug tritt nach 30 Tagen ab Rechnungseingang auch ohne Mahnung ein (B2B). Unsere Mahn-Stufen sind konservativer (Erinnerung Tag 5, Mahnung 14, Mahnung 30), weil Handwerker-Kunden kulant agieren wollen.
  • §147 AO: Rechnungen, Buchungsbelege, Geschäftsbriefe 10 Jahre aufbewahren. Realisiert über S3 Object Lock Compliance-Mode mit retention_days = 3660.
  • §146 Abs. 4 AO: Keine Veränderung festgeschriebener Buchungen — spiegelt sich im Append-only-Status-Übergang issued → paid/stornoed wider.

# Szenario Erwartetes Verhalten
EC-01 §14-UStG-Pflichtangabe fehlt (USt-ID Empfänger bei Reverse Charge) Freigabe blockiert, 409 mit strukturiertem Fehler missing_mandatory_fields: [customer_vat_id]. Entwurf bleibt erhalten. Kein Nummernverbrauch.
EC-02 Skonto-Frist abgelaufen, Kunde zieht trotzdem 2 % Zahlungseingang wird als Teilzahlung erfasst (Nettobetrag – Skonto). Offener Restbetrag bleibt stehen und wandert in Altersstufe >60 d. Buchhaltung entscheidet: verzicht (manuell als zweite Teilzahlung vom Typ skonto_deduction_late mit Begründung buchen) oder Nachforderung (Mahnvorschlag Stufe 1 auf Restbetrag).
EC-03 Storno mit Korrekturrechnung Original bleibt unverändert. Automatisch generierter Storno-Beleg SK-2026-NNNN mit negativen Beträgen, Verweis bezieht_sich_auf, Pflicht-Begründung. DATEV-Export enthält Storno als Gegenbuchung, nicht Stornierung.
EC-04 Rechnungsnummer-Lücke durch Absturz zwischen UPDATE next_number und INSERT invoice Advisory-Lock + Transaction: Entweder beide passieren (Commit) oder keine (Rollback mit Nummer-Rücksetzung). Zusätzlich Nightly-Job prüft auf Lücken und alarmiert Audit-Channel.
EC-05 Großmenge: Manager fordert OP-Liste für 3 Jahre als CSV (120.000 Zeilen) Hintergrund-Export via BullMQ-Job. Frau Müller bekommt Download-Link per E-Mail, nicht Synchron-Call. UI-Timeout nach 5 s → „Export läuft, Ergebnis im Postfach“.
EC-06 Multi-Tenant-Bleed-Versuch: User wechselt aktiven Tenant während Rechnungs-Freigabe RLS erlaubt nur Zugriff auf Rechnungen des aktiven Tenants. Session-Token trägt tenant_id. Wechsel → erneuter Login-Flow erzwungen. Laufende Freigabe bricht mit 401 ab.
EC-07 Wiederkehrende Rechnung: Kunde wurde in Zwischenzeit inaktiv gesetzt Cron-Job überspringt die Vorlage, Log-Eintrag „Recurring skipped: customer inactive“. Buchhaltung sieht Badge in Vorlagenliste.
EC-08 SEPA-Rückläufer nach bereits versandter Mahnung Stufe 1 Zahlung wird als negative Teilzahlung erfasst (method = 'sepa_return'), Offenstand steigt. Mahn-Eskalation wird nicht automatisch zurückgesetzt — Buchhaltung entscheidet manuell, ob Stufe 1 erneut greift oder direkt Stufe 2. Audit-Log dokumentiert.
EC-09 Kleinunternehmer-Mandant (§19 UStG) versucht USt-Ausweis UI zeigt USt-Spalten als deaktiviert. PDF enthält Pflicht-Hinweis. vatRate = 0.00 erzwungen, Validator meldet Inkonsistenz, wenn Position trotzdem USt trägt.
EC-10 Zeitbuchung wurde nach Rechnungsstellung storniert (z. B. falscher Mitarbeiter) Positions-Zeile bleibt in Rechnung bestehen (GoBD). Kennzeichen am Beleg „Quelle storniert — siehe Begründung“. Buchhaltung kann Rechnung stornieren und neu ausstellen.

Funktionalität: Einfache Rechnungsstellung §14 UStG-konform
Hintergrund:
Angenommen ein Tenant "shk-gebruder-schmidt" mit aktivem Modul-Gate "module.kern.billing"
Und ein Nummernkreis "RE-2026-" mit Stand 147 für Geschäftsjahr 2026
Und ein Kunde "Meiergroup GmbH" mit Debitor 10042, UStID "DE123456789"
Und ein Skonto-Profil "Standard 2 % 10 Tage, netto 30 Tage" bei Meiergroup
Und eine Buchhalterin "Frau Müller" mit Rolle "buchhaltung" und Scope "all"
Szenario: Happy Path — Rechnung aus KW-16-Stunden an Meiergroup freigeben
Angenommen 42,25 Monteurstunden Hannes K. und 45,25 Stunden Mehmet Y. auf Projekt
"Sanierung Heizungsanlage Bgm. Müller-Schule" sind vom Manager freigegeben
Wenn Frau Müller im Projekt "Rechnung aus freigegebenen Buchungen" wählt
Und die Positionen übernimmt, Leistungszeitraum 12.-18.04.2026 setzt
Und "Freigeben & Versenden" klickt
Dann wird die Rechnungsnummer "RE-2026-0148" atomar vergeben
Und das PDF liegt im S3 mit Object Lock (Retention 10 Jahre)
Und die E-Mail an "[email protected]" wird per AWS SES verschickt
Und der Nettobetrag ist 6.806,65 €, USt 19 % 1.293,26 €, Brutto 8.099,91 €
Und Fälligkeit ist der 19.05.2026, Skonto-Frist der 29.04.2026
Und ein Audit-Log-Eintrag "invoice.issued" mit Hash-Chain-Verweis wird erzeugt
Und ein Webhook-Event "invoice.issued" wird dispatched
Szenario: Grenzfall — §14 UStG-Pflichtangabe fehlt (USt-ID Empfänger bei Reverse Charge)
Angenommen die Rechnung ist als Bauleistung §13b UStG markiert
Und die USt-ID der Meiergroup ist im Kundenstamm leer
Wenn Frau Müller "Freigeben & Versenden" klickt
Dann antwortet die API mit Status 409 und Fehler-Code "missing_mandatory_fields"
Und der Fehler listet "customer_vat_id" und "reverse_charge_note"
Und die Rechnungsnummer bleibt unvergeben (Stand 147)
Und es wird KEIN PDF erzeugt und KEINE E-Mail versandt
Und der Entwurf bleibt im Status "draft" erhalten
Szenario: Grenzfall — Skonto-Frist abgelaufen, Kunde zieht trotzdem
Angenommen die Rechnung RE-2026-0148 wurde am 19.04.2026 ausgestellt
Und die Skonto-Frist war der 29.04.2026
Und am 05.05.2026 geht eine Zahlung von 7.937,91 € ein (entspricht Brutto – 2 % Skonto)
Wenn Frau Müller den Zahlungseingang über "Zahlung buchen" erfasst
Dann wird die Teilzahlung als "bank_transfer" über 7.937,91 € verbucht
Und der Offenstand beträgt 162,00 € (8.099,91 € – 7.937,91 €)
Und die Rechnung wandert in Status "partial"
Und im Mahnvorschlag am 20.05.2026 (30+ Tage) erscheint der Restbetrag mit Stufe 1
Und es gibt keine automatische Skonto-Anerkennung nachträglich
Szenario: Grenzfall — Storno mit Korrekturrechnung
Angenommen die Rechnung RE-2026-0148 wurde am 19.04.2026 freigegeben
Und Frau Müller stellt am 22.04.2026 fest, dass Position 004 (Material) falsch berechnet war
Wenn sie "Stornieren" klickt und als Begründung eingibt
"Positionsmenge Pos. 004 war falsch (812,40 € statt 612,40 €). Neue Rechnung folgt."
Dann wird automatisch der Storno-Beleg "SK-2026-0003" erzeugt
Und der Storno-Beleg verweist im Feld "bezieht_sich_auf" auf "RE-2026-0148"
Und alle Beträge des Storno-Belegs stehen mit negativem Vorzeichen
Und das Original RE-2026-0148 bleibt im Zustand "stornoed" UND im GoBD-Archiv unverändert sichtbar
Und ein Audit-Log "invoice.stornoed" mit Begründung wird erzeugt
Und der DATEV-Export enthält beide Belege als Gegenbuchungen
Szenario: Grenzfall — Multi-Tenant-Isolation beim Rechnungs-Abruf
Angenommen ein zweiter Tenant "musterbetrieb-maler" mit RE-2026-0001
Wenn Frau Müller (Tenant shk-gebruder-schmidt) per API
GET /v1/kern/invoices/{uuid-of-musterbetrieb-invoice} aufruft
Dann antwortet die API mit 404 (nicht 403, um Existenz nicht zu offenbaren)
Und es wird KEIN Audit-Log-Eintrag in Tenant "musterbetrieb-maler" erzeugt

  • U-01 Pflichtfeld-Validator §14 UStG — Tabelle von 24 Permutationen (Normal/Kleinbetrag/Kleinunternehmer/Reverse Charge × alle Felder).
  • U-02 Skonto-Deadline-Berechnung: issueDate + skontoDays Bank-Tag-Logik (Wochenende springt auf Montag — Verträge können aber arbeiten „Kalendertage“; Default Kalendertage, konfigurierbar).
  • U-03 Property-based (fast-check): Rundung Netto×USt=Brutto in Cent-Genauigkeit; keine Summen-Abweichung ±0,01 €.
  • U-04 Verzugszinsen-Berechnung: Basiszinssatz + 9 % B2B, zinstage nach §187 BGB (Ereignistag zählt nicht).
  • U-05 Nummernkreis-Atomizität: 1.000 parallele assign_invoice_number-Aufrufe im Worker-Pool → exakt 1.000 eindeutige aufeinanderfolgende Nummern.
  • W-01 Rechnungs-Formular — Freigabe-Button disabled bei fehlenden Pflichtfeldern, Tooltip listet fehlende Felder.
  • W-02 OP-Liste — Altersstruktur-Summen in Footer stimmen mit Zeilen-Summen überein (Drilldown-Konsistenz).
  • W-03 Golden-Test für PDF-Vorschau-Overlay de-DE + Light/Dark.
  • I-01 Multi-Tenant-Isolation: Tenant A erzeugt Rechnung, Tenant-B-User ruft sie per ID ab → 404; kein Bleed in OP-Liste, kein Bleed im Audit-Log.
  • I-02 RLS invoices_role_manager_team: Manager sieht nur Rechnungen seiner Projekte; Rechnung eines fremden Projekts liefert 404.
  • I-03 Nummernvergabe unter 200 parallelen /issue-Calls → keine Duplikate, keine Lücken (DB-Advisory-Lock-Test).
  • I-04 Zahlungseingang-Idempotenz: Derselbe Idempotency-Key liefert 200 (nicht 409) mit identischem Payload.
  • I-05 SEPA-Rückläufer-Pfad: Payment sepa_return erhöht Offenstand korrekt.
  • E-01 Happy Path: Manager erzeugt Vorschlag → Buchhaltung gibt frei → PDF erscheint → E-Mail-SMTP-Mock erhält Mail (Chromium + Firefox + WebKit).
  • E-02 Storno-Pfad: Original-Status bleibt sichtbar in archivierter Ansicht.
  • E-03 Visual Regression: Rechnungs-Formular, OP-Liste, Mahnvorschlag-Dialog.
  • E-04 Accessibility: axe-core ohne Critical/Serious auf Formular und OP-Liste.
  • C-01 Hash-Chain-Integrität: Manuell manipulierter netAmount lässt Chain brechen → Detection-Test meldet.
  • C-02 DATEV-EXTF-Validator akzeptiert die aus einer Rechnung erzeugte EXTF_Buchungsstapel.csv.
  • C-03 KoSIT-Validator (V1): erzeugte XRechnung-3.0-XML bestehen Schematron.
  • C-05-derived §14-UStG-Pflichtfeld-Matrix als Property-based Tests (alle Kombinationen aus Inland/EU-B2B/Drittland/Reverse Charge/Kleinbetrag/Kleinunternehmer).
  • E-01 im 20×-Lokal-Rerun grün; Stripe-Sandbox-Mock fixiert (nicht echte Stripe-API).

Nicht-Ziel Begründung
Eingangsrechnungs-Verarbeitung / ZUGFeRD-Inbound Liegt in V1.5 als separates Feature; Nicht-Scope dieses Feinkonzepts.
USt-Voranmeldung / ELSTER-Übergabe Liegt beim Steuerberater via DATEV (§3.10). Werkszeit liefert Daten, meldet nicht selbst.
Mehrstufige Angebotsprozesse mit GAEB-LV Im Bau-Feinkonzept §4.3 (V1). Hier: freie Positionen.
Factoring / Forderungsverkauf Kein Referenzkunde (DOR §1.1.1).
Abschlags-/Schlussrechnungslogik nach VOB/B §14 mit Kumulationskette Im Bau-Feinkonzept §3.5-Bau (V1).
Automatischer Zahlungsabgleich mit Bankauszug (HBCI/FinTS) V2+, abhängig von Referenzkunde mit DATEV Unternehmen Online.
Anlagenbuchhaltung, Kostenrechnung, BWA Bleibt FiBu-Kern (DATEV/sage/lexoffice).
Stripe Subscriptions Wiederkehrend ist rein rechnungslogisch — Zahlungs-Automation kommt erst mit SEPA-Lastschrift-Mandaten in V2.
Mahn-Eskalation nach §688 ZPO (gerichtlich) Schnittstelle zu Inkasso in V2+; MVP/V1 bleibt bei 3 Stufen außergerichtlich.
Leistungsgutscheine, Rabattcodes, Affiliate-Tracking Nicht im Zielmarkt B2B-Handwerk (FUNKTIONSUMFANG §10).

Risiko / Annahme Impact Wahrscheinlichkeit Gegenmaßnahme
XRechnung-Schematron-Regeln ändern sich (KoSIT updated regelmäßig) mittel hoch Nightly-Compliance-Job validiert gegen aktuelle KoSIT-Version; Release-Notes überwachen.
Mandant verlangt eigenes Rechnungs-PDF-Layout (Logo, Farbe, Position) mittel hoch Typst-Templates sind pro Tenant überschreibbar; Fallback auf Standard-Template.
Basiszinssatz-Update wird verpasst (halbjährlich) niedrig niedrig Cron-Reminder an Admin-Rolle 14 Tage vor 01.01./01.07.
Rechnungsnummer-Kreissprung bei Mandanten-Fusion (2 Tenants → 1) hoch sehr niedrig Dokumentierter Migrationspfad in ADR; Nummernkreise werden nicht fusioniert, sondern ein neuer Kreis mit Zeitraum startet.
Annahme: Buchhaltung ist Online bei Freigabe niedrig hoch UI-Block bei Offline; Dokumentation betont Online-only.
§14 UStG wird durch Wachstumschancengesetz weiter verschärft mittel mittel Field-Matrix ist datengetrieben (Tabelle), nicht im Code hartkodiert.
Falsche SKR-Kontierung pro Leistungsart → DATEV-Export fehlerhaft hoch mittel Admin-Mapping (§F-A-07) + DATEV-Validator in C-02; Sample-Export vor Produktivgang pflicht.

  • Vorbedingung: kern/04-projekte-kunden (Debitor-Stammdaten), kern/03-zeiterfassung (Buchungsfreigaben), kern/11-audit-log (Append-only-Backbone), kern/12-auth (Rollen buchhaltung, admin).
  • Schnittstelle zu:
    • kern/06-spesen-reisemanagement — weiterberechenbare Spesen werden als Positionen gezogen (Tag chargeable = true).
    • kern/10-datev-integration — jede freigegebene Rechnung erzeugt einen EXTF-Buchungsstapel-Eintrag (Erlöskonto je Leistungsart, USt-Konto je Satz, Debitorenkonto je Kunde).
    • handwerk/05-rechnungsstellung-bau (V1) — XRechnung/ZUGFeRD, Abschlags-/Schlussrechnungs-Logik, Sicherheitseinbehalt.
  • Wird konsumiert von: kern/10-datev-integration (Beleg-Export), reports/03-deckungsbeitrag (Projekt-Ist-Umsatz), kern/09-dashboard (KPI-Karten OP-Summe).

Status: TBD — zu klären mit Sales / PO.

DOR §1.1.1 verlangt einen namentlich dokumentierten Design-Partner.

Kandidaten-Profile für Sales-Recherche:

  • SHK-Betrieb 40–80 MA in NRW/Bayern mit interner Buchhaltung (keine externe Steuerkanzlei für Tagesgeschäft), DATEV-erfahren, monatlich 60–150 Ausgangsrechnungen.
  • Maler-/Stuck-Betrieb 20–40 MA mit hohem Anteil Privatkunden (Zahlungsmoral-Pain-Point, mehrstufiges Mahnwesen wertvoll).
  • Gebäudetechnik-Mittelstand mit Wartungsvertrags-Anteil (Wiederkehrende Rechnungen valuable).

Validierungs-Fragen:

  1. Wie schreiben Sie heute Ihre Ausgangsrechnungen (Tool, Zeitaufwand pro Rechnung)?
  2. Was kostet Sie das heute pro Monat, welche Nebenkosten (Briefporto, Druckerwartung, Excel-Pflege)?
  3. Wie oft passieren Nummernlücken, Tippfehler, falsche USt-Angaben? Wer merkt das wann?
  4. Wie steuern Sie heute die OP-Liste und das Mahnwesen? Welches DSO haben Sie?
  5. Welche Wiederkehrenden (Wartungsverträge, Pauschalen) laufen bei Ihnen monatlich? Wie viele?
  6. Würden Sie 30 Tage gegen 30 % Rabatt in den Beta-Slot gehen?

Build-Sequenz:

  1. Datenmodell + Nummernkreise + RLS + Multi-Tenant-Test. Das Nummernkreis-Modell ist der schmerzhafteste Moment für Nacharbeit — zuerst sauber bauen. GoBD-Hash-Chain ab Commit 1.
  2. §14-UStG-Validator als eigene, datengetriebene Komponente (Tabelle Inland/EU/Drittland × Fall × Pflichtfeld). Tests bevor UI.
  3. Entwurf-CRUD + Positions-Verwaltung (manuelle Positionen zuerst, dann Zeit-Import).
  4. PDF-Pipeline über Typst. Template mit Firmenkopf-Austausch pro Tenant.
  5. Freigabe-Flow + Nummernvergabe + S3-Object-Lock-Upload.
  6. OP-Liste + Zahlungseingang.
  7. Storno + Korrekturrechnung (vor Mahnwesen, weil einfacher).
  8. Mahnwesen 3-stufig.
  9. Wiederkehrende Rechnungen.
  10. DATEV-EXTF-Kopplung (§3.10 übernimmt, aber Format-Kontrakt definiert dieses Feinkonzept).
  11. XRechnung/ZUGFeRD über Mustang-Lambda (V1).

Risiko-Reihenfolge (erst absichern, wenn Zeitnot):

  • Nicht verhandelbar: Nummernkreis-Atomizität, §14-UStG-Vollständigkeit, GoBD-Hash-Chain, Multi-Tenant-Isolation, Storno-Unveränderlichkeit.
  • Erste Streich-Kandidaten: Wiederkehrende Rechnungen (V1), XRechnung (V1), Mahnwesen-Auto-Versand (Manuell-Auslösung reicht MVP).

Stop-the-Bus-Triggers:

  • Rechnungsnummern-Duplikat oder -Lücke im Test.
  • Hash-Chain-Bruch nach Freigabe.
  • Multi-Tenant-Bleed in OP-Liste.
  • §14-UStG-Pflichtfeld wird in Produktion akzeptiert, obwohl leer.
  • DATEV-Validator wirft Rejected-Status auf produktivem EXTF-Output.

Was uns 2027 dankbar macht:

  • Der Nummernkreis als eigene Tabelle mit assign_invoice_number()-Funktion trägt später SEPA-Lastschrift-Mandats-Kreise, Ausgangsbestellungs-Kreise, Angebots-Kreise. Einmal richtig gemacht, nie wieder angefasst.
  • Der §14-UStG-Validator als datengetriebene Matrix (nicht hartcodiert) verkraftet jede Wachstumschancengesetz-Novelle ohne Code-Änderung.
  • Die Trennung issueDate vs. serviceDate auf Positions-Ebene (nicht Rechnungs-Ebene) trägt Teilleistungs- und Bauabrechnungs-Szenarien ohne Schema-Bruch.

Letzte Aktualisierung: 2026-04-19.

Für Entwickler — API-Endpoints20
MethodePfadAuthZweck
GET/v1/kern/rechnungenbearerAuthRechnungen auflisten (cursor-paginiert)
POST/v1/kern/rechnungenbearerAuthRechnung anlegen (Entwurf)
GET/v1/kern/rechnungen/{id}bearerAuthRechnung-Detail
PATCH/v1/kern/rechnungen/{id}bearerAuthRechnung-Header aktualisieren
POST/v1/kern/rechnungen/{id}/freigebenbearerAuthRechnung freigeben
GET/v1/kern/rechnungen/{id}/pdfbearerAuthRechnung als PDF
GET/v1/kern/rechnungen/{id}/positionenbearerAuthPositionen auflisten
POST/v1/kern/rechnungen/{id}/positionenbearerAuthPosition anlegen
DELETE/v1/kern/rechnungen/{id}/positionen/{posId}bearerAuthPosition löschen
PATCH/v1/kern/rechnungen/{id}/positionen/{posId}bearerAuthPosition aktualisieren
GET/v1/kern/rechnungen/{id}/skonto-previewbearerAuthSkonto-Vorschau
POST/v1/kern/rechnungen/{id}/stornierenbearerAuthRechnung stornieren
POST/v1/kern/rechnungen/{id}/storno-korrekturbearerAuthStorno-Korrektur-Rechnung erzeugen
POST/v1/kern/rechnungen/{id}/versendenbearerAuthRechnung versenden
GET/v1/kern/rechnungen/{id}/xrechnung.xmlbearerAuthXRechnung-Export (UBL/CII)
GET/v1/kern/rechnungen/{id}/zahlungseingaengebearerAuthZahlungseingänge auflisten
POST/v1/kern/rechnungen/{id}/zahlungseingaengebearerAuthZahlungseingang buchen
GET/v1/kern/rechnungen/{id}/zugferd.pdfbearerAuthZUGFeRD-Hybrid-PDF/A-3
GET/v1/kern/rechnungen/{id}/zugferd.xmlbearerAuthZUGFeRD-CII-XML-Export
GET/v1/kern/rechnungen/op-listebearerAuthOffene-Posten-Liste (US-03)