import 'address.dart'; import 'delivery_item.dart'; /// Lebenszyklus einer Lieferung. /// /// - `active`: Standard nach Anlage; Fahrer kann scannen/ausliefern. /// - `held`: Pausiert (Kunde nicht da, Termin verschoben) — kein Bearbeitungsfortschritt. /// - `canceled`: Abgebrochen — wird nicht mehr ausgeliefert. /// - `completed`: Abgeschlossen — Signatur und Notizen sind hinterlegt. enum DeliveryState { active, held, canceled, completed } /// Eine einzelne Auslieferung an einen Kunden innerhalb einer Tour. /// /// Anders als im alten Modell trägt `Delivery` hier ausschließlich /// Logistik-Daten — keine Preise, keine Rabatte, keine Zahlungsoptionen. /// Diese ERP-Themen sind in Phase C+D-2 absichtlich nicht migriert und /// hängen hinter `FeatureFlags`. class Delivery { const Delivery({ required this.id, required this.tourId, required this.customerId, required this.contactPersonIds, required this.deliveryAddressSnapshot, required this.erpBelegartId, required this.erpBelegnummer, required this.state, required this.sortOrder, required this.items, required this.prepaidAmount, required this.paymentMethodId, this.assignedCarId, this.desiredTime, this.specialAgreements, this.stateReason, }); final String id; final String tourId; final String customerId; /// 0..n Kontakte am Kunden, die für diese Lieferung relevant sind. /// Lookup über `TourDetails.contactById`. final List contactPersonIds; /// Eingefrorene Lieferadresse zum Zeitpunkt der Belegerzeugung — bleibt /// stabil, auch wenn die Stammadresse am Kunden später geändert wird. final Address deliveryAddressSnapshot; /// ERP-Belegart (Lieferschein, Rechnung, …) und -Nummer. Für die App nur /// informativ; in Notizen/Reklamationen ist die Belegnummer der vom /// Kunden verständliche Bezugspunkt. final int erpBelegartId; final String erpBelegnummer; final DeliveryState state; /// Optionaler Klartext, warum `state` auf `held`/`canceled` steht. Vom /// Backend nicht-leer erzwungen, sobald ein Reason-pflichtiger Zustand /// gesetzt wird. final String? stateReason; /// Sortier-Reihenfolge innerhalb der Tour, gesetzt durch /// `PUT /tours/{id}/delivery-order`. Niedriger = früher. final int sortOrder; /// UUID des Fahrzeugs, dem diese Lieferung beim Laden zugewiesen wurde. /// `null` = noch nicht zugewiesen. final String? assignedCarId; /// Bei Bestellung schon bezahlter Betrag in EUR. `0.0` wenn der Kunde /// alles bei Lieferung zahlt. Wird vom ERP-Sync gesetzt. final double prepaidAmount; /// FK auf eine `PaymentMethod` (UUID). Auflösung zu Display-Name und /// Aktiv-Status geht über die Stammdaten-Liste, die die App separat /// lädt — nicht hier embeddet, damit das Tour-Aggregat klein bleibt. final String paymentMethodId; final String? desiredTime; final String? specialAgreements; final List items; // ─── Abgeleitete Sicht-Eigenschaften ────────────────────────────────── /// Nur Items, die der Treiber tatsächlich scannen muss. Nicht-scanbare /// Artikel (Dienstleistungen, Versand) sowie bereits entfernte Items /// werden nicht mitgezählt. Iterable scannableItems( bool Function(String articleId) isScannable, ) sync* { for (final item in items) { if (item.isRemoved) continue; if (!isScannable(item.articleId)) continue; yield item; } } /// `true`, sobald *alle* scanbaren Items dieser Lieferung als `done` /// markiert sind. Wird in der Loading-Übersicht angezeigt und /// kontrolliert in der Detail-Phase den Übergang zur Signatur. bool allScannableItemsDone(bool Function(String articleId) isScannable) { final scannables = scannableItems(isScannable).toList(); if (scannables.isEmpty) return false; return scannables.every((item) => item.isDone); } Delivery copyWith({ String? id, String? tourId, String? customerId, List? contactPersonIds, Address? deliveryAddressSnapshot, int? erpBelegartId, String? erpBelegnummer, DeliveryState? state, String? stateReason, int? sortOrder, String? assignedCarId, Object? desiredTime = _sentinel, Object? specialAgreements = _sentinel, List? items, double? prepaidAmount, String? paymentMethodId, }) { return Delivery( id: id ?? this.id, tourId: tourId ?? this.tourId, customerId: customerId ?? this.customerId, contactPersonIds: contactPersonIds ?? this.contactPersonIds, deliveryAddressSnapshot: deliveryAddressSnapshot ?? this.deliveryAddressSnapshot, erpBelegartId: erpBelegartId ?? this.erpBelegartId, erpBelegnummer: erpBelegnummer ?? this.erpBelegnummer, state: state ?? this.state, stateReason: stateReason ?? this.stateReason, sortOrder: sortOrder ?? this.sortOrder, assignedCarId: assignedCarId ?? this.assignedCarId, desiredTime: identical(desiredTime, _sentinel) ? this.desiredTime : desiredTime as String?, specialAgreements: identical(specialAgreements, _sentinel) ? this.specialAgreements : specialAgreements as String?, items: items ?? this.items, prepaidAmount: prepaidAmount ?? this.prepaidAmount, paymentMethodId: paymentMethodId ?? this.paymentMethodId, ); } } const Object _sentinel = Object();