153 lines
5.4 KiB
Dart
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();
|