User Stories — Epic 6: Finanzen

Methodik: Connextra (Als/möchte/damit) + Gherkin-Akzeptanzkriterien (Gegeben/Wenn/Dann) + INVEST-Qualitätsgate. Sprache der Stories DE; Fachbegriffe/Status/Codes wie im Schema (stabile movement_type-/payment_method-Codes, nie übersetzt). Bezug: Modul 6 (15-finanzen.md), Schema Dok 30 §2.7 invoices, §4 (movements/payment_links/monthly_closings), §11 (Auto-Posting AP-1..AP-9), §13 (RLS-Matrix); Compliance Dok 45 (OR 957–963b / GeBüV / MWSTG). Rollen-Codes kanonisch (UPPERCASE): ADMIN, BUCHHALTUNG, OPERATIONS, FAHRER, AFFILIATE, READONLY. IDs: US-FIN-NN (stabil). Status: Entwurf (zur Freigabe).


Personas in diesem Epic

RollePersonBezug zu den Finanzen
BUCHHALTUNGMarielaKernnutzerin: manuelle Erfassung, Zahlungen, Debitoren/Kreditoren, Monatsabschluss (open↔review), Export — das Tagesgeschäft im Ledger
ADMINMarcelalles + Storno (void), Periode close/reopen, Lookup-/MWST-Pflege, Anfangssalden
OPERATIONSMarkusnur lesend im Ledger (operative Übersicht Kasse/Debitoren); löst über Logistik/Depot Auto-Postings aus
READONLYTreuhänder/Gastnur lesen; offizielle Schnittstelle ist der Treuhänder-Export (CSV/Excel/PDF + MWST-Kennzahlen)
SystemAuto-PostingEdge-/Server-Function post_movement() via service_role — bucht Events idempotent ins Ledger, nie über Client-Keys

INVEST-Gate (für alle Stories dieses Epics geprüft): jede Story ist unabhängig schneidbar, verhandelbar, liefert Geschäftswert, schätzbar, klein genug für einen Schritt und über die Gherkin-Szenarien testbar (inkl. Negativfälle, RLS, Revisionssicherheit).


US-FIN-01 — Manuelle Bewegung erfassen (Erfassung_Digitar-Ersatz)

Als BUCHHALTUNG möchte ich einen Geldfluss als einzelne movements-Zeile erfassen (Vorgang, Zahlweise, Datum, Partei, Gesamtbetrag, Bezahlt, Notiz), damit jeder Cash-/Bank-Vorgang genau einmal und revisionssicher im Ledger landet.

Akzeptanzkriterien

  • Gegeben ich wähle movement_type_code + payment_method_code aus den Bottom-Sheet-Selects und gebe Datum/Betrag, wenn ich speichere, dann entsteht eine movements-Zeile mit source_type='manual', und period_key, cash_in/out, bank, open_debt, status ergeben sich abgeleitet aus v_movements (nicht gespeichert).
  • Gegeben eine cash-Zahlweise (CASH), wenn die Zeile gebucht ist, dann erzeugt sie entweder cash_in oder cash_out, nie beides (disjunkte Typ-Mengen).
  • Gegeben eine bank-Zahlweise (BANK/TWINT/CARD), dann fliesst der Betrag in bank_chf, nicht in die Kasse.
  • Gegeben total_chf < paid_chf bei INVOICE, wenn ich speichere, dann warnt das System „Überzahlung" und speichert nur mit allow_overpayment.

Traceability: movements (§3.2/Dok 30 §4.1), v_movements (§3.5), Workflow A (§4). (Deckt data-01 ab: alle Beträge in einer Geld-Domäne numeric(12,2).)


US-FIN-02 — Auto-Posting: Event erzeugt Bewegung (idempotent)

Als System (Auto-Posting via service_role) möchte ich aus jedem operativen Event (Rechnung, Zahlung, Depot, Provision) genau eine movements-Zeile per Upsert erzeugen, damit das Ledger die Single Source of Truth bleibt und Re-Delivery/Races kein Duplikat erzeugen.

Akzeptanzkriterien

  • Gegeben ein operatives Event mit source_type+source_id, wenn post_movement() läuft, dann macht es ein Upsert auf movements mit onConflict (source_type, source_id) → ein Event = max. eine Buchung.
  • Gegeben dasselbe Event wird erneut zugestellt (Retry/Race), wenn post_movement() erneut läuft, dann wird dieselbe Zeile überschrieben — kein Duplikat (movements_source_uniq).
  • Gegeben der Ursprung wird korrigiert (z. B. Betrag geändert), wenn das Folge-Event eintrifft, dann überschreibt der Upsert die bestehende Zeile statt eine neue anzulegen.
  • Gegeben ein Auto-Posting-Versuch durch einen Client-Key, dann wird er abgelehnt — Auto-Posting läuft ausschliesslich über service_role (kein Client kann Quellbuchungen fälschen).
  • Gegeben eine manuelle Zeile (source_id=NULL), dann greift das Unique bewusst nicht (mehrere manuelle Buchungen erlaubt).

Traceability: Auto-Posting-Engine §4-B, Dok 30 §11.1–11.3 (AP-1..AP-9, Pseudocode post_movement), movements_source_uniq (§11.2). (Deckt Idempotenz-Härtung 8.9 ab.)


US-FIN-03 — Rechnung im Ledger buchen (Forderung, genau einmal)

Als System (ausgelöst durch Rechnungsstellung in Modul 4/5) möchte ich bei invoices-Anlage eine INVOICE-Forderungsbuchung erzeugen, damit die Kundenforderung genau einmal im Ledger erscheint — ohne Doppelzählung.

Akzeptanzkriterien

  • Gegeben eine neu angelegte invoices-Zeile, wenn AP-1 läuft, dann entsteht movements (INVOICE, source_type='invoice_issued', source_id=invoices.id, total_chf=invoices.total_chf, paid_chf=0, Kanal OPEN, period_key aus issue_date) und invoices.movement_id wird gesetzt (Buchungsbrücke).
  • Gegeben eine angenommene Offerte, wenn sie konvertiert wird, dann wird keine RECEIVABLE gebucht — die Forderung entsteht nur bei Rechnungsstellung (F-06/AP-10 entfällt; keine RECEIVABLE+INVOICE-Doppelzählung in v_debitoren/v_dashboard).
  • Gegeben die Rechnungssumme wird im Ursprung korrigiert, wenn AP-1 erneut feuert, dann überschreibt der Upsert dieselbe INVOICE-Zeile (Betrag aktualisiert, kein Duplikat).

Traceability: Dok 30 §2.7 (invoices, movement_id), §11.1 (AP-1), F-06 (§2 Finanzen).


US-FIN-04 — Kundenzahlung erfassen mit expliziter Verknüpfung

Als BUCHHALTUNG möchte ich zu einer offenen INVOICE eine Zahlung erfassen und sie explizit an die Rechnung hängen, damit die Restschuld prüfbar (statt per String-Match) berechnet wird.

Akzeptanzkriterien

  • Gegeben eine offene INVOICE, wenn ich „Zahlung erfassen" auslöse, dann entsteht movements (INVOICE_PAYMENT, source_type='debtor_payment') und eine payment_links-Zeile (payment_id, target_id=INVOICE-movement, amount_chf > 0).
  • Gegeben eine Splitzahlung auf mehrere Rechnungen, wenn ich erfasse, dann entstehen mehrere payment_links-Zeilen, deren Summe ≤ paid_chf ist.
  • Gegeben dieselbe (payment_id, target_id)-Kombination, wenn sie doppelt angelegt würde, dann wird sie durch payment_links_uniq abgelehnt.
  • Gegeben Altdaten ohne Link, wenn die Restschuld berechnet wird, dann greift der Migrations-Fallback reference + contact_id (View deckt beide Pfade ab).

Traceability: Dok 30 §4.2 (payment_links), §11.1 (AP-2), Workflow C (§4).


US-FIN-05 — Rechnungsstatus OPEN/PARTIAL/PAID (Trigger)

Als BUCHHALTUNG möchte ich, dass sich invoices.status automatisch aus den zugeordneten Zahlungen ergibt, damit Mahnwesen und Debitoren auf einem verlässlichen Status fussen.

Akzeptanzkriterien

  • Gegeben Zahlungen sind via payment_links der INVOICE-Bewegung zugeordnet, wenn der Trigger auf payment_links/movements feuert, dann schreibt er invoices.status: Σ ≥ total_chfPAID, 0 < Σ < total_chfPARTIAL, Σ = 0 → bleibt OPEN (F-04, bewusste Ausnahme von „Status nur aus Views"; der Restschuld-Betrag bleibt View-abgeleitet).
  • Gegeben der Rechnungsstatus, dann dient er Mahnwesen/Debitoren — er ist kein Auslöser der Affiliate-Provision (Geschäftsentscheid 2026-06-28 #19): die Provision entsteht bei Offerte-Konversion (pending) und wird fällig/freigegeben beim Tracking-DELIVERED der Sendung (US-AFF-05/US-AFF-06). Die frühere PAID-Bindung ist aufgehoben.
  • Gegeben eine Teil- oder Vollzahlung, dann wird kein Provisions-Trigger ausgelöst (Provisions-Lifecycle ist von invoices.status entkoppelt).

Traceability: Dok 30 §2.7 (F-04), Workflow C Schritt 4 (§4). (Entscheid 2026-06-28 #19: Provisions-Auslöser = DELIVERED, nicht PAID/Zahlung — vormals 🔲 E14, jetzt entschieden.)


US-FIN-06 — Debitoren & Restschuld (abgeleitet)

Als BUCHHALTUNG möchte ich offene Kundenforderungen mit Restschuld und Ampel sehen, damit ich weiss, wer noch zahlen muss — ohne dass ein Saldo doppelt gespeichert wird.

Akzeptanzkriterien

  • Gegeben Forderungen vom Typ INVOICE/RECEIVABLE/DEPOSIT_CASH, wenn ich v_debitoren öffne, dann sehe ich nur Zeilen mit open_debt_chf > 0.05, jeweils mit status_code (🟢 PAID / 🟠 PARTIAL / 🔴 OPEN).
  • Gegeben eine Zahlung wird erfasst, wenn die View neu rechnet, dann sinkt open_debt_chf = max(0, total − Σ Zahlungen) (primär über payment_links, Fallback reference + contact_id) — Restschuld nie negativ.
  • Gegeben Σ Zahlungen > total (Überzahlung), dann ist open_debt_chf = 0 und ein Warn-Badge „Überzahlung" erscheint.

Traceability: v_debitoren (§3.6), v_movements.open_debt_chf (§3.5), Validierung Restschuld-Match (§7).


US-FIN-07 — Interner Transfer Kasse ↔ Bank (Balance-Invariante)

Als BUCHHALTUNG möchte ich Geld zwischen Kasse, Bank und Twint umbuchen (z. B. CASH_TO_BANK), damit Kassen- und Bankbestand stimmen — ohne dass der Transfer als Ertrag/Aufwand oder als offene Schuld erscheint.

Akzeptanzkriterien

  • Gegeben ich wähle CASH_TO_BANK mit Betrag/Datum, wenn ich speichere, dann entsteht der Transfer mit gemeinsamem transfer_group_id, Status INTERNAL_TRANSFER, open_debt_chf = 0, und er ist aus Debitoren/Kreditoren/Erfolgsrechnung ausgeschlossen.
  • Gegeben ein Transfer mit zwei Beinen (transfer_group_id), wenn die Beträge der beiden Beine nicht übereinstimmen, dann wird die Buchung abgelehnt — die Balance-Invariante erzwingt gleiche Beträge je Gruppe (Negativszenario: unbalancierter Transfer).
  • Gegeben ein Transfer würde den Kassenbestand negativ machen, wenn ich speichere, dann erscheint eine Soft-Warnung (kein harter Block; realer Bargeldbestand kann abweichen).

Traceability: movement_types.is_internal_transfer (§3.1), Workflow D (§4), Validierung interner Transfer (§7). (Deckt data-05 ab: transfer_group_id-Balance-Invariante per Trigger erzwungen — sonst unbalancierte interne Transfers.)


US-FIN-08 — Laufender Kassensaldo & Dashboard-KPIs

Als OPERATIONS/BUCHHALTUNG möchte ich den chronologischen Kassenverlauf mit laufendem Saldo sowie die Finanz-KPIs sehen, damit ich Liquidität und offene Posten auf einen Blick erfasse.

Akzeptanzkriterien

  • Gegeben zahlungswirksame Bewegungen, wenn ich die Kasse (v_kasse) öffne, dann sehe ich je Zeile cash_in/cash_out und einen berechneten laufenden Saldo (running_cash_chf, Window-Funktion) — der Saldo ist nicht gespeichert.
  • Gegeben das Finanz-Dashboard, wenn ich es öffne, dann zeigt v_dashboard Kasse-netto, Bank-Eingang/-Ausgang, Debitoren offen, Kreditoren offen, Anzahlungen offen.
  • Gegeben OPERATIONS, wenn er Kasse/Dashboard öffnet, dann kann er lesen, aber im Ledger nichts schreiben (Fussnote 9, §13).

Traceability: v_kasse/v_dashboard (§3.6), RLS-Matrix movements OPS = R (Dok 30 §13).


US-FIN-09 — Kreditoren manuell erfassen (Lieferanten)

Als BUCHHALTUNG möchte ich Lieferantenrechnungen und -zahlungen als manuelle Bewegungen erfassen, damit offene Kreditoren mit Priorität/Fälligkeit sichtbar sind — auch ohne eigene Kreditoren-Tabelle.

Akzeptanzkriterien

  • Gegeben eine Lieferantenrechnung, wenn ich sie erfasse, dann entsteht eine manuelle movements-Zeile (source_type='manual', SUPPLIER_DEBT/EXPENSE) — kein Auto-Posting (AP-7/AP-8 entfallen, F-02).
  • Gegeben eine Lieferantenzahlung, wenn ich sie erfasse, dann entsteht SUPPLIER_PAYMENT und optional payment_links auf die SUPPLIER_DEBT.
  • Gegeben offene Kreditoren, wenn ich v_kreditoren öffne, dann sind sie nach Priorität sortiert (Überfällig/Bald fällig/Offen/Bezahlt; „Dringend" via Flag 🔲).

Traceability: F-02 (§2 Finanzen), v_kreditoren (§3.6), Dok 30 §11.1 (AP-7/AP-8 entfallen).


US-FIN-10 — Anzahlung/Depot buchen & verknüpfen

Als System (ausgelöst durch Depot-Order/-Zahlung in Modul 5/6) möchte ich Depot-Soll und Depot-Abono idempotent buchen und explizit verknüpfen, damit Anzahlungsstand und Restbetrag sicher zusammenfinden.

Akzeptanzkriterien

  • Gegeben eine Depot-Order, wenn AP-3 läuft, dann entsteht die DEPOSIT_CASH-Soll-Bewegung (source_type='deposit_order', total=deposit_total_chf, paid=0) und deposit_orders.movement_id wird gesetzt.
  • Gegeben eine Depot-Zahlung, wenn AP-4 läuft, dann entsteht der Depot-Abono (source_type='deposit_payment', Kanal = payment_methods.channel) und payment_links(payment_id=Abono, target_id=Depot-Soll) — die Soll-Bewegung wird über deposit_payments.deposit_order_id → deposit_orders.movement_id gefunden, nicht per reference-Match (F-14).
  • Gegeben eine Depot-Order ohne Zahlung, dann zeigt v_anzahlungen Status DEPOSIT_NO_PAYMENT (🟡).
  • Gegeben ein verfallenes Depot (FORFEITED), wenn AP-6 bucht, dann als INCOME (Ertrag, 0 Cash) — 🔲 ob Ertrag oder neutral ist Geschäftsentscheid.

Traceability: Dok 30 §11.1 (AP-3/AP-4/AP-6), F-14, v_anzahlungen (§3.6), Workflow B (§4). (Entscheid-abhängig: verfallenes Depot als INCOME vs. neutral — 🔲 AP-6.)


US-FIN-11 — Monatsabschluss & Periodensperre (close)

Als ADMIN/BUCHHALTUNG möchte ich einen Monat prüfen und abschliessen, damit die Periode revisionssicher gesperrt ist (OR 957 / GeBüV).

Akzeptanzkriterien

  • Gegeben ein Monat in v_monatskontrolle, wenn offene Debitoren/Kreditoren/Anzahlungen > 0 sind, dann steht monthly_closings.status='review' und der Button „Periode abschliessen" weist auf die offenen Posten hin.
  • Gegeben ich schliesse ab, wenn status='closed' gesetzt wird, dann werden opening/closing cash+bank als Snapshot, closed_at/closed_by geschrieben und die Periode ist gesperrt.
  • Gegeben eine closed-Periode, wenn jemand eine Bewegung einfügen/ändern oder eine Zeile per Datumswechsel in diese Periode verschieben will, dann blockiert fn_guard_closed_period() die Mutation (auch für Auto-Posting → fällt in die offene Folgeperiode).
  • Gegeben status nur open/review/closed zulässig, dann wird jeder andere Wert vom Check-Constraint abgelehnt.

Traceability: monthly_closings (Dok 30 §4.3), fn_guard_closed_period() (§11.2 / §13 Querschnitts-Invarianten), Workflow E (§4), Compliance §2.5.


US-FIN-12 — Periode wiedereröffnen (audit-pflichtig)

Als ADMIN möchte ich eine abgeschlossene Periode nur auditiert wiedereröffnen können, damit Nachbuchungen nachvollziehbar bleiben und keine stille Rückdatierung möglich ist.

Akzeptanzkriterien

  • Gegeben eine closed-Periode, wenn ich „Reopen" auslöse, dann dürfen das nur ADMIN, es werden reopened_at/reopened_by gesetzt und audit_log erhält action='reopen_period' → Korrekturspur bleibt sichtbar.
  • Gegeben BUCHHALTUNG, wenn sie eine closed-Periode wiedereröffnen will, dann wird es abgelehnt (Reopen nur ADMIN; BUCHHALTUNG bewegt nur open↔review, Dok 30 §13).
  • Gegeben eine Nachbuchung ohne Reopen, wenn sie nötig ist, dann ist die Alternative eine Buchung in der offenen Folgeperiode mit Korrektur-Referenz.

Traceability: monthly_closings.reopened_* (Dok 30 §4.3), Compliance §2.5 (Art. 9), RLS-Matrix (§13).


US-FIN-13 — Beleg verknüpfen & Storno statt Löschen

Als BUCHHALTUNG/ADMIN möchte ich Belege an Bewegungen hängen und Fehlbuchungen ausschliesslich stornieren (nie hart löschen), damit das Ledger ein unveränderbares Journal bleibt (OR 957 / GeBüV).

Akzeptanzkriterien

  • Gegeben eine Bewegung, wenn ich einen Beleg ablege, dann verknüpft storage_objects(owner_type='movement', owner_id) ihn eindeutig (10-Jahres-Aufbewahrung retain_until).
  • Gegeben eine Fehlbuchung, wenn ich sie korrigiere, dann geht das nur via void-RPC (voided_at/voided_by/void_reason) durch ADMIN/BUCHHALTUNG + Neubuchung — die Original-Zeile bleibt im Audit, die View filtert sie aus.
  • Gegeben ein hartes DELETE auf movements, dann wird es für jede Rolle (inkl. ADMIN) blockiert (for delete using (false)).
  • Gegeben eine quellen-erzeugte Zeile (source_type ≠ 'manual'), wenn jemand sie direkt im Ledger ändern will, dann ist sie read-only — nur der operative Ursprung wird editiert (Upsert).
  • Gegeben eine stornierte Zahlung, wenn das void greift, dann kaskadiert es über payment_links (payment-void) — und weil payment_links.payment_id auf restrict statt cascade delete steht, kann kein hartes Löschen das No-Delete-Ledger umgehen.

Traceability: movements.voided_at (Dok 30 §4.1), payment_links (§4.2), storage_objects (Compliance §1.4/§2.8), RLS mov_no_delete (§3.7), Querschnitts-Invarianten (§13). (Deckt data-04 ab: payment_links.payment_id on delete restrict — kein Cascade-Delete am No-Delete-Ledger vorbei. Deckt append-only/Revisionssicherheit GeBüV ab.)


US-FIN-14 — Audit-Spur jeder Mutation (append-only)

Als READONLY (Treuhänder) möchte ich, dass jede Mutation an movements und monthly_closings unveränderbar protokolliert wird, damit die Revision wer/wann/alt→neu lückenlos nachvollziehen kann.

Akzeptanzkriterien

  • Gegeben eine schreibende Operation auf movements/monthly_closings, wenn sie ausgeführt wird, dann schreibt fn_audit() (SECURITY DEFINER) genau eine audit_log-Zeile mit old_data/new_data (JSONB), diff, actor_user_id, denormalisiertem actor_email und DB-seitigem Zeitstempel at.
  • Gegeben das audit_log, wenn jemand (auch ADMIN oder service_role) eine Zeile ändern/löschen will, dann wird es durch Grant-Entzug + Rules (do instead nothing) verhindert — append-only.
  • Gegeben READONLY/Treuhänder, wenn er das Journal liest, dann kann er lesen, aber nichts mutieren; AFFILIATE hat keinen Zugriff.

Traceability: audit_log (Dok 30 §6.1, K-15), Compliance §2.1–2.4, RLS-Matrix audit_log (§13). (Deckt append-only/Revisionssicherheit GeBüV ab.)


US-FIN-15 — Finanz-RLS: Rollen-Trennung (RLS-Negativfall)

Als ADMIN möchte ich, dass die Schreib-/Leserechte am Ledger strikt rollengebunden sind, damit keine Rolle fremde oder gesperrte Finanzdaten manipulieren kann (revDSG + Least Privilege).

Akzeptanzkriterien

  • Gegeben BUCHHALTUNG/ADMIN, wenn sie eine Bewegung ändern, dann nur falls source_type='manual' und Periode nicht closed (mov_update-Policy); sonst abgelehnt.
  • Gegeben OPERATIONS, dann nur Lesezugriff im Ledger (kein Insert/Update/Delete).
  • Gegeben FAHRER, dann sieht er nur eigene Logistik-Quellen (source_type IN ('deposit_order','deposit_payment')) und kann sonst nichts (Fussnote 10).
  • Gegeben AFFILIATE, wenn er Finanzdaten abruft, dann kein Zugriff (movements, payment_links, audit_log — harte Mandantentrennung).
  • Gegeben READONLY/Treuhänder, dann nur lesend über alle Finanz-Views.

Traceability: RLS-Matrix Dok 30 §13 (movements/payment_links/monthly_closings, Fussnoten 8–10), Beispiel-Policies §3.7, Compliance §4.5/§8.2–8.3. (Deckt revDSG-Mandantentrennung + Least Privilege ab.)


US-FIN-16 — MWST erfassen mit mitgespeichertem Satz (schema-ready)

Als BUCHHALTUNG möchte ich, dass bei aktiver MWST der angewandte Satz auf der Bewegung mitgespeichert wird, damit ein späterer Satzwechsel keine bereits gebuchten Beträge verfälscht (Snapshot-Stabilität).

Akzeptanzkriterien

  • Gegeben vat_enabled=true und vat_rates.active=true, wenn ich eine Bewegung erfasse, dann verlangt das Formular einen vat_code, und vat_amount_chf wird im Schreibpfad (Server-Action bzw. post_movement()) aus vat_rates.rate_pct berechnet und auf der Zeile gespeichert — nicht in einer View, keine generated column (da rate_pct in Fremdtabelle).
  • Gegeben eine gespeicherte MWST-Bewegung, wenn sich der Satz in vat_rates später ändert (neue valid_from), dann bleibt der angewandte Satz auf der historischen Zeile erhalten (mitgespeichert) — kein Snapshot-Drift.
  • Gegeben ein Re-Upsert einer quellen-erzeugten MWST-Buchung, dann ist vat_amount_chf idempotent (gleicher Wert).
  • Gegeben vat_enabled=false, dann bleiben alle Bewegungen vat_code=NULL/vat_amount_chf=NULL (MWST-neutral); Aktivierung wirkt nur prospektiv.

Traceability: movements.vat_code/vat_amount_chf (Dok 30 §4.1, F-18), vat_rates (§3.1), Compliance §3.2/§3.9, Workflow F (§4). (Deckt data-02 ab: angewandten MWST-Satz auf movements/invoices mitspeichern — sonst Snapshot-Drift bei Satzwechsel.)


US-FIN-17 — MWST scharfschalten (Aktivierung)

Als ADMIN möchte ich MWST kontrolliert aktivieren können, damit vor Eintritt der Steuerpflicht keine falschen Steuerkennzahlen entstehen.

Akzeptanzkriterien

  • Gegeben Eintritt der Steuerpflicht, wenn ich vat_rates.active=true + Feature-Flag vat_enabled setze, dann ist MWST ab diesem Zeitpunkt aktiv — ohne Schema-Migration.
  • Gegeben aktive MWST, wenn Rechnungen/Exporte erzeugt werden, dann tragen sie Satz je Position, MWST-Betrag und die geforderten Pflichtangaben (Art. 26 MWSTG).
  • Gegeben die Methodenwahl, dann ist sie als Settings-Flag vat_method ∈ {effective, flat_rate} abgebildet (Saldosteuersatz vs. effektiv) — 🔲 Methode/Satz/brutto-netto Treuhänderentscheid.

Traceability: vat_rates.active/Settings vat_enabled/vat_method (Compliance §3.1/§3.3), Workflow F (§4). (Entscheid-abhängig: MWST-Scharfschaltung/Methode/brutto-netto — 🔲 E13.)


US-FIN-18 — Treuhänder-Export

Als READONLY (Treuhänder) / BUCHHALTUNG möchte ich eine Periode als CSV/Excel/PDF (inkl. MWST-Kennzahlen, wenn aktiv) exportieren, damit die formale Buchhaltung über die offizielle Schnittstelle gespeist wird.

Akzeptanzkriterien

  • Gegeben ein Zeitraum + Format (CSV/Excel/PDF), wenn ich exportiere, dann erzeugt eine Edge-Function die Datei in Supabase Storage und liefert einen Download-Link.
  • Gegeben die Option „nur abgeschlossene Perioden", wenn ich sie wähle, dann enthält der Export ausschliesslich closed-Perioden.
  • Gegeben MWST aktiv, dann enthält der Export die MWST-Kennzahlen (Umsatz nach Satz, geschuldete Steuer, Vorsteuer bei effektiver Methode); ist MWST inaktiv, sind die MWST-Spalten ausgeblendet.
  • Gegeben ein Export-Vorgang, dann wird audit_log mit action='export' (wer/wann) protokolliert; der Export enthält nur das fachlich Nötige (revDSG).

Traceability: UI Treuhänder-Export (§5.7), Compliance §2.6/§3.8/§7, Audit-Abdeckung §8.10. (Abgrenzung: Caja ist Vorsystem — der Export ist die einzige offizielle Ausgabe, nicht der rechtsgültige Abschluss. 🔲 Exportformat Bexio/Abacus/Banana.)


Schätzung & Priorität (Vorschlag — vom Team final zu bestätigen)

StoryGrößePriorität (MoSCoW)
US-FIN-01 Manuelle BewegungMMust
US-FIN-02 Auto-Posting (idempotent)LMust
US-FIN-03 Rechnung buchen (INVOICE)MMust
US-FIN-04 Kundenzahlung + payment_linksMMust
US-FIN-05 Rechnungsstatus OPEN/PARTIAL/PAIDMMust
US-FIN-06 Debitoren/RestschuldSMust
US-FIN-07 Interner Transfer (Balance)MMust
US-FIN-08 Kassensaldo & DashboardMMust
US-FIN-09 Kreditoren manuellSShould
US-FIN-10 Anzahlung/DepotLMust
US-FIN-11 Monatsabschluss/Sperre (close)LMust
US-FIN-12 Periode reopen (audit)SShould
US-FIN-13 Beleg & Storno (kein Delete)MMust
US-FIN-14 Audit-Spur (append-only)MMust
US-FIN-15 Finanz-RLS (Rollen-Trennung)MMust
US-FIN-16 MWST mit Satz-SnapshotMShould
US-FIN-17 MWST scharfschaltenSCould
US-FIN-18 Treuhänder-ExportLMust

Größe = grobe Aufwands-Indikation (S/M/L), nicht Story Points. Priorität nach MoSCoW (Must/Should/Could). Beides ist ein Vorschlag zur Release-Planung — die verbindliche Schätzung/Priorisierung macht das Team im Backlog. MWST-Stories (16/17) sind Should/Could, weil schema-ready, aber bis zur Steuerpflicht inaktiv.


Abdeckung & offene Punkte (dieses Epic)

  • 18 Stories decken das Finanz-Modul end-to-end ab: Erfassung (manuell + Auto-Posting) → Rechnung/Forderung → Zahlung/Status → Debitoren/Kreditoren → Kasse/Bank/Transfer → Depot/Anzahlung → Monatsabschluss/Sperre/Reopen → Beleg/Storno/Audit/RLS → MWST (ready + Aktivierung) → Treuhänder-Export. Alle abgeleiteten Grössen bleiben View-berechnet (keine Doppelspeicherung).
  • Audit-Verzahnung (als testbare Szenarien encodiert):
    • data-01 (eine Geld-Domäne numeric(12,2)) → US-FIN-01.
    • data-02 (angewandten MWST-Satz mitspeichern, Snapshot-Drift) → US-FIN-16.
    • data-04 (payment_links.payment_id on delete restrict, No-Delete-Ledger) → US-FIN-13.
    • data-05 (transfer_group_id-Balance-Invariante, Negativfall unbalanciert) → US-FIN-07.
    • append-only / Revisionssicherheit (GeBüV) → US-FIN-11/12/13/14 (Periodensperre, auditiertes Reopen, Storno statt Delete, unveränderbares audit_log).
    • Idempotenz (8.9) → US-FIN-02 (Upsert auf (source_type, source_id)).
    • revDSG-Mandantentrennung / Least Privilege → US-FIN-15 (RLS-Negativfälle FAHRER/AFFILIATE).
  • Entscheid-abhängig (🔲 markiert):
    • E13 — MWST: Scharfschaltung/Methode (Saldo vs. effektiv) und brutto/netto total_chf → US-FIN-16/17.
    • AP-6 — verfallenes Depot: als INCOME-Ertrag vs. neutral → US-FIN-10.
    • E14 — Provisions-Auslöser: ✅ entschieden 2026-06-28 (#19) — Auslöser ist DELIVERED (Freigabe approved), Entstehung bei Offerte-Konversion; nicht PAID/Zahlung → US-FIN-05, US-AFF-05/06.
    • Mehrwährung: aktuell reines CHF; DOP/USD (currency + Kurs) out of scope — kein eigener Story-Schnitt, als Modul-offener-Punkt vermerkt (§9, Compliance §1.7).
    • Weitere offene Punkte (Anfangssalden, „Dringend"-Flag Kreditoren, INCOME im Dropdown, Exportformat) bleiben Modul-offene Punkte (§9) und sind in den betroffenen Stories als 🔲 referenziert.
  • Nächster Schritt: Format-/Inhaltsfreigabe → Backlog-Übernahme mit verbindlicher Schätzung + INVEST-Red-Team-Pass; MWST-Stories erst bei Eintritt der Steuerpflicht aktivieren.