feat(zahlung): Zahlungsabwicklung als eigenes Protokoll (POST /deliveries/{id}/payment)

- Neue Tabelle delivery_payments (append-only, idempotent ueber
  client_event_id): Methode + Code-Snapshot, server-seitig berechneter
  offener Betrag, Fahrer, Fahrzeug, Zeitpunkt
- Endpoint prueft unter Zeilen-Lock: Lieferung aktiv, Methode aktiv,
  offener Betrag > 0 und identisch mit dem vom Fahrer bestaetigten Betrag
- Offener Betrag als gemeinsamer Helper (open_amount_cents) fuer
  Zahlungsprotokoll und Abschluss
- Abschluss-Gate: gueltige protokollierte Zahlung erfuellt die
  Inkasso-Pflicht und liefert die Methode; delivery_completions.payment_id
  verknuepft den Abschluss mit der Zahlung. Altes payment_collected-Flag
  bleibt fuer aeltere App-Versionen gueltig
- Tour-Aggregat und Admin-Belegdetails liefern die juengste Zahlung
- Einzel-Reset loescht auch das Zahlungsprotokoll
- Integrationstest (ignored, braucht Wegwerf-DB) fuer Protokoll + Gate

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dennis Nemec
2026-09-25 14:04:35 +02:00
parent 5731fea501
commit 954c5f52b2
22 changed files with 741 additions and 58 deletions

View File

@ -0,0 +1,40 @@
-- 0032_delivery_payments.sql
--
-- Zahlungsprotokoll („Abkassieren") als eigener Schritt vor dem Abschluss.
--
-- Bisher gab es nur das Ja/Nein-Feld `delivery_completions.payment_collected`,
-- gesetzt erst mit den Unterschriften. Jetzt wickelt der Fahrer die Zahlung in
-- einem eigenen Step ab; jede Bestätigung ist eine eigene Zeile (append-only,
-- analog `delivery_credit_audit`). Der aktuelle Stand einer Lieferung ist die
-- jüngste Zeile.
--
-- * amount_cents — server-seitig berechneter offener Betrag zum
-- Zeitpunkt der Bestätigung (nicht vom Client
-- übernommen). Ändert sich danach der offene Betrag
-- (z. B. neue Gutschrift), passt die Zeile nicht mehr
-- und der Abschluss verlangt eine neue Abwicklung.
-- * payment_method_code — Snapshot des Methoden-Codes (cash/ec_card/invoice),
-- damit das Protokoll auch nach Umbenennen/Deaktivieren
-- der Stammdaten lesbar bleibt.
--
-- Idempotenz: client_event_id ist UNIQUE (Netz-Retry erzeugt keine Dublette).
CREATE TABLE delivery_payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
client_event_id UUID NOT NULL UNIQUE,
delivery_id UUID NOT NULL REFERENCES deliveries(id) ON DELETE CASCADE,
payment_method_id UUID NOT NULL REFERENCES payment_methods(id) ON DELETE RESTRICT,
payment_method_code TEXT NOT NULL,
amount_cents BIGINT NOT NULL CHECK (amount_cents >= 0),
recorded_by_personalnummer BIGINT NOT NULL,
recorded_by_car_id UUID,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX delivery_payments_delivery
ON delivery_payments (delivery_id, recorded_at DESC);
-- Verknüpfung Abschluss → die Zahlung, die beim Abschluss gültig war.
-- NULL bei Abschlüssen ohne Zahlungsprotokoll (alt, offen == 0, alte App).
ALTER TABLE delivery_completions
ADD COLUMN payment_id UUID REFERENCES delivery_payments(id) ON DELETE SET NULL;