# VAT-Split (Multi-Rate) Plan — KAR-639 — ✅ BUILT (4 PRs, 2026-05-29)

> STATUS: DONE. V1 #60 + V2 #61 merged+live. V3 #62 + V4 #63 green, awaiting Kais merge.
> Do NOT rebuild. Remaining work is Option A (DATEV scharfschalten) — see datev-hardening-plan-kar638.md.

---
(original plan below, for reference)

# VAT-Split (Multi-Rate) Plan — KAR-638 — DRAFT, await Kais "go"

Discovered 2026-05-29 during Option-B test: real METRO invoice has MULTIPLE VAT
rates in one receipt. Current model collapses to a single vat_rate → WRONG
(booked 19% on 314,66 brutto; effective ~12%). Kais confirmed it's wrong.
This jumped ahead of Option A (DATEV-finalize) in priority.

Sensitive paths: migration (#3) + packages/datev (#4) → plan-mode, Kais "go" before code.

## The real data (METRO 314,66 €, 23.05.2026)
Summary box (page 1):
- 19% (A): netto 59,98 → MwSt 11,40 → brutto 71,38
- 7%  (B): netto 116,15 → MwSt 8,13 → brutto 124,28
- Leergut 19%: netto 100,00 → MwSt 19,00 → brutto 119,00
- Sum: netto 276,13, MwSt 38,53, brutto 314,66

## Current state
- `receipts` has single vat_rate NUMERIC(5,2), net_cents, vat_cents, total_cents.
  Comment claims "VAT-Split" but schema is single-rate. Has `extra JSONB` (unused for this).
- Extraction fields (ocr.ts DEFAULT_RECEIPT_FIELDS): merchant, total, vat_rate(single), date.
- belege-review.tsx edits a single rate. DATEV export route maps 1 receipt → uses single vat_rate.

## What the Steuerberater needs (answered to Kais)
- VAT-RATE split: per rate one DATEV booking line (brutto per rate → Wareneingang account + Steuerschlüssel: 7%=BU 9, 19%=BU 8).
- Warengruppen NOT needed (METRO-internal; collapse to 7%/19% Wareneingang). Optional later for own cost analysis.

## Kais decisions (2026-05-29, msg 4449)
- Warengruppen: capture but INTERNAL ONLY (cost analysis), NOT in DATEV export. → separate `receipt_category_lines` table.
- VAT rates: flexible LIST (not hard-coded 2). Practice = 7% + 19%; must not break on 0%/steuerfrei or a 3rd rate.
- Leergut: DATEV = 19% bucket; cost analysis = own Warengruppe.
- PR #59 (multi-upload): merge now, independent of this work (different file, no conflict).

## Plan
1. **Migration** (sensitive #3): TWO child tables.
   a. `receipt_vat_lines` (id, receipt_id FK CASCADE, tenant_id, space_id, vat_rate NUMERIC(5,2), net_cents, vat_cents, gross_cents, kind TEXT CHECK in('standard','leergut','other') default 'standard', skr_account, bu_key, line_no). For DATEV.
   b. `receipt_category_lines` (id, receipt_id FK CASCADE, tenant_id, space_id, category TEXT (Warengruppe), net_cents, line_no). INTERNAL cost analysis only — never exported to DATEV.
   Both: ENABLE+FORCE RLS space-scoped (is_member_of_space), auto-fill tenant/created_by trigger, audit.log_event, `[plan-approved]` PR token. receipts.total/net/vat_cents stay = sums of vat lines. Keep receipts.vat_rate nullable for single-rate fallback/back-compat.
2. **Extraction** (packages/belege schema/prompt + ocr.ts): add `vat_lines: [{rate, net, vat, gross}]` to extraction schema; prompt to read the "NETTOWERT % MWST MWST BRUTTO" summary box. Map to receipt totals + child lines. Fallback: if only single rate found, one line.
3. **Persistence** (actions.ts saveReceipt/extractFromImage): insert receipt + its vat_lines (transaction-ish; RLS via space membership). updateReceipt edits lines.
4. **Review UI** (belege-review.tsx): render N vat-line rows (rate/net/vat/gross), add/remove, validate Σ lines == total (±1 cent). de-DE formatting.
5. **DATEV export** (packages/datev, sensitive #4): one booking line per vat_line (gross → Wareneingang account by rate via SKR mapping + BU key). Leergut → 19%. Replaces the single-rate mapping.
6. Cleanup: verwerfen/delete the wrong test receipt (BELEG-2026-05-0001 single-19%).

## Tests
- receipt_vat_lines RLS contract (packages/rls); extraction multi-line mapping (ocr.test); datev one-line-per-rate (csv/xml tests); review sum-validation pure helper.

## PR slicing
- PR-V1: migration receipt_vat_lines + RLS test.
- PR-V2: extraction schema/prompt + ocr mapping + persistence (receipt + lines).
- PR-V3: review UI multi-line edit + sum validation.
- PR-V4: DATEV export one-line-per-rate (sensitive #4, own plan note in PR).

## Open Qs for Kais (fold into "go")
- Leergut/Pfand: in 19%-Bucket mitbuchen (einfach) ODER eigene Zeile/Konto (manche Berater)? Default: 19%-Bucket, eine Zeile.
- SKR03 oder SKR04 fürs Konto-Mapping? (Default SKR04 gastronomie per 05-DATEV §4.1.)
