import 'package:hl_lieferservice/model/article.dart'; import 'package:hl_lieferservice/model/delivery.dart'; /// Aggregiert eine [Delivery] mit ihren scannbaren Artikeln zu einer /// Belade-Einheit. Bildet die Datenbasis für die Beladen-Phase (Vollbild- /// Kunde + Übersicht). /// /// **Wichtige Geschäftslogik:** Für die Frage "ist diese Lieferung fertig /// beladen?" zählen nur Artikel aus dem **Standardlager** (warehouseNr /// `null` oder `"0"`). Außenlager-Artikel werden separat beim /// Kundenbesuch in der Ausliefer-Phase abgeholt — sie blockieren also /// nicht den Beladen-Abschluss. Konsequenz: alle Counter-Getter /// ([totalArticles], [completeArticles], [scannedUnits], [totalUnits], /// [isComplete], [isPartial], [hasAnyScanned]) ignorieren Außenlager- /// Artikel. Wer alle Artikel braucht, greift direkt auf [articles] zu. /// /// Aufrufer füllen [articles] mit den scannbaren Artikeln dieser Lieferung /// (also nicht alle Artikel der Lieferung — vorgefiltert über /// `Article.scannable`). class LoadingGroup { /// Die zugrundeliegende Lieferung (inkl. Kunde, Adresse, State). final Delivery delivery; /// Nummernschild des zugewiesenen Fahrzeugs zur Darstellung im Badge. /// `null` wenn die Lieferung noch keinem Auto zugeordnet ist. final String? carPlate; /// Die scannbaren Artikel der Lieferung (bereits vorgefiltert). final List
articles; const LoadingGroup({ required this.delivery, required this.articles, this.carPlate, }); /// Alle Standardlager-Artikel (Lager-Nummer `null` oder `"0"`). Bildet /// die Basis aller Beladen-Counter, weil Außenlager-Ware nicht in der /// Belade-Halle scannbar ist. List
get _standardArticles => articles .where((a) => !_isExternalWarehouse(a.warehouseNr)) .toList(growable: false); /// Anzahl der scannbaren Standardlager-Artikel. Parent-Artikel zählen /// als 1 (nicht je Komponente). int get totalArticles => _standardArticles.length; /// Anzahl der vollständig gescannten Standardlager-Artikel. Bei Parent- /// Artikeln gilt "vollständig" = alle Komponenten vollständig. int get completeArticles => _standardArticles.where((a) => a.isFullyScanned).length; /// Gesamtanzahl der erwarteten Einzelstücke aus dem Standardlager — /// bei Parent-Artikeln summiert über die Required-Amounts der /// Komponenten. int get totalUnits => _standardArticles.fold(0, (sum, a) { if (a.isParent && a.components.isNotEmpty) { return sum + a.components.fold(0, (s, c) => s + c.requiredAmount); } return sum + a.amount; }); /// Bereits gescannte Einzelstücke aus dem Standardlager — analog zu /// [totalUnits]. int get scannedUnits => _standardArticles.fold(0, (sum, a) { if (a.isParent && a.components.isNotEmpty) { return sum + a.components.fold(0, (s, c) => s + c.scannedAmount); } return sum + a.scannedAmount + a.scannedRemovedAmount; }); /// `true`, wenn alle Standardlager-Artikel vollständig gescannt sind. /// /// Edge-Case: Lieferung **ohne** Standardlager-Artikel (alle Artikel /// liegen in Außenlagern) → automatisch fertig, weil in der Beladen- /// Phase nichts zu tun ist. bool get isComplete { if (articles.isEmpty) return false; if (_standardArticles.isEmpty) return true; return completeArticles == totalArticles; } /// `true`, wenn mindestens ein Stück gescannt wurde — egal ob Artikel /// vollständig oder nicht. bool get hasAnyScanned => scannedUnits > 0; /// `true`, wenn die Lieferung angefangen, aber nicht abgeschlossen wurde. bool get isPartial => hasAnyScanned && !isComplete; /// `true`, wenn mindestens ein Artikel der Lieferung NICHT aus dem /// Standard-Lager kommt. Standard-Lager hat die Nummer "0"; ein /// `warehouseNr == null` interpretieren wir als "nicht angegeben" und /// damit als Standard (kein False-Positive auf Datenlücken). bool get hasExternalWarehouseArticles => articles.any((a) => _isExternalWarehouse(a.warehouseNr)); /// Eindeutige Liste der Außenlager-Namen, die in dieser Lieferung /// vorkommen — für Badges/Hinweise in der Übersicht. Wenn ein Artikel /// nur eine `warehouseNr` aber keinen Namen hat, wird die Nummer als /// Fallback genommen. List get externalWarehouseLabels { final labels = {}; for (final a in articles) { if (!_isExternalWarehouse(a.warehouseNr)) continue; final label = (a.warehouseName?.isNotEmpty ?? false) ? a.warehouseName! : "Lager ${a.warehouseNr}"; labels.add(label); } return labels.toList(growable: false); } static bool _isExternalWarehouse(String? nr) => nr != null && nr.isNotEmpty && nr != "0"; }