Files
Holzleitner-Lieferservice-App/lib/domain/entity/delivery.dart
Dennis Nemec a9bf8ecdd1 Final commit.
2026-06-01 17:12:28 +02:00

153 lines
5.4 KiB
Dart

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<String> 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<DeliveryItem> 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<DeliveryItem> 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<String>? contactPersonIds,
Address? deliveryAddressSnapshot,
int? erpBelegartId,
String? erpBelegnummer,
DeliveryState? state,
String? stateReason,
int? sortOrder,
String? assignedCarId,
Object? desiredTime = _sentinel,
Object? specialAgreements = _sentinel,
List<DeliveryItem>? 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();