Zum Inhalt springen

Spesen- und Reisemanagement (Beleg-OCR, BMF-Pauschalen, Bewirtung, Kilometer, Weiterberechnung)

Beta — in Erprobung

Scope-Notiz: Werkszeit erfasst Belege, Reisekosten, Bewirtung und Kilometer-Spesen am Entstehungsort, prüft sie gegen §4 Abs. 5 EStG und die BMF-Reisekostenpauschalen, genehmigt sie im Manager-Workflow und übergibt Buchungssätze an §3.10 (DATEV-Export). Werkszeit ersetzt keine Lohnbuchhaltung, keine Kreditkarten-Abrechnung und keine Kassenbuchführung.


feature_id: kern/06-spesen-reisemanagement
title: Spesen- und Reisemanagement (Beleg-OCR, BMF-Pauschalen, Bewirtung, Kilometer, Weiterberechnung)
funktionsumfang_ref: §3.6
roadmap_horizont: MVP # Beleg + BMF-Pauschalen + Bewirtung Day 1; Kreditkarten-Integration V2
plattformen:
mobile: vollständig # Hauptpfad: Beleg vor Ort fotografieren
web: mit-Bulk # Manager-Genehmigung, DATEV-Export
desktop: aus
owner_rolle: Mitarbeiter # Erfassung; Manager genehmigt; Buchhaltung exportiert
modul_gate_flag: module.kern.expenses
compliance_flags:
gobd: true # Beleg ist steuerrelevanter Geschäftsbeleg, 10 Jahre §147 AO
arbzg: false # Abwesenheitszeit zählt als Arbeitszeit, aber ArbZG-Logik liegt in §3.1
vob: false
dsgvo: true # Teilnehmerliste Bewirtung, Reisedaten, Standort
bfsg: true # Mobile-Erfassung ist ESS-Flow; A11y-Pflicht
betrvg: true # Standort-Erfassung (GPS) ist Leistungs-/Verhaltenskontrolle → BR-Freischaltung
stvg: false
weitere: ["§4 Abs. 5 EStG (Bewirtung)", "§9 Abs. 4a EStG + BMF-Schreiben 2026 (Verpflegungs­mehraufwand)", "§8 Abs. 2 EStG (Sachbezug)", "§3 Nr. 16 EStG", "SteuerBescheinigungsV", "UStG §15 (Vorsteuer-Abzug bei Kleinbetrag/Gesamtbeleg)", "§33 UStDV (Kleinbetragsrechnung ≤ 250 €)"]
referenzkunde:
status: TBD
name: "zu klären mit Sales — idealerweise Betrieb mit Montage-Außendienst, Bundesland-überschreitender Reisetätigkeit"
quelle: "Interview-Protokoll Sales-Discovery"
estimate_eng_tage: 36 # 18 Erfassung/OCR + 10 BMF-Pauschalen + 8 Bewirtung/Genehmigung
abhängigkeiten:
- kern/03-zeiterfassung # Reisezeit vs. Arbeitszeit
- kern/04-projekte-kunden # Projekt-Zuordnung, „weiterberechenbar"-Flag
- kern/05-rechnungsstellung # Spesen werden in Rechnung als Positionen übernommen
- kern/10-datev-integration # Reisekostenkonten 4660ff (SKR03) / 6640ff (SKR04)
- kern/11-audit-log
- kern/12-auth

Marktrealität (DE-Handwerk). Ein Monteur auf Montage-Einsatz in 180 km Entfernung löst im Laufe eines Tages 4–7 Belege aus: Tanken, Autobahn-Imbiss (Mittag), Schrauben-Nachkauf beim örtlichen Baustoffhändler, Abend-Pension, Frühstück. Jedes dieser Belege hat eine andere steuerliche Behandlung: Tanken ist Vorsteuer-abzugsfähig (Kleinbetragsrechnung §33 UStDV bis 250 €), Mittagessen wird durch Verpflegungs-Pauschale verdrängt (kein separater Vorsteuerabzug bei Verpflegung auf Dienstreise außerhalb Inland-24h), Schrauben sind Material (nicht Spesen), Pension ist Übernachtungsaufwand mit 7 % USt (Beherbergung) und ggf. 19 % auf Frühstück, Frühstück am nächsten Morgen wird wiederum durch Pauschale erfasst oder als Arbeitgeber-Bewirtung zugerechnet.

Heute macht die Buchhaltung das nachgelagert aus Schuhkarton-Belegen und handschriftlichen Reisekostenformularen — Fehlerquote hoch, Vorsteuer-Verluste regelmäßig vierstellig pro Jahr, BP-Risiko real. Die Alt-App hatte ein expensesService.js, aber keine BMF-Pauschalen-Logik (Mitarbeiter musste Verpflegung manuell einbuchen — fehlerträchtig), keine Mitternachtsregel (Abreise Tag 1 + Rückkehr Tag 2 = 2×14 € + 1×28 €, Grenzfall nicht abgedeckt), keine Ausland-Differenzierung (Schweiz-Einsatz mit CHF-Bewirtungsbeleg lief als Inland).

Schmerzpunkt der Alt-App. LESSONS-LEARNED §11 nennt vmaService.js (BMF-Sätze) als übernehmenswert — aber das UI hat die Logik nicht sichtbar gemacht. Monteure reichten 20 € Parkhaus-Beleg ohne MwSt-Ausweis ein, Buchhaltung trug 3,19 € Vorsteuer ein, Finanzamt strich — weil der Beleg kein §14 UStG war, sondern ein reiner Kassenzettel ohne Empfänger. Werkszeit muss dem Monteur am Erfassungszeitpunkt sagen, was er hat und was es braucht — sonst landen wir wieder bei Nacharbeit.

Erwarteter Outcome.

  • Zeit pro Beleg: Von ~6 min Nacharbeit (Zettel, Tipp, Buchhaltung-Rückfrage) auf <45 s am Ort (Foto + Bestätigen).
  • Vorsteuer-Verlust-Reduktion: Schätzung 60 % weniger Rejects wegen unvollständiger Pflichtangaben, weil UI präventiv prüft.
  • BP-Risiko: Reisekosten-Dokumentation wird vollständig, pauschalen-korrekt, mitternachtsregel-konform.

Rolle Aktion Scope Plattform
Mitarbeiter Beleg fotografieren, Reisekosten erfassen, Bewirtung erfassen, Km-Pauschale erfassen own 📱🌐
Manager Team-Spesen genehmigen (einzeln oder bulk), nachhaken team 📱 (Einzel) 🌐 (Bulk)
Buchhaltung Spesen freigeben für DATEV-Export, Korrekturen auslösen all 🌐
Admin Pauschalen-Profile, Kilometer-Sätze, Kontenmapping, BR-Freischaltung GPS all 🌐

Persona-Skizzen:

  • Hannes Krüger (Monteur, 38, SHK, musterbetrieb-maler) — auf Montage-Wochen in Bayern, manchmal 2–3 Tage Auswärts-Einsatz. Nutzt iPhone 14, fotografiert Belege am Ort, hasst Formulare im Fahrzeug. Fährt Firmenwagen (erhält Kilometer-Pauschale nur bei Sonderfahrten).
  • Markus Bauer (Monteur, 42, musterbetrieb-maler) — fährt eigenes Auto mit 0,30 €/km-Anspruch nach §9 Abs. 1 Nr. 4a EStG.
  • Sabine Maier (Manager, 45) — prüft 40–60 Spesen-Einreichungen pro Monat, will Bulk-Genehmigung mit auffallenden Abweichungen oben (Heuristik).
  • Frau Müller (Buchhaltung, 56) — reißt den DATEV-Export (Konto 4660 Reisekosten Arbeitnehmer, 4663 Bewirtung, 4664 Übernachtung, 4652 Kilometergeld) zum Monatswechsel, will keine Mappings mehr manuell anfassen.

US-01 [MVP] Als Monteur Hannes möchte ich auf dem Heimweg aus München einen
Tankstellen-Beleg fotografieren und mit einem Tap als
"Dienstfahrt Meiergroup GmbH, 19.04.2026" buchen, damit ich
den Zettel nicht verliere und die Buchhaltung ihn ohne
Rückfrage verarbeitet.
US-02 [MVP] Als Monteur möchte ich bei einem eintägigen Außentermin
über 8 Stunden automatisch die Verpflegungspauschale
14 € nach §9 Abs. 4a EStG angeboten bekommen, ohne
Betragsfelder ausfüllen zu müssen.
US-03 [MVP] Als Monteur möchte ich für eine mehrtägige Dienstreise
München → Stuttgart → München die BMF-Pauschalen nach der
Mitternachtsregel (Anreise 14 €, Zwischentag 28 €,
Abreise 14 €) automatisch generiert bekommen.
US-04 [MVP] Als Monteur möchte ich einen Bewirtungsbeleg erfassen mit
Pflichtfeldern Anlass, Teilnehmer, Ort, Datum nach §4 Abs. 5
Nr. 2 EStG, damit die Buchhaltung die 70 % Abzugsfähigkeit
korrekt verbucht.
US-05 [V1] Als Monteur möchte ich eine Dienstreise in die Schweiz
(Basel, 20./21.04.2026) erfassen, wobei das System
automatisch die Auslands-Tagegelder der Schweiz
(61 € / 41 € laut BMF-Tabelle 2026) statt Inland-Sätzen
heranzieht.
US-06 [MVP] Als Manager Sabine möchte ich auffällige Spesen (Bewirtung
> 100 € pro Person, Km-Abrechnung ohne eigenes Fahrzeug,
doppelte Belege am selben Tag) in der Bulk-Ansicht
priorisiert oben sehen, um gezielt nachzuhaken.
US-07 [MVP] Als Buchhaltung möchte ich pro Monatswechsel einen
DATEV-Export aller freigegebenen Spesen auf die korrekten
Reisekostenkonten 4660/4663/4664/4652 (SKR03) generieren.
US-08 [MVP] Als Admin möchte ich eine weiterberechenbare Spesen-Kategorie
(z. B. Fahrtkostenpauschale 0,42 €/km mit Aufschlag) an einen
Kunden koppeln, damit das in §3.5 als Rechnungsposition
übernommen wird.

  • F-M-01 Beleg-Quick-Capture: Kamera öffnet sich aus dem Tab „Mehr → Spesen → + Beleg“ mit Live-Rahmen-Erkennung. Nach Auslösen: Thumbnail + sofortige OCR-Vorverarbeitung (ML Kit on-device).
  • F-M-02 OCR-Felderkennung: Betrag brutto, MwSt-Satz (0/7/19 %), Lieferantenname, Datum, ggf. Rechnungsnummer, Zahlungsart. Felder sind editierbar; rot markiert, wenn Konfidenz <80 %.
  • F-M-03 Beleg-Kategorien (Pflicht-Auswahl): Tanken · Material (→ wandert nach §4.6, nicht Spesen) · Parken · Maut · Imbiss/Restaurant · Bewirtung (Gäste) · Übernachtung · Reinigung · ÖPNV · Taxi · Sonstiges. Pro Kategorie: Konten-Mapping, Vorsteuer-Indikator, Pflichtfelder.
  • F-M-04 Projekt-/Kunden-Zuordnung mit GPS-Vorschlag: Wenn Standort ≤ 500 m zu bekannter Baustelle → Vorschlag; sonst manuell aus Favoriten.
  • F-M-05 „Weiterberechenbar“-Flag bei Zuordnung zu einem Projekt mit „Spesen-Weiterberechnung aktiviert“ — landet später in §3.5 als Rechnungsposition.
  • F-M-06 Bewirtungsbeleg-Spezialformular: Anlass (Freitext, Pflicht), Teilnehmer-Liste (Name + Firma, 1..N; eigene Person als erster Eintrag vorbelegt), Ort (GPS-vorgeschlagen), Datum/Uhrzeit, Trinkgeld separat, Gesamt brutto, Handschriftliche Prüfung durch Gastwirt erforderlich: Feld „Gastwirt bestätigt Angaben“ per Unterschrift auf Bewirtungsbeleg-Rückseite (Foto).
  • F-M-07 Reisekosten-Assistent: „Neue Reise“ → Startort, Zielort, Beginn, (Ende), Zweck, Projekt. System generiert automatisch Pauschalen-Tageseinträge nach BMF-Tabelle 2026 (Inland / Ausland je Zielland; Mitternachtsregel bei >8h Abwesenheit am An-/Abreisetag).
  • F-M-08 Kilometerabrechnung: Nur sichtbar, wenn im Mitarbeiter-Stamm eigenes_fahrzeug = true hinterlegt ist. Strecken-Eingabe Kilometer oder „Tour erfassen“ (GPS-Track) — GPS-Modus nur bei aktivierter BR-Freischaltung (§10.6). Satz: 0,30 €/km PKW, 0,20 €/km Motorrad/Kraftrad.
  • F-M-09 Offline-Erfassung: Belege werden mit Thumbnail im Drift gespeichert; Original geht per S3-Multipart-Upload mit presigned URL sobald online. Outbox führt Idempotency-Key (UUID v7).
  • F-M-10 Einreichung: Mitarbeiter drückt „Einreichen“ auf einer zusammengestellten Woche/Monatsmappe; Manager erhält Push.
  • F-M-11 Status-Anzeige: Entwurf · Eingereicht · Genehmigt · Abgelehnt (mit Kommentar) · Exportiert (DATEV).
  • F-W-01 Team-Spesen-Übersicht (Manager): Filter Zeitraum/Mitarbeiter/Projekt/Status; Heuristik-Sort (Auffälligkeit oben).
  • F-W-02 Bulk-Genehmigung: Checkbox-Mehrfachauswahl, „Alle genehmigen“ mit Auffälligkeits-Bestätigung.
  • F-W-03 Einzel-Prüfung: Großes Beleg-Foto links, Feld-Liste rechts, Prüf-Historie, Kommentarfeld, [Genehmigen] [Ablehnen + Kommentar] [Zurückgeben an Mitarbeiter].
  • F-W-04 Auffälligkeits-Heuristiken: (a) Bewirtung > 100 €/Person, (b) Km-Anspruch ohne Eigenfahrzeug-Flag, (c) doppelter Beleg innerhalb 60 min im selben Geoumkreis, (d) Beleg ≥ 250 € ohne §14 UStG-Pflichtangaben, (e) Pauschale überlappend mit anderem Pauschalen-Tag, (f) Ausland ohne Zielland-Auswahl.
  • F-W-05 Reisekosten-Monatsabschluss (Buchhaltung): Zeitraum wählen, Vorschau DATEV-Export (Konto × Menge × Summe), Freigabe → Export-Zip.
  • F-W-06 DATEV-EXTF-Export für Spesen: Buchungssätze auf 4660 (Reisekosten AN), 4663 (Bewirtung), 4664 (Übernachtung), 4652 (Kilometergeld AN), 4670 (Pauschalen) — je nach Kategorie und SKR-Mapping aus §F-A-03.
  • F-W-07 Belegarchiv durchsuchbar (Volltext OCR, Datum, Kategorie, Mitarbeiter, Projekt). 10-Jahres-S3-Object-Lock aktiv.
  • F-W-08 Weiterberechnungs-Übergabe an §3.5: Freigegebene „weiterberechenbar“-Spesen erscheinen in der Rechnungs-Positions-Auswahl automatisch, mit optionalem Aufschlag.
  • F-X-01 Spesen-Status in persönlicher Tagesübersicht (offen/eingereicht/genehmigt).
  • F-X-02 Audit-Trail je Beleg sichtbar: wer hat wann was geändert (Append-only, Hash-Chain).
  • F-A-01 Spesen-Kategorien: Name, Default-Konto (SKR03/04), Vorsteuer-Flag, Pflichtfelder-Matrix (Anlass? Teilnehmer? Zielland? Km-Feld?), „weiterberechenbar“-Default.
  • F-A-02 Pauschalen-Profile: BMF-Tabelle 2026 Inland (28 €/14 €) + Ausland (pro Land: zwei Sätze, eingepflegt aus BMF-Schreiben jährlich). Admin kann keine eigenen Sätze erfinden, nur die BMF-Tabelle aktivieren/aktualisieren.
  • F-A-03 Kontenrahmen-Mapping SKR03/SKR04 pro Kategorie × Land × USt-Satz.
  • F-A-04 Km-Sätze (0,30 € PKW / 0,20 € Motorrad, gesetzlich festgelegt nach §9 Abs. 1 Nr. 4a EStG, Admin kann Mitnahme-Sätze 0,02 €/km für mitgenommene Kollegen freischalten).
  • F-A-05 BR-Freischaltung Standort-Erfassung — separater Config-Schalter mit Eintrag „BR-Zustimmungs-Beleg ID“, Pflicht-Tenant-Admin-Audit.
  • F-A-06 „Weiterberechenbar“-Kunden-Profile — welcher Kunde akzeptiert welche Spesenarten in welcher Höhe mit welchem Aufschlag.
Anforderung-ID MVP V1 V1.5 V2
F-M-01 bis F-M-07 (inkl. BMF Inland)
F-M-07 (Ausland)
F-M-08 (Km-Pauschale manuell)
F-M-08 (GPS-Track)
F-M-09 (Offline)
F-W-01 bis F-W-07
F-W-08 (Weiterberechnung)
Firmen-Kreditkarten-Integration (American Express, Pluxee)
SAP Concur-Import

HTML-Hero-Mockup: 06-spesen-reisemanagement.html — Mobile-Beleg-Erfassung + Web-Bulk-Genehmigung.

┌────────────────────────────────┐
│ ← Neuer Beleg ✕ │
├────────────────────────────────┤
│ 🛡 GoBD · DSGVO · §9 EStG │
│ │
│ [Foto-Vorschau groß] │
│ Aral Tankstelle München │
│ Nymphenburger Str. 84 │
│ │
│ Betrag brutto │
│ [ 84,92 € ] 🟢 │
│ │
│ USt-Satz │
│ (•) 19 % ( ) 7 % ( ) 0% │
│ │
│ Datum · Uhrzeit │
│ [ 19.04.2026 · 17:42 ] 🟢 │
│ │
│ Kategorie 🔴 │
│ ▼ Tanken (Fahrzeug) │
│ │
│ Projekt-Zuordnung │
│ ▼ Sanierung Heizung Bgm.Müller │
│ GPS 320 m Distanz │
│ │
│ [ ] Weiterberechenbar an Kunde │
│ │
│ [✓ Speichern] [+ Weiteres] │
└────────────────────────────────┘
┌────────────────────────────────┐
│ ← Bewirtung erfassen ✕ │
├────────────────────────────────┤
│ §4 Abs. 5 Nr. 2 EStG │
│ 70 % abzugsfähig · 30 % Eigen │
│ │
│ 🟢 Beleg erkannt · 142,80 € │
│ │
│ Anlass 🔴 │
│ ┌────────────────────────────┐ │
│ │Angebotsgespräch Heizungs- │ │
│ │tausch Schule │ │
│ └────────────────────────────┘ │
│ │
│ Teilnehmer 🔴 │
│ ● Hannes Krüger (eigene Firma) │
│ ● Dr. Meier (Meiergroup GmbH) │
│ ● Fr. Vogt (Schulträger) │
│ [+ Teilnehmer hinzufügen] │
│ │
│ Ort │
│ [ Ristorante Da Luigi, Mchn ] │
│ │
│ Datum · Zeit │
│ [ 19.04.2026 · 19:30 ] │
│ │
│ Trinkgeld separat │
│ [ 8,00 € ] │
│ │
│ 📷 Rückseite mit Unterschrift │
│ Gastwirt hinzufügen (Pflicht) │
│ [Foto aufnehmen] │
│ │
│ [✓ Speichern & Einreichen] │
└────────────────────────────────┘

6.3 Mobile — Reisekosten-Assistent (3-Tage-Reise)

Abschnitt betitelt „6.3 Mobile — Reisekosten-Assistent (3-Tage-Reise)“
┌────────────────────────────────┐
│ ← Neue Dienstreise ✕ │
├────────────────────────────────┤
│ §9 Abs. 4a EStG · BMF 2026 │
│ │
│ Von [ München ] │
│ Nach [ Stuttgart ] │
│ Zweck [ Montage Meiergrp ] │
│ Projekt ▼ Bgm.Müller-Schule │
│ │
│ Beginn [ 19.04.2026 · 07:30] │
│ Ende [ 21.04.2026 · 18:00] │
│ │
│ ─── Automatisch generiert ──── │
│ │
│ Mi 19.04. Anreise 14,00 € │
│ (>8h Abwesenheit, Mitter- │
│ nachtsregel Tag 1) │
│ Do 20.04. Zwischen 28,00 € │
│ (24-h-Abwesenheit) │
│ Fr 21.04. Abreise 14,00 € │
│ (Mitternachtsregel Tag 3) │
│ │
│ Summe Pauschale 56,00 € │
│ (nicht Vorsteuer-pflichtig) │
│ │
│ + Übernachtungen Belege 🔗 2 │
│ + Fahrtkosten Eigen-KFZ 🔗 1 │
│ │
│ [✓ Speichern] [✎ Anpassen] │
└────────────────────────────────┘

6.4 Mobile — Grenzfall: Bewirtungsbeleg-Pflichtangabe fehlt

Abschnitt betitelt „6.4 Mobile — Grenzfall: Bewirtungsbeleg-Pflichtangabe fehlt“
┌────────────────────────────────┐
│ ⚠ Bewirtungsbeleg unvollständig│
├────────────────────────────────┤
│ §4 Abs. 5 S. 1 Nr. 2 EStG │
│ Abzug nicht möglich, weil: │
│ │
│ ✕ Anlass fehlt │
│ ✕ Teilnehmer unvollständig │
│ (nur 1 Person erfasst) │
│ ✕ Gastwirt-Unterschrift fehlt │
│ │
│ Der Beleg wird gespeichert, │
│ aber NICHT zum Betriebsausgabe-│
│ abzug eingereicht. Buchhaltung │
│ wird informiert. │
│ │
│ [Vervollständigen] [Trotzdem] │
└────────────────────────────────┘

6.5 Web — Manager-Bulk-Genehmigung mit Heuristiken

Abschnitt betitelt „6.5 Web — Manager-Bulk-Genehmigung mit Heuristiken“
┌────────────────────────────────────────────────────────────────────────────────┐
│ Werkszeit · musterbetrieb-maler Sabine M. · Manager [Profil ▾] │
├───────────┬────────────────────────────────────────────────────────────────────┤
│ Sidebar │ Spesen-Genehmigung · April 2026 │
│ ──────────│ ───────────────────────────────────────────────────────────────── │
│ Zeit │ KPI: 42 offen · 28 auffällig · 6.284,40 € Volumen │
│ Plan │ │
│ Doku │ Filter: [Alle Mitarb.▾] [Zeitraum: Apr 26 ▾] [🔴 Auffällig zuerst]│
│▶Spesen │ │
│ Projekt │ ☐ MA Datum Kat Betrag Proj ⚠Heur. │
│ Rechnung │ ☑ Hannes K. 19.04.2026 Bewirtung 142,80€ Bgm.M-S 🟡 Trink. │
│ DATEV │ ☑ Markus B. 18.04.2026 Km-Pauschale 56,40€ Meier 🟡 0,42/km│
│ ────── │ ☑ Hannes K. 17.04.2026 Tanken 84,92€ Bgm.M-S │
│ │ ☑ Hannes K. 17.04.2026 Tanken 82,10€ Bgm.M-S 🔴 Dopp. │
│ │ ☐ Mehmet Y. 16.04.2026 Übernacht 112,00€ Meier │
│ │ │
│ │ Heuristik-Dialog (Hover „🔴 Dopp."): │
│ │ ┌─────────────────────────────────────────────────────────────┐ │
│ │ │ Zwei Tankbelege innerhalb 25 min am selben Ort (Aral Mchn). │ │
│ │ │ Vermutlich doppelt eingereicht. Bitte prüfen. │ │
│ │ └─────────────────────────────────────────────────────────────┘ │
│ │ │
│ │ [3 genehmigen] [1 ablehnen] [1 zurück an MA] [⤓ DATEV-EXTF] │
└───────────┴────────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────────────────────────────────────────────────┐
│ DATEV-Export · Reisekosten · Periode 01.04.–30.04.2026 │
├────────────────────────────────────────────────────────────────────────────────┤
│ Mandant 77810 · Berater 12345 · SKR03 · Wirtschaftsjahr 2026 │
│ │
│ Buchungsstapel „Spesen_042026" (42 Sätze, 6.284,40 €) │
│ │
│ Konto Bezeichnung Anz Summe EUR Gegenkonto │
│ 4660 Reisekosten AN (Fahrtkosten) 18 1.840,00 1371 Reisek.durchl.Posten│
│ 4663 Bewirtungskosten 70 % abzugsf. 4 412,24 1371 │
│ 4664 Übernachtungskosten 6 1.024,00 1371 │
│ 4652 Kilometergeld Eigen-KFZ 10 628,00 1371 │
│ 4670 Verpflegungsmehraufw. Pauschal 4 2.380,00 1371 │
│ │
│ Hinweis: Bewirtung 30 % nicht abzugsfähig (auf Konto 4655) = 176,67 € │
│ │
│ [EXTF herunterladen] [An Konto senden] │
└────────────────────────────────────────────────────────────────────────────────┘

apps/api/src/db/schema/expenses.ts
export const expenseReceiptsTable = pgTable('expense_receipts', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull().references(() => tenantsTable.id),
userId: uuid('user_id').notNull().references(() => usersTable.id), // Einreicher
projectId: uuid('project_id').references(() => projectsTable.id),
category: text('category').notNull(), // 'fuel'|'parking'|'toll'|'meal_entertainment'|'lodging'|'public_transport'|'taxi'|'per_diem'|'mileage'|'other'
receiptDate: date('receipt_date').notNull(),
receiptTime: time('receipt_time'),
vendorName: text('vendor_name'),
vendorLocation: text('vendor_location'),
grossAmount: numeric('gross_amount', { precision: 10, scale: 2 }).notNull(),
vatRate: numeric('vat_rate', { precision: 4, scale: 2 }).notNull(), // 0.00 / 7.00 / 19.00
vatAmount: numeric('vat_amount', { precision: 10, scale: 2 }).notNull(),
currency: text('currency').notNull().default('EUR'),
exchangeRate: numeric('exchange_rate', { precision: 12, scale: 6 }), // bei Nicht-EUR
amountEur: numeric('amount_eur', { precision: 10, scale: 2 }).notNull(),
ocrConfidence: numeric('ocr_confidence', { precision: 3, scale: 2 }), // 0.00–1.00
photoS3Key: text('photo_s3_key').notNull(), // Object Lock Compliance 10 Jahre
photoBackS3Key: text('photo_back_s3_key'), // Bewirtungsbeleg Rückseite
chargeable: boolean('chargeable').notNull().default(false),
chargeableMarkup: numeric('chargeable_markup', { precision: 4, scale: 2 }), // Aufschlag bei Weiterberechnung
status: text('status').notNull().default('draft'), // 'draft'|'submitted'|'approved'|'rejected'|'exported'
geoLat: numeric('geo_lat', { precision: 9, scale: 6 }),
geoLon: numeric('geo_lon', { precision: 9, scale: 6 }),
metadataJson: jsonb('metadata_json'), // OCR-Rohdaten, Beleg-Typ-Details
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
createdBy: uuid('created_by').notNull().references(() => usersTable.id),
idempotencyKey: uuid('idempotency_key').notNull(),
// GoBD
hashPrev: bytea('hash_prev'),
hashSelf: bytea('hash_self'),
}, (t) => ({
tenantUserIdx: index('expense_receipts_tenant_user_idx').on(t.tenantId, t.userId, t.receiptDate.desc()),
statusIdx: index('expense_receipts_status_idx').on(t.tenantId, t.status),
idempotencyIdx: uniqueIndex('expense_receipts_idemp_idx').on(t.tenantId, t.idempotencyKey),
}));
export const expenseEntertainmentDetailsTable = pgTable('expense_entertainment_details', {
receiptId: uuid('receipt_id').primaryKey().references(() => expenseReceiptsTable.id, { onDelete: 'cascade' }),
tenantId: uuid('tenant_id').notNull(),
occasion: text('occasion').notNull(), // §4 Abs. 5 Nr. 2 EStG — Anlass
location: text('location').notNull(),
tipAmount: numeric('tip_amount', { precision: 10, scale: 2 }).notNull().default('0.00'),
hostSignatureProvided: boolean('host_signature_provided').notNull().default(false),
deductiblePercent: numeric('deductible_percent', { precision: 5, scale: 2 }).notNull().default('70.00'),
});
export const expenseEntertainmentParticipantsTable = pgTable('expense_entertainment_participants', {
id: uuid('id').primaryKey().defaultRandom(),
receiptId: uuid('receipt_id').notNull().references(() => expenseReceiptsTable.id, { onDelete: 'cascade' }),
tenantId: uuid('tenant_id').notNull(),
name: text('name').notNull(),
company: text('company'),
isOwn: boolean('is_own').notNull().default(false), // eigener Betrieb
position: integer('position').notNull(),
});
export const tripsTable = pgTable('trips', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull(),
userId: uuid('user_id').notNull(),
projectId: uuid('project_id'),
originCity: text('origin_city').notNull(),
destinationCity: text('destination_city').notNull(),
destinationCountryIso2: text('destination_country_iso2').notNull().default('DE'),
purpose: text('purpose').notNull(),
departureAt: timestamp('departure_at', { withTimezone: true }).notNull(),
returnAt: timestamp('return_at', { withTimezone: true }),
status: text('status').notNull().default('draft'),
});
export const perDiemEntriesTable = pgTable('per_diem_entries', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull(),
tripId: uuid('trip_id').notNull().references(() => tripsTable.id, { onDelete: 'cascade' }),
entryDate: date('entry_date').notNull(),
dayKind: text('day_kind').notNull(), // 'departure'|'full'|'return'|'single_over_8h'
countryIso2: text('country_iso2').notNull(),
bmfRateLarge: numeric('bmf_rate_large', { precision: 6, scale: 2 }).notNull(), // 28,00 Inland / abweichend Ausland
bmfRateSmall: numeric('bmf_rate_small', { precision: 6, scale: 2 }).notNull(), // 14,00 Inland
amountEur: numeric('amount_eur', { precision: 10, scale: 2 }).notNull(),
autoGenerated: boolean('auto_generated').notNull().default(true),
overrideReason: text('override_reason'),
}, (t) => ({
uniq: uniqueIndex('per_diem_unique').on(t.tripId, t.entryDate),
}));
export const mileageEntriesTable = pgTable('mileage_entries', {
id: uuid('id').primaryKey().defaultRandom(),
tenantId: uuid('tenant_id').notNull(),
userId: uuid('user_id').notNull(),
projectId: uuid('project_id'),
tripDate: date('trip_date').notNull(),
kmDriven: numeric('km_driven', { precision: 8, scale: 2 }).notNull(),
vehicleKind: text('vehicle_kind').notNull(), // 'car'|'motorcycle'
ratePerKm: numeric('rate_per_km', { precision: 4, scale: 2 }).notNull(), // 0.30 / 0.20
amountEur: numeric('amount_eur', { precision: 10, scale: 2 }).notNull(),
originCity: text('origin_city'),
destinationCity: text('destination_city'),
gpsTrackS3Key: text('gps_track_s3_key'), // optional, nur mit BR-Freischaltung
status: text('status').notNull().default('draft'),
});
export const bmfRatesTable = pgTable('bmf_rates', {
id: uuid('id').primaryKey().defaultRandom(),
countryIso2: text('country_iso2').notNull(),
validFrom: date('valid_from').notNull(),
validTo: date('valid_to'),
rateLarge: numeric('rate_large', { precision: 6, scale: 2 }).notNull(), // 24h-Satz
rateSmall: numeric('rate_small', { precision: 6, scale: 2 }).notNull(), // An-/Abreise-Satz
lodgingRate: numeric('lodging_rate', { precision: 6, scale: 2 }), // Pauschal-Übernachtung nur bei Arbeitgeber-Kostenübernahme
}, (t) => ({
uniq: uniqueIndex('bmf_rate_unique').on(t.countryIso2, t.validFrom),
}));

RLS-Policy-Sketch:

ALTER TABLE expense_receipts ENABLE ROW LEVEL SECURITY;
CREATE POLICY expense_tenant_isolation ON expense_receipts
USING (tenant_id = current_setting('app.tenant_id')::uuid);
CREATE POLICY expense_scope_own ON expense_receipts
FOR ALL
USING (
tenant_id = current_setting('app.tenant_id')::uuid
AND (
current_setting('app.scope') = 'all'
OR (current_setting('app.scope') = 'team' AND user_id IN (
SELECT tm.user_id FROM team_members tm
WHERE tm.manager_user_id = current_setting('app.user_id')::uuid))
OR (current_setting('app.scope') = 'own' AND user_id = current_setting('app.user_id')::uuid)
)
);
-- Buchhaltung: Lesen + Exportieren für alle Tenants-User
CREATE POLICY expense_role_buchhaltung ON expense_receipts
FOR SELECT
USING (
tenant_id = current_setting('app.tenant_id')::uuid
AND current_setting('app.role') IN ('admin', 'buchhaltung')
);

ER-Bezug. Referenziert users (Einreicher, Genehmiger), projects und customers (Zuordnung, weiterberechenbar), time_entries (Reise = Arbeitszeit Teil des Tagessaldos in §3.1), invoices (Weiterberechnung an §3.5), datev_exports (Buchungssätze an §3.10).


Methode Pfad Auth-Scope Rate-Limit Idempotenz Beschreibung
POST /v1/kern/expenses/receipts expenses:write Standard Pflicht Beleg anlegen (nach Foto-Upload)
GET /v1/kern/expenses/receipts expenses:read:<scope> Standard Liste mit Filter
GET /v1/kern/expenses/receipts/{id} expenses:read:<scope> Standard Detail
PATCH /v1/kern/expenses/receipts/{id} expenses:write Standard Pflicht Korrektur (Append-only bei Freigabe)
POST /v1/kern/expenses/receipts/{id}/submit expenses:write Standard Pflicht Zur Genehmigung einreichen
POST /v1/kern/expenses/receipts/{id}/approve expenses:approve Privileged Pflicht Genehmigen
POST /v1/kern/expenses/receipts/{id}/reject expenses:approve Privileged Pflicht Ablehnen mit Kommentar
POST /v1/kern/expenses/receipts/bulk-approve expenses:approve Privileged Pflicht Bulk-Genehmigung
POST /v1/kern/expenses/trips expenses:write Standard Pflicht Dienstreise anlegen
POST /v1/kern/expenses/trips/{id}/generate-per-diem expenses:write Standard Pflicht BMF-Pauschalen automatisch generieren
POST /v1/kern/expenses/mileage expenses:write Standard Pflicht Km-Pauschale erfassen
POST /v1/kern/expenses/export/datev-extf expenses:export Privileged Pflicht Monatsabschluss-Export
GET /v1/kern/expenses/receipts/{id}/photo expenses:read:<scope> Standard Presigned S3-URL (5 min)

OpenAPI-Schema-Skizze:

paths:
/v1/kern/expenses/trips/{id}/generate-per-diem:
post:
operationId: generatePerDiem
x-werkszeit-scope: expenses:write
responses:
'200':
description: Pauschalen-Eintragungen erzeugt (Mitternachtsregel berücksichtigt)
content:
application/json:
schema:
type: object
properties:
entries: { type: array, items: { $ref: '#/components/schemas/PerDiemEntry' } }
totalAmountEur: { type: number, format: decimal }
'422':
description: Reise-Daten unzureichend (z. B. Ende nicht gesetzt und Reise älter als 24 h)

Webhook-Events:

  • expense.submitted
  • expense.approved / expense.rejected
  • expense.exported (mit DATEV-Batch-Referenz)
  • trip.created

Profil Begründung Mechanik
Voll-offline (Receipts, Trips, Mileage) Monteur ist häufig offline in Kellern, Autobahn-Tunneln, Funklöchern. Beleg-Erfassung darf nie verloren gehen. Drift-Tabelle lokal, Outbox mit Idempotency-Key (UUID v7). Thumbnail lokal, Original-Foto bleibt als pending_upload_s3 lokal bis Upload; photoS3Key wird nachgetragen.
Online-only (Bulk-Genehmigung, DATEV-Export) Manager-/Buchhaltungs-Aufgaben auf Großbildschirm. UI-Block bei connectivity == none.

Konflikt-Strategie. Append-only mit versionierter Korrektur. Manager ändert nach Einreichung an Beleg B Kategorie → neue Version mit Begründungsfeld. Original bleibt im Audit-Trail. Bei parallel-änderndem Mitarbeiter (gleichzeitig offline Änderung durch MA + online Korrektur durch Manager) → Manager-Änderung gewinnt (Server-Autorität), MA-Änderung wird in Konflikt-Liste gemeldet und muss erneut bestätigt werden.

Foto-/Datei-Sync. S3-Multipart über presigned URL; Drift speichert Thumbnail (≤ 200 KB) und Referenz.


  • Append-only-Tabelle: expense_receipts und alle Subtabellen (entertainment_details, participants, trips, per_diem_entries, mileage_entries) mit hashPrev/hashSelf. Ab Status approved ist nur noch Storno/Neuausfertigung mit Begründung möglich — kein In-Place-Edit.
  • Aufbewahrung: Beleg-Foto (Vorderseite, Rückseite, GPS-Track-Datei) in S3 mit Object Lock Compliance-Mode, Retention 3660 Tage (10 Jahre, §147 AO).
  • Nachvollziehbarkeit: Jede Mutation erzeugt Audit-Log-Eintrag (Append-only, Hash-verkettet — siehe kern/11-audit-log). Manager-Genehmigung, Ablehnung, Zurückgabe, Export → je eigener Eintrag mit vollem Kontext.
  • Verfahrensdokumentation: Bausteine „Elektronischer Beleg-Workflow“, „OCR-basierte Vorerfassung (mit menschlicher Pflicht-Freigabe — keine voll-automatische Buchung)“, „Bewirtungsbeleg-Workflow §4 Abs. 5 EStG”, „BMF-Pauschalen-Aktualisierungs-Prozess“.

nicht zutreffend für das Spesen-Feature direkt. Reise-Zeit als Arbeitszeit ist Thema in §3.1 Zeiterfassung; dort greifen die ArbZG-Prüfungen.

nicht zutreffend.

  • Art. 5 Datenminimierung: Standort-Daten (GPS) werden nur erfasst, wenn der Mitarbeiter das Feature in der App aktiviert hat UND im Tenant die BR-Freischaltung vorliegt UND die Kategorie GPS tatsächlich benötigt (Kilometer-Track). Bei Reisespesen ohne GPS-Track bleibt Geo leer.
  • Art. 6 Abs. 1 lit. b (Vertrag): Spesenerstattung ist Teil des Arbeitsvertrags → Vertragsgrundlage. Für Teilnehmer-Namen in Bewirtung zusätzlich berechtigtes Interesse nach lit. f (Nachweispflicht gegenüber Finanzbehörden, §4 Abs. 5 EStG).
  • Art. 17 Löschung kollidiert mit §147 AO — 10 Jahre zwingend. Maskierung der personenbeziehbaren Felder (createdBy, userId) nach Ablauf; aber nur logisch, Audit-Historie bleibt.
  • Art. 20 Datenexport: Vollständiger Export pro Betroffenem als JSON + Fotos-Zip, einschließlich Zuordnungs-Metadaten.
  • Art. 30 Verarbeitungsverzeichnis: „Spesenerfassung und Reisekostenabrechnung — Rechtsgrundlage Vertrag + §147 AO — Aufbewahrung 10 Jahre — Kategorien: Name, Anschrift, Bankverbindung (für Erstattung), Standortdaten (bei Km-Track mit BR-Zustimmung), Beleg-Metadaten inkl. Teilnehmer bei Bewirtung“.
  • DSFA: Pflicht, wenn GPS-Track-Feature aktiviert wird (Art. 35 Abs. 3 DSGVO iVm §26 BDSG). Ohne GPS-Track: DSFA entfällt, Standard-Dokumentation genügt. Vorlage im Admin-Portal hinterlegt.

Die Mobile-Erfassung ist ein ESS-Flow (Mitarbeiter erfasst eigene Daten) — fällt unter BFSG ab 28.06.2025.

  • Screenreader: VoiceOver / TalkBack — Jedes Formularfeld hat semanticsLabel; Kamerabild wird mit erkannten Feldern als Live-Region angesagt ("Betrag 84,92 Euro erkannt, USt 19 Prozent, Datum 19.04.2026, Tippe zum Bestätigen").
  • Tastatur-Navigation (Web): Tab-Reihenfolge: Belegliste → Filter → Detail-Form → Aktionen. Skip-Link auf Hauptinhalt.
  • Kontrast: Erkennung-Feldmarker 🟢/🔴 zusätzlich mit Text, nicht nur Farbe. Feld-Borders Orange auf Weiß: #FF6B1A auf #FAFAF7 → 3,11:1 nur für Nicht-Text-Kontrast (ausreichend nach WCAG 2.2 AA 1.4.11). Fehler-Text in Rot auf Weiß: #B5391A auf #FAFAF7 → 5,58:1 ≥ 4,5:1 ✓.
  • GPS-Track für Kilometer ist Leistungs-/Verhaltenskontrolle nach §87 Abs. 1 Nr. 6 BetrVG. Ohne Betriebsrat-Zustimmung (oder bei BR-freien Betrieben ohne explizite Vereinbarung) ist das Feature deaktiviert.
  • Admin schaltet GPS-Track hinter Pflicht-Workflow frei: (1) Upload BR-Beschluss-PDF, (2) Verantwortlicher benannt, (3) DSFA-Dokument verlinkt, (4) Audit-Eintrag mit Zeitstempel und Verantwortlichem.
  • UI zeigt beim Aktivieren der GPS-Erfassung einen Hinweis „Standort-Track aktiv — basierend auf BR-Beschluss vom “.
  • Default: GPS-Track deaktiviert in jedem neuen Tenant.
  • §4 Abs. 5 Nr. 2 EStG (Bewirtung): 70 % abzugsfähig als Betriebsausgabe, 30 % nicht abzugsfähig. Pflichtangaben laut AEAO zu §4 Abs. 5 EStG: Ort, Tag, Teilnehmer, Anlass der Bewirtung, Höhe der Aufwendungen. Bei Gaststätten: Rechnung muss maschinell erstellt und maschinell registriert sein (§14 UStG-Pflichtangaben); bei kleinen Beträgen reicht §33 UStDV.
  • §9 Abs. 4a EStG + BMF-Schreiben 2026 (Verpflegungsmehraufwand): Inland 28 € 24-h-Abwesenheit, 14 € An-/Abreisetag bei mehrtägig oder eintägig >8h. Mitternachtsregel: Beginnt Dienstreise vor 00:00 und endet nach 00:00 am Folgetag → beide Tage zählen, jeder mit 14 € (≥ 8h Abwesenheit je Tag). Ausland nach BMF-Länderliste (jährlich aktualisiert in bmf_rates).
  • §8 Abs. 2 EStG (Sachbezug) und §3 Nr. 16 EStG: Erstattete Reisekosten bleiben steuerfrei, wenn Nachweispflicht erfüllt. Werkszeit dokumentiert, FiBu meldet über DATEV-LODAS an Lohnsteuer.
  • §15 UStG Vorsteuerabzug: Bei Kleinbetragsrechnung (§33 UStDV, ≤ 250 € brutto) genügen: vollständiger Name/Anschrift des Ausstellers, Ausstellungsdatum, Menge und Art, Entgelt und Steuerbetrag in einer Summe, Steuersatz. Bei > 250 € brutto: alle §14 UStG-Pflichtangaben, auch Empfänger-Name — der ist bei Tankbelegen typischerweise nicht enthalten, daher Vorsteuer-Abzug nur bei Kleinbetrag; > 250 € Tankbeleg → Buchhalter muss Rechnungs-Erstausstellung bei Tankstelle anfordern. Werkszeit warnt bei > 250 € fehlender Empfänger-Angabe.

# Szenario Erwartetes Verhalten
EC-01 BMF-Pauschale Inland 2026 — Eintägige Geschäftsreise Hannes, Abwesenheit 9:15 h Automatisch 14 € (Kleinbetragssatz nach §9 Abs. 4a EStG) generiert, Kategorie per_diem, Steuersatz 0 %.
EC-02 Mitternachtsregel — Dienstreise beginnt 19.04. 23:00, endet 20.04. 03:00 (4h Abwesenheit jeweils) System erkennt < 8h je Tag → keine Pauschale. Mitarbeiter sieht Hinweis „Unter 8h Abwesenheit an beiden Tagen, keine Pauschale nach §9 Abs. 4a EStG“.
EC-03 Bewirtungsbeleg ohne §4(5)1 Nr.2-Pflichtangaben — Teilnehmer/Anlass leer Beleg lässt sich speichern, wird aber mit Warnung „Nicht abzugsfähig — Pflichtangaben fehlen“ gespeichert. Kategorie bleibt meal_entertainment, Flag missingMandatoryFields=true, Manager bekommt Priorisierungs-Hinweis im Bulk-Screen. Buchhaltung sieht separaten Filter „Nicht abzugsfähig“ und kann Beleg trotzdem als 100 % nicht abzugsfähig auf Konto 4655 buchen (privater Aufwand).
EC-04 Auslandstagegeld Schweiz — Zweitägige Reise Basel 20./21.04.2026 (Ankunft 19.04. 17:00, Abreise 21.04. 20:00) System liest aus bmf_rates CH-Sätze 2026 (Beispielwerte: 61 €/41 €). Generiert: 19.04. Anreise 41 €, 20.04. Zwischentag 61 €, 21.04. Abreise 41 €. Gesamt 143 €. Währung CHF für Belege wird separat über exchange_rate in EUR umgerechnet, Wechselkurs Bundesbank-Tageskurs.
EC-05 Großmenge — Manager fordert Spesen-Export Q1/2026 (1.800 Belege) Hintergrund-Job via BullMQ, Download-Link per E-Mail. UI-Fallback: Tages-Stückelung.
EC-06 Multi-Tenant-Bleed-Versuch — Mitarbeiter gehört zu zwei Tenants, wechselt während Beleg-Erfassung Session-Wechsel re-initialisiert app.tenant_id. Entwurf aus Tenant A wird beim Wechsel in Tenant B nicht sichtbar (RLS). Drift-Cache trennt Tenants via Composite-Key (tenantId, receiptId).
EC-07 Plan-Limit Starter — 11. Mitarbeiter fügt Beleg hinzu, obwohl Starter-Plan auf 10 User beschränkt Wird in kern/07-billing abgefangen, nicht hier. Spesen-Feature-Flag ist im Starter-Plan vollständig freigeschaltet.
EC-08 Duplikat-Heuristik — Hannes reicht denselben Tankbeleg zweimal offline ein (zwei Geräte) Idempotency-Key auf Client-Seite verhindert Zweifach-POST. Ohne Idempotency: Server-seitige Heuristik (Betrag + Datum ±5 min + Vendor + User) markiert Zweitbeleg als possible_duplicate, Manager sieht Marker.
EC-09 Kein eigenes Fahrzeug hinterlegt, Km-Pauschale trotzdem eingereicht UI blockt Km-Formular-Aufruf. Falls über API umgangen: Validator prüft user.owns_private_vehicle = true, sonst 422.
EC-10 Beleg-Foto verloren (Upload-Fehler nach Löschung der App) Thumbnail-Referenz bleibt mit Status photo_missing. Buchhaltung kann Beleg nicht freigeben; Mitarbeiter-Push „Bitte Beleg neu fotografieren“. Wenn nicht möglich: Admin kann Buchung mit Begründung händisch freigeben (Audit).

Funktionalität: Spesen- und Reisemanagement (Erfassung, Pauschalen, Genehmigung)
Hintergrund:
Angenommen ein Tenant "musterbetrieb-maler" mit aktivem Modul-Gate "module.kern.expenses"
Und Mitarbeiter "Hannes Krüger" mit Rolle "Mitarbeiter", Scope "own"
Und im Tenant ist die BMF-Rate-Tabelle 2026 geladen (Inland 28/14, Schweiz 61/41)
Und im Tenant ist Kategorie "meal_entertainment" mit 70 % abzugsfähig konfiguriert
Und Manager "Sabine Maier" mit Scope "team"
Szenario: Happy Path — Tankbeleg Aral München, Dienstreise Hannes
Angenommen Hannes ist offline auf der A8 auf Rückfahrt von München
Wenn Hannes in der App Tab "Mehr → Spesen → + Beleg" öffnet
Und ein Foto vom Aral-Beleg macht
Dann erkennt die OCR Betrag 84,92 €, USt 19 %, Datum 19.04.2026 17:42
Und Hannes wählt Kategorie "Tanken" und Projekt "Bgm. Müller-Schule"
Und speichert
Und der Beleg landet in der lokalen Drift-Outbox mit Idempotency-Key
Und bei Wiederherstellung der Verbindung wird der Beleg nach Server synchronisiert
Und das Foto geht per S3-Multipart mit presigned URL in S3 (Object Lock 10 Jahre)
Und der Status ist "draft", bis Hannes "Einreichen" tippt
Szenario: Grenzfall — BMF-Pauschale Inland 2026, eintägige Reise 9:15 h
Angenommen Hannes hat eine Reise "München → Augsburg, 19.04.2026, 7:30 – 16:45"
Wenn Hannes "Pauschale generieren" tippt
Dann erzeugt das System einen Per-Diem-Eintrag mit 14,00 € für 19.04.2026
Und die Begründung ist "Eintägige Reise > 8h Abwesenheit (§9 Abs. 4a EStG)"
Und der Satz stammt aus bmf_rates für DE, gültig ab 01.01.2026
Und kein Vorsteuer-Abzug wird gebucht (Pauschale ist kein Ausgabe-Beleg)
Szenario: Grenzfall — Bewirtungsbeleg ohne §4(5)1 Nr.2-Pflichtangaben
Angenommen Hannes fotografiert einen Restaurant-Beleg "Da Luigi München" 142,80 €
Und er lässt "Anlass" und "Teilnehmer" leer
Wenn Hannes "Speichern" tippt
Dann warnt die App "Nicht abzugsfähig — §4 Abs. 5 Nr. 2 EStG Pflichtangaben fehlen: Anlass, Teilnehmer"
Und Hannes bestätigt "Trotzdem speichern"
Dann wird der Beleg mit Flag "missing_mandatory_fields=true" gespeichert
Und der Status ist "draft"
Und im Manager-Bulk-Screen erscheint die Zeile oben mit 🔴-Markierung
Und beim späteren DATEV-Export wird der Beleg auf Konto 4655 (nicht abzugsfähig) gebucht, NICHT auf 4663
Szenario: Grenzfall — Auslandstagegeld Schweiz zweitägige Reise
Angenommen Hannes legt eine Reise Basel an: 20.04.2026 08:00 bis 21.04.2026 19:00
Und Zielland "CH"
Wenn Hannes "Pauschale generieren" tippt
Dann erzeugt das System zwei Per-Diem-Einträge:
| entry_date | day_kind | country_iso2 | amount_eur |
| 2026-04-20 | departure | CH | 41,00 |
| 2026-04-21 | return | CH | 41,00 |
Und die Sätze stammen aus bmf_rates für CH, gültig ab 01.01.2026
Und Hannes kann zusätzlich Übernachtungsbelege (in CHF, automatisch EUR-umgerechnet) anhängen
Und kein Inland-Pauschalen-Eintrag wird erzeugt
Szenario: Grenzfall — Multi-Tenant-Isolation
Angenommen ein zweiter Tenant "shk-gebruder-schmidt" mit Beleg-Nr. ER-2026-0098
Wenn Hannes (Tenant musterbetrieb-maler) per API
GET /v1/kern/expenses/receipts/{uuid-aus-shk} abruft
Dann antwortet die API mit 404
Und in Tenant "shk-gebruder-schmidt" wird KEIN Audit-Log-Eintrag erzeugt
Und in der OP-Spesen-Liste von Tenant A taucht der fremde Beleg nirgends auf

  • U-01 BMF-Rate-Lookup per (countryIso2, date) → korrekter Satz (Inland 2026, Schweiz 2026, Italien 2026, Drittland ohne Eintrag → Standard-Satz 20 €/12 € nach BMF-Fallback).
  • U-02 Mitternachtsregel-Klassifikation: tripStart+tripEnd{ departure_day, full_days[], return_day }. Property-based über fast-check mit Zeitspannen 1 h..240 h.
  • U-03 Bewirtungs-Pflichtfeld-Validator: 2⁴ = 16 Fälle (Anlass/Teilnehmer/Ort/Signatur je vorhanden/fehlend) → deductiblePercent = 70 | 0, missingMandatoryFields[].
  • U-04 Vorsteuer-Berechnung Kleinbetrag vs. Vollrechnung: Netto = Brutto / (1 + USt/100), Rundung Cent. Property-based.
  • U-05 Km-Betrag: km_driven × rate_per_km, Cent-Rundung.
  • W-01 Beleg-Formular: USt-Radio-Group ist disabled bei Kategorie per_diem (Pauschale nicht vorsteuerfähig).
  • W-02 Bewirtungs-Formular: Teilnehmer-Liste kann nicht unter 1 (eigene Person) fallen; 1 Person ohne Firma wirft Validierung.
  • W-03 Golden-Test Reise-Assistent de-DE Dark Theme.
  • W-04 Km-Formular wird nur gerendert, wenn user.ownsPrivateVehicle == true.
  • I-01 Multi-Tenant-Isolation: Beleg in Tenant A nicht sichtbar/abrufbar von Tenant B (alle API-Endpunkte × Tenant-Kombinationen).
  • I-02 RLS Scope team: Manager sieht nur Belege der eigenen Team-Member, nicht anderer Manager-Teams.
  • I-03 Idempotenz POST /receipts: Zweifach mit identischem Idempotency-Key → 200 mit identischem Payload (nicht 409).
  • I-04 Reise-Per-Diem-Generierung + erneutes Aufrufen → dieselben Einträge (idempotent).
  • I-05 DATEV-EXTF-Export-Kontenbelegung bei gemischtem Bestand (Bewirtung voll + abzugsfähig, Pauschalen, Km, Tanken Vorsteuer).
  • E-01 Patrol-Szenario: Beleg offline erfassen → online sync → Manager genehmigt → DATEV-Export enthält Buchungssatz. iOS + Android + Chromium + Firefox + WebKit.
  • E-02 Reise-Wizard mit Mitternachtsregel-Edge-Case (Reise 19.04. 23:00 – 20.04. 03:00) → keine Pauschale.
  • E-03 Visual Regression Mobile-Beleg-Form Light/Dark.
  • E-04 Accessibility: axe-core auf Web-Bulk-Genehmigung; Flutter-Semantics-Test für Mobile-Form (VoiceOver-Reihenfolge korrekt).
  • C-01 Hash-Chain-Integrität expense_receipts: Manuelle Mutation nach Freigabe bricht Chain → Detector meldet.
  • C-02 DATEV-EXTF-Validator akzeptiert den Monatsabschluss-Export.
  • C-05 Property-based BMF-Pauschalen: jede generierte Reise produziert gültige Pauschalen-Summe gemäß §9 Abs. 4a EStG; kein Tag überlappt.
  • C-06 S3-Object-Lock-Assertion: Hochgeladenes Foto darf im Test nicht gelöscht werden (Retention-Check gegen MinIO mit Object-Lock in Testcontainer).
  • 20× E-01-Re-Run lokal; OCR-Mock fixiert (keine echte ML-Kit-Inferenz im Test).

Nicht-Ziel Begründung
Firmen-Kreditkarten-Abrechnung (American Express, Pluxee, Hanseatic) V2+. Benötigt OAuth-Integrationen, zeitlich-getriggerte Matchings. Kein Referenzkunde in Phase 1.
SAP Concur / Haufe X360 Spesen-Connector V3+ Enterprise-Segment, nicht Phase 1.
Reisebuchungs-Portal (Flüge, Hotels) Nicht im Zielmarkt KMU-Handwerk.
Automatischer Reise-Preis-Vergleich Nicht in Phase 1.
Mobile Kassenbuch §146a AO TSE-pflichtig, eigenes Feature-Domain.
Bewirtungsnachweis-Signatur des Gastwirts digital Bleibt Foto der Rückseite; elektronische Gastwirt-Signatur ist BMF-rechtlich unklar (AEAO).
Sachbezug-Berechnung bei Mahlzeitsgestellung durch AG §8 Abs. 2 EStG komplex, V2 bei konkretem Kundenbedarf.
Währung jenseits EUR/CHF/GBP/USD MVP: EUR, CHF, GBP, USD. Alles andere via Admin-Konfig in V1.
Anlagenbuchhaltung / Pool-Fahrzeug-Verwaltung Fahrzeug-Pool im Bau-Modul §4.6 / Autolager; nicht Spesen.

Risiko / Annahme Impact Wahrscheinlichkeit Gegenmaßnahme
BMF-Länder-Sätze ändern sich jährlich, Vergessen = falsche Pauschalen hoch hoch Jährlicher Updater-Job mit Reminder 15.12. an Admin; bmf_rates versioniert mit valid_from/valid_to; Nightly-Compliance-Test.
OCR-Qualität bei verknitterten Thermo-Belegen gering mittel hoch Manuelle Feld-Korrektur ist Default-Erwartung; UI macht klar: OCR = Vorschlag. Tests mit Real-Beleg-Korpus.
BR-Freischaltung für GPS wird umgangen (Admin setzt Haken ohne echten Beschluss) hoch mittel Pflicht-Upload BR-Beschluss-PDF + Audit-Eintrag; Rechtsabteilung-Hinweis im Wizard; in AVV-Template vermerkt.
Bewirtungs-Gastwirt-Signatur auf Rückseite nicht überall akzeptiert mittel mittel Kunden-Hotline-Hinweis-Baustein; bei Ablehnung Finanzamt → 30/70 auf 0/100 umstellen, Buchung auf 4655.
Annahme: EUR ist Standardwährung; CHF-Umrechnung Tageskurs mittel mittel Bundesbank-API-Integration als eigenes V1-Ticket; Fallback EZB-Referenzkurs.
Weiterberechnungs-Aufschlag führt zu Kunden-Unstimmigkeiten niedrig mittel Aufschlag pro Kunde-Profil; Transparenz im Rechnungstext.
Doppelter Beleg-Import bei Bluetooth-Belegscanner (Zebra) niedrig niedrig Hardware-Scanner in V2, nicht MVP.

  • Vorbedingung: kern/03-zeiterfassung (Reise-Zeit wird dort erfasst; Spesen hat nur die Kosten-Seite), kern/04-projekte-kunden (Zuordnung + „weiterberechenbar“-Config), kern/11-audit-log, kern/12-auth.
  • Schnittstelle zu:
    • kern/05-rechnungsstellung — weiterberechenbare Spesen landen im Positionsvorschlag der Rechnung (mit Aufschlag, optional).
    • kern/10-datev-integration — Monats-EXTF-Export mit Konten 4660 / 4663 / 4664 / 4652 / 4670 (SKR03) bzw. 6640 / 6644 / 6660 / 6668 (SKR04).
    • On-device ML Kit / Apple Vision für OCR (TECH-STACK §3.2, §7.3).
  • Wird konsumiert von: kern/07-reporting (Spesen pro Projekt), kern/09-dashboard (offene Spesen-KPI für Manager), handwerk/03-bautagebuch (Standort-Referenz wenn Beleg im Baustellen-Geoumkreis).

Status: TBD.

Kandidaten-Profile:

  • Gebäudetechnik-Betrieb 30–60 MA mit regelmäßigen Montage-Außeneinsätzen (2–5 Tage auswärts pro Monat je Monteur).
  • Bauunternehmen mit Projekten in wechselnden Bundesländern und gelegentlichen Ausland-Einsätzen (Schweiz, Österreich).
  • Handwerksbetrieb mit Montagearbeit (Aufzug, Industrieklima) und hohem Km-Anteil pro Mitarbeiter.

Validierungs-Fragen:

  1. Wie läuft Ihre Belegerfassung heute (Papier, Excel, App)? Wie viele Belege/Monat pro Monteur?
  2. Rechnen Sie Ausland-Dienstreisen ab? In welche Länder?
  3. Nutzen Sie Kilometerpauschale bei Mitarbeitern mit Eigen-KFZ? Wie häufig?
  4. Wie häufig macht Ihre Buchhaltung Rückfragen wegen §4 Abs. 5 EStG-Pflichtangaben?
  5. Haben Sie einen Betriebsrat? Wenn ja: Mit welchen Leistungskontroll-Themen habt ihr euch bereits beschäftigt?
  6. Was kosten Sie heute fehlerhafte Spesen-Buchungen (Finanzamt-Rejects, Vorsteuer-Verluste) grob pro Jahr?

Build-Sequenz:

  1. Datenmodell + RLS + Multi-Tenant-Test. expense_receipts, trips, per_diem_entries, mileage_entries mit Hash-Chain.
  2. BMF-Rate-Tabelle + Lookup-Service inkl. versioniertem Import-Kanal. Compliance-Test.
  3. Mobile Beleg-Formular (Standard, ohne OCR) + Upload-Pipeline + S3 Object Lock.
  4. OCR-Integration (ML Kit on-device). UI macht klar: Vorschlag, nicht Wahrheit.
  5. Reise-Wizard mit BMF-Pauschalen-Generator (inkl. Mitternachtsregel, Auslandslogik).
  6. Bewirtungsbeleg-Formular mit §4 Abs. 5 EStG-Validator + 30/70-Buchungssatz.
  7. Km-Erfassung (manuell zuerst, GPS-Track V1.5 mit BR-Workflow).
  8. Manager-Bulk-Genehmigung mit Heuristiken.
  9. DATEV-EXTF-Export (Kontrakt mit §3.10).
  10. Weiterberechnungs-Kopplung an §3.5 Rechnungsstellung.

Risiko-Reihenfolge:

  • Nicht verhandelbar: Multi-Tenant, GoBD Hash-Chain auf Belegen, BMF-Pauschalen-Korrektheit, §4 Abs. 5 EStG Pflichtfeld-Validator.
  • Streichbar bei Zeitnot: Duplikat-Heuristik, OCR-Konfidenz-Feintuning, Ausland-Länder > 10 wichtigsten (Fallback-Satz genügt MVP).

Stop-the-Bus-Triggers:

  • BMF-Pauschale weicht in Test vom offiziellen Satz ab.
  • Beleg ohne Foto wird als „exported“ markierbar.
  • GPS-Track wird ohne BR-Freischaltung aktiv.
  • Multi-Tenant-Bleed in Manager-Bulk-Screen.
  • S3 Object-Lock-Retention ist nicht gesetzt.

Was uns 2027 dankbar macht:

  • Die Trennung expense_receipts (Aufwand) vs. trips (Rahmen) vs. per_diem_entries (Pauschalen-Rechnung) trägt später Auslands-Sonderregelungen ohne Schema-Bruch.
  • BMF-Rate-Tabelle als eigene versionierte Entity lässt sich ohne Code-Deploy jährlich aktualisieren.
  • On-device-OCR (ML Kit) statt Server-OCR spart mittelfristig Bedrock-Token-Kosten erheblich (Skalierung mit Mitarbeiter-Zahl).

Letzte Aktualisierung: 2026-04-19.

Für Entwickler — API-Endpoints19
MethodePfadAuthZweck
POST/v1/kern/expenses/export/datev-extfbearerAuthDATEV-EXTF-Export (blockiert)
POST/v1/kern/expenses/mileagebearerAuthKilometerabrechnung anlegen
GET/v1/kern/expenses/receiptsbearerAuthBelege auflisten (cursor-paginiert)
POST/v1/kern/expenses/receiptsbearerAuthBeleg anlegen (Entwurf)
GET/v1/kern/expenses/receipts/{id}bearerAuthBeleg-Detail inkl. Bewirtungsangaben
PATCH/v1/kern/expenses/receipts/{id}bearerAuthBeleg ändern (nur draft/submitted/rejected)
POST/v1/kern/expenses/receipts/{id}/approvebearerAuthBeleg genehmigen (submitted → approved)
GET/v1/kern/expenses/receipts/{id}/photobearerAuthBeleg-Foto (presigned S3 URL)
POST/v1/kern/expenses/receipts/{id}/rejectbearerAuthBeleg ablehnen (submitted → rejected)
POST/v1/kern/expenses/receipts/{id}/submitbearerAuthBeleg einreichen (draft/rejected → submitted)
GET/v1/kern/expenses/receipts/{receiptId}/genehmigungenbearerAuthGenehmigungs-Historie eines Belegs
POST/v1/kern/expenses/receipts/{receiptId}/genehmigungen/approve-bhbearerAuthGenehmigungs-Workflow Stufe 3 — Buchhaltungs-Freigabe
POST/v1/kern/expenses/receipts/{receiptId}/genehmigungen/approve-vgbearerAuthGenehmigungs-Workflow Stufe 2 — Vorgesetzten-Freigabe
POST/v1/kern/expenses/receipts/{receiptId}/genehmigungen/reimbursebearerAuthGenehmigungs-Workflow Stufe 4 — Erstattung
POST/v1/kern/expenses/receipts/{receiptId}/genehmigungen/rejectbearerAuthGenehmigungs-Workflow — ablehnen
POST/v1/kern/expenses/receipts/{receiptId}/genehmigungen/submitbearerAuthGenehmigungs-Workflow Stufe 1 — einreichen
POST/v1/kern/expenses/receipts/bulk-approvebearerAuthBelege im Stapel genehmigen
POST/v1/kern/expenses/tripsbearerAuthDienstreise anlegen
POST/v1/kern/expenses/trips/{id}/generate-per-diembearerAuthVerpflegungsmehraufwand generieren (Per-Diem)