Final commit.

This commit is contained in:
Dennis Nemec
2026-06-01 17:12:28 +02:00
parent 3ecbc82885
commit a9bf8ecdd1
385 changed files with 29081 additions and 12089 deletions

View File

@ -0,0 +1,99 @@
import 'package:hl_lieferservice/domain/entity/delivery.dart';
import 'package:hl_lieferservice/domain/entity/delivery_item.dart';
import 'package:hl_lieferservice/domain/entity/tour_details.dart';
/// Ergebnis der Item-Auflösung beim Scan. Sealed, damit das UI per
/// `switch` exhaustiv pro Fall eine passende Meldung zeigt.
sealed class ItemMatch {
const ItemMatch();
const factory ItemMatch.ok(DeliveryItem item) = ItemMatchOk;
const factory ItemMatch.notInDelivery() = ItemMatchNotInDelivery;
const factory ItemMatch.notScannable() = ItemMatchNotScannable;
const factory ItemMatch.allDone() = ItemMatchAllDone;
const factory ItemMatch.allRemoved() = ItemMatchAllRemoved;
const factory ItemMatch.notOpen() = ItemMatchNotOpen;
}
class ItemMatchOk extends ItemMatch {
const ItemMatchOk(this.item);
final DeliveryItem item;
}
class ItemMatchNotInDelivery extends ItemMatch {
const ItemMatchNotInDelivery();
}
class ItemMatchNotScannable extends ItemMatch {
const ItemMatchNotScannable();
}
class ItemMatchAllDone extends ItemMatch {
const ItemMatchAllDone();
}
class ItemMatchAllRemoved extends ItemMatch {
const ItemMatchAllRemoved();
}
class ItemMatchNotOpen extends ItemMatch {
const ItemMatchNotOpen();
}
/// Findet in der Lieferung das erste **nicht fertig** gescannte Item mit der
/// gegebenen Article-Nummer. „Top-down"-Strategie: hat eine Lieferung zwei
/// Belegzeilen mit demselben Artikel (z. B. 20 + 10), wird zuerst die
/// niedrigere Belegzeile gefüllt. Erst wenn diese fertig ist, „rollt" der
/// nächste Scan auf die zweite Zeile weiter.
///
/// [itemFilter] schränkt die betrachteten Positionen ein — z. B. nur
/// Filial-Items in der Abhol-Phase. Ohne Filter werden alle Positionen
/// berücksichtigt (Beladen-Phase).
///
/// Klassifiziert den Misserfolgs-Grund (nicht scanbar / bereits fertig /
/// entfernt …), damit das UI dem Fahrer eine sinnvolle Meldung zeigt.
ItemMatch matchItem({
required Delivery delivery,
required TourDetails details,
required String articleNumber,
bool Function(DeliveryItem item)? itemFilter,
}) {
final normalized = articleNumber.trim();
final candidates = delivery.items
.where((it) => itemFilter?.call(it) ?? true)
.toList()
..sort((a, b) => a.belegzeilenNr.compareTo(b.belegzeilenNr));
final matchingArticle = candidates.where((it) {
final art = details.articleOf(it.articleId);
return art?.articleNumber == normalized;
}).toList();
if (matchingArticle.isEmpty) {
return const ItemMatch.notInDelivery();
}
// Erstes Item, das wir tatsächlich scannen können.
for (final item in matchingArticle) {
if (item.isDone || item.isRemoved) continue;
final article = details.articleOf(item.articleId);
if (article == null || !article.scannable) continue;
return ItemMatch.ok(item);
}
// Kein scannbares offenes Item — Grund anhand der Item-Verteilung
// klassifizieren, damit das UI eine sinnvolle Meldung zeigt.
final allNotScannable = matchingArticle.every((it) {
final art = details.articleOf(it.articleId);
return art == null || !art.scannable;
});
if (allNotScannable) return const ItemMatch.notScannable();
final allRemoved = matchingArticle.every((it) => it.isRemoved);
if (allRemoved) return const ItemMatch.allRemoved();
final allDone = matchingArticle.every((it) => it.isDone);
if (allDone) return const ItemMatch.allDone();
// Gemischte Konstellation (z. B. eine Zeile entfernt, eine fertig) —
// praktisch selten; konservativ als „nicht (mehr) offen" melden.
return const ItemMatch.notOpen();
}