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

@ -204,3 +204,26 @@ pub struct DeliveryCredit {
pub amount_cents: i64,
pub reason: String,
}
/// Protokollierte Zahlungsabwicklung („Abkassieren") einer Lieferung.
///
/// Append-only: jede Bestätigung des Fahrers ist ein eigener Eintrag; im
/// Tour-Aggregat wird pro Lieferung nur der jüngste mitgeliefert. Der Betrag
/// ist der server-seitig berechnete offene Betrag zum Zeitpunkt der
/// Bestätigung. Weicht der aktuelle offene Betrag davon ab (z. B. nach einer
/// neuen Gutschrift), ist der Eintrag veraltet und muss erneuert werden.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct DeliveryPayment {
pub id: Uuid,
pub delivery_id: Uuid,
pub payment_method_id: Uuid,
/// Snapshot des Methoden-Codes (`cash`, `ec_card`, `invoice`, …).
pub payment_method_code: String,
/// Abgewickelter Betrag in Cent (offener Betrag bei Bestätigung).
pub amount_cents: i64,
pub recorded_by_personalnummer: i64,
pub recorded_by_car_id: Option<Uuid>,
pub recorded_at: DateTime<Utc>,
}

View File

@ -37,7 +37,8 @@ pub use common::Address;
pub use contact::{ContactChannel, ContactKind, ContactRole, ContactSource};
pub use customer::{Customer, CustomerContact};
pub use delivery::{
Delivery, DeliveryCredit, DeliveryItem, DeliveryNote, DeliveryState, ScanState, ScanStatus,
Delivery, DeliveryCredit, DeliveryItem, DeliveryNote, DeliveryPayment, DeliveryState, ScanState,
ScanStatus,
};
pub use payment::PaymentMethod;
pub use process_state::{DeliveryPhase, DeliveryProcessState};