Spesen- und Reisemanagement (Beleg-OCR, BMF-Pauschalen, Bewirtung, Kilometer, Weiterberechnung)
Feinkonzept — Spesen & Reisemanagement (§3.6)
Abschnitt betitelt „Feinkonzept — Spesen & Reisemanagement (§3.6)“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.
1. Header & Metadaten
Abschnitt betitelt „1. Header & Metadaten“feature_id: kern/06-spesen-reisemanagementtitle: Spesen- und Reisemanagement (Beleg-OCR, BMF-Pauschalen, Bewirtung, Kilometer, Weiterberechnung)funktionsumfang_ref: §3.6roadmap_horizont: MVP # Beleg + BMF-Pauschalen + Bewirtung Day 1; Kreditkarten-Integration V2plattformen: mobile: vollständig # Hauptpfad: Beleg vor Ort fotografieren web: mit-Bulk # Manager-Genehmigung, DATEV-Export desktop: ausowner_rolle: Mitarbeiter # Erfassung; Manager genehmigt; Buchhaltung exportiertmodul_gate_flag: module.kern.expensescompliance_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 (Verpflegungsmehraufwand)", "§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/Genehmigungabhä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-auth2. Kontext & Problem
Abschnitt betitelt „2. Kontext & Problem“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.
3. Personas & Rollen
Abschnitt betitelt „3. Personas & Rollen“| 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.
4. User-Stories
Abschnitt betitelt „4. User-Stories“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.5. Funktionale Anforderungen
Abschnitt betitelt „5. Funktionale Anforderungen“5.1 Mobile App (📱)
Abschnitt betitelt „5.1 Mobile App (📱)“- 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 = truehinterlegt 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).
5.2 Web-App (🌐)
Abschnitt betitelt „5.2 Web-App (🌐)“- 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.
5.3 Cross-Plattform (🔄)
Abschnitt betitelt „5.3 Cross-Plattform (🔄)“- 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).
5.4 Admin-Konfiguration
Abschnitt betitelt „5.4 Admin-Konfiguration“- 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.
5.5 Roadmap-Schichtung
Abschnitt betitelt „5.5 Roadmap-Schichtung“| 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 | ✅ |
6. Mockups & Flows
Abschnitt betitelt „6. Mockups & Flows“HTML-Hero-Mockup: 06-spesen-reisemanagement.html — Mobile-Beleg-Erfassung + Web-Bulk-Genehmigung.
6.1 Mobile — Beleg-Capture nach Foto
Abschnitt betitelt „6.1 Mobile — Beleg-Capture nach Foto“┌────────────────────────────────┐│ ← 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] │└────────────────────────────────┘6.2 Mobile — Bewirtungsbeleg Spezialformular
Abschnitt betitelt „6.2 Mobile — Bewirtungsbeleg Spezialformular“┌────────────────────────────────┐│ ← 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] │└───────────┴────────────────────────────────────────────────────────────────────┘6.6 Web — DATEV-EXTF-Vorschau Reisekosten
Abschnitt betitelt „6.6 Web — DATEV-EXTF-Vorschau Reisekosten“┌────────────────────────────────────────────────────────────────────────────────┐│ 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] │└────────────────────────────────────────────────────────────────────────────────┘7. Datenmodell-Skizze
Abschnitt betitelt „7. Datenmodell-Skizze“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-UserCREATE 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).
8. API-Endpunkte
Abschnitt betitelt „8. API-Endpunkte“| 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.submittedexpense.approved/expense.rejectedexpense.exported(mit DATEV-Batch-Referenz)trip.created
9. Offline-Profil
Abschnitt betitelt „9. Offline-Profil“| 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.
10. Compliance-Mapping
Abschnitt betitelt „10. Compliance-Mapping“10.1 GoBD
Abschnitt betitelt „10.1 GoBD“- Append-only-Tabelle:
expense_receiptsund alle Subtabellen (entertainment_details,participants,trips,per_diem_entries,mileage_entries) mithashPrev/hashSelf. Ab Statusapprovedist 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“.
10.2 ArbZG
Abschnitt betitelt „10.2 ArbZG“— nicht zutreffend für das Spesen-Feature direkt. Reise-Zeit als Arbeitszeit ist Thema in §3.1 Zeiterfassung; dort greifen die ArbZG-Prüfungen.
10.3 VOB/B
Abschnitt betitelt „10.3 VOB/B“— nicht zutreffend.
10.4 DSGVO
Abschnitt betitelt „10.4 DSGVO“- 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.
10.5 BFSG / WCAG 2.2 AA
Abschnitt betitelt „10.5 BFSG / WCAG 2.2 AA“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ß:
#FF6B1Aauf#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ß:#B5391Aauf#FAFAF7→ 5,58:1 ≥ 4,5:1 ✓.
10.6 BetrVG §87(1)6 — Standort-Erfassung
Abschnitt betitelt „10.6 BetrVG §87(1)6 — Standort-Erfassung“- 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.
10.7 Weitere Spezial-Compliance
Abschnitt betitelt „10.7 Weitere Spezial-Compliance“- §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.
11. Edge-Cases
Abschnitt betitelt „11. Edge-Cases“| # | 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). |
12. Akzeptanzkriterien (Gherkin)
Abschnitt betitelt „12. Akzeptanzkriterien (Gherkin)“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 auf13. Test-Cases
Abschnitt betitelt „13. Test-Cases“13.1 Unit-Tests
Abschnitt betitelt „13.1 Unit-Tests“- 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 überfast-checkmit 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.
13.2 Widget-/Component-Tests
Abschnitt betitelt „13.2 Widget-/Component-Tests“- 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-DEDark Theme. - W-04 Km-Formular wird nur gerendert, wenn
user.ownsPrivateVehicle == true.
13.3 Integrations-Tests
Abschnitt betitelt „13.3 Integrations-Tests“- 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).
13.4 E2E-Tests
Abschnitt betitelt „13.4 E2E-Tests“- 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).
13.5 Compliance-Tests
Abschnitt betitelt „13.5 Compliance-Tests“- 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).
13.6 Flakiness-Schutz
Abschnitt betitelt „13.6 Flakiness-Schutz“- 20× E-01-Re-Run lokal; OCR-Mock fixiert (keine echte ML-Kit-Inferenz im Test).
14. Nicht-Ziele
Abschnitt betitelt „14. Nicht-Ziele“| 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. |
15. Risiken & offene Annahmen
Abschnitt betitelt „15. Risiken & offene Annahmen“| 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. |
16. Abhängigkeiten
Abschnitt betitelt „16. Abhängigkeiten“- 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).
17. Referenzkunde-Slot
Abschnitt betitelt „17. Referenzkunde-Slot“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:
- Wie läuft Ihre Belegerfassung heute (Papier, Excel, App)? Wie viele Belege/Monat pro Monteur?
- Rechnen Sie Ausland-Dienstreisen ab? In welche Länder?
- Nutzen Sie Kilometerpauschale bei Mitarbeitern mit Eigen-KFZ? Wie häufig?
- Wie häufig macht Ihre Buchhaltung Rückfragen wegen §4 Abs. 5 EStG-Pflichtangaben?
- Haben Sie einen Betriebsrat? Wenn ja: Mit welchen Leistungskontroll-Themen habt ihr euch bereits beschäftigt?
- Was kosten Sie heute fehlerhafte Spesen-Buchungen (Finanzamt-Rejects, Vorsteuer-Verluste) grob pro Jahr?
18. Senior-Berater-Empfehlung
Abschnitt betitelt „18. Senior-Berater-Empfehlung“Build-Sequenz:
- Datenmodell + RLS + Multi-Tenant-Test.
expense_receipts,trips,per_diem_entries,mileage_entriesmit Hash-Chain. - BMF-Rate-Tabelle + Lookup-Service inkl. versioniertem Import-Kanal. Compliance-Test.
- Mobile Beleg-Formular (Standard, ohne OCR) + Upload-Pipeline + S3 Object Lock.
- OCR-Integration (ML Kit on-device). UI macht klar: Vorschlag, nicht Wahrheit.
- Reise-Wizard mit BMF-Pauschalen-Generator (inkl. Mitternachtsregel, Auslandslogik).
- Bewirtungsbeleg-Formular mit §4 Abs. 5 EStG-Validator + 30/70-Buchungssatz.
- Km-Erfassung (manuell zuerst, GPS-Track V1.5 mit BR-Workflow).
- Manager-Bulk-Genehmigung mit Heuristiken.
- DATEV-EXTF-Export (Kontrakt mit §3.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
| Methode | Pfad | Auth | Zweck |
|---|---|---|---|
| POST | /v1/kern/expenses/export/datev-extf | bearerAuth | DATEV-EXTF-Export (blockiert) |
| POST | /v1/kern/expenses/mileage | bearerAuth | Kilometerabrechnung anlegen |
| GET | /v1/kern/expenses/receipts | bearerAuth | Belege auflisten (cursor-paginiert) |
| POST | /v1/kern/expenses/receipts | bearerAuth | Beleg anlegen (Entwurf) |
| GET | /v1/kern/expenses/receipts/{id} | bearerAuth | Beleg-Detail inkl. Bewirtungsangaben |
| PATCH | /v1/kern/expenses/receipts/{id} | bearerAuth | Beleg ändern (nur draft/submitted/rejected) |
| POST | /v1/kern/expenses/receipts/{id}/approve | bearerAuth | Beleg genehmigen (submitted → approved) |
| GET | /v1/kern/expenses/receipts/{id}/photo | bearerAuth | Beleg-Foto (presigned S3 URL) |
| POST | /v1/kern/expenses/receipts/{id}/reject | bearerAuth | Beleg ablehnen (submitted → rejected) |
| POST | /v1/kern/expenses/receipts/{id}/submit | bearerAuth | Beleg einreichen (draft/rejected → submitted) |
| GET | /v1/kern/expenses/receipts/{receiptId}/genehmigungen | bearerAuth | Genehmigungs-Historie eines Belegs |
| POST | /v1/kern/expenses/receipts/{receiptId}/genehmigungen/approve-bh | bearerAuth | Genehmigungs-Workflow Stufe 3 — Buchhaltungs-Freigabe |
| POST | /v1/kern/expenses/receipts/{receiptId}/genehmigungen/approve-vg | bearerAuth | Genehmigungs-Workflow Stufe 2 — Vorgesetzten-Freigabe |
| POST | /v1/kern/expenses/receipts/{receiptId}/genehmigungen/reimburse | bearerAuth | Genehmigungs-Workflow Stufe 4 — Erstattung |
| POST | /v1/kern/expenses/receipts/{receiptId}/genehmigungen/reject | bearerAuth | Genehmigungs-Workflow — ablehnen |
| POST | /v1/kern/expenses/receipts/{receiptId}/genehmigungen/submit | bearerAuth | Genehmigungs-Workflow Stufe 1 — einreichen |
| POST | /v1/kern/expenses/receipts/bulk-approve | bearerAuth | Belege im Stapel genehmigen |
| POST | /v1/kern/expenses/trips | bearerAuth | Dienstreise anlegen |
| POST | /v1/kern/expenses/trips/{id}/generate-per-diem | bearerAuth | Verpflegungsmehraufwand generieren (Per-Diem) |