feat(loading): Set-Kopf wird grün, wenn alle Komponenten geladen sind

Ein nicht-scanbarer Set-Kopf (Parent-Artikel) hat keinen eigenen
Scan-Status. Sobald alle scanbaren, nicht entfernten Komponenten fertig
(isDone) sind, wird der Kopf jetzt ebenfalls grün dargestellt
(effectiveDone = isDone || setParentComplete) und zeigt statt des
unterdrückten Hinweises ein grünes „Komplett geladen".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dennis Nemec
2026-06-24 11:11:21 +02:00
parent 8b1b35b374
commit f12ad5d3c0

View File

@ -558,6 +558,17 @@ class _CustomerBody extends StatelessWidget {
}) })
.map((it) => it.id) .map((it) => it.id)
.toSet(); .toSet();
// Set-Kopf „komplett": alle scanbaren, nicht entfernten Komponenten fertig.
// Dann wird auch der (selbst nicht scanbare) Kopf grün dargestellt.
bool setParentComplete(DeliveryItem parent) {
final nr = artNrOf(parent);
if (nr == null) return false;
final comps = delivery.items.where((c) =>
c.parentArtikelNr == nr &&
!c.isRemoved &&
(details.articleOf(c.articleId)?.scannable ?? false));
return comps.isNotEmpty && comps.every((c) => c.isDone);
}
// Set-Köpfe je Lagergruppe (warehouseId → einzuhängende Köpfe) + // Set-Köpfe je Lagergruppe (warehouseId → einzuhängende Köpfe) +
// gesammelte IDs, um sie aus der Dienstleistungs-Sektion zu entfernen. // gesammelte IDs, um sie aus der Dienstleistungs-Sektion zu entfernen.
final injectedParentsByWarehouseId = <String, List<DeliveryItem>>{}; final injectedParentsByWarehouseId = <String, List<DeliveryItem>>{};
@ -686,6 +697,8 @@ class _CustomerBody extends StatelessWidget {
details: details, details: details,
onAction: (action) => onItemAction(item, action), onAction: (action) => onItemAction(item, action),
suppressScanHint: setParentIds.contains(item.id), suppressScanHint: setParentIds.contains(item.id),
setParentComplete: setParentIds.contains(item.id) &&
setParentComplete(item),
), ),
], ],
// Gebuchte Dienstleistungen (nicht-scanbare Positionen ohne // Gebuchte Dienstleistungen (nicht-scanbare Positionen ohne
@ -701,6 +714,8 @@ class _CustomerBody extends StatelessWidget {
details: details, details: details,
onAction: (action) => onItemAction(item, action), onAction: (action) => onItemAction(item, action),
suppressScanHint: setParentIds.contains(item.id), suppressScanHint: setParentIds.contains(item.id),
setParentComplete: setParentIds.contains(item.id) &&
setParentComplete(item),
), ),
], ],
], ],
@ -807,6 +822,44 @@ class _ScanNotRequiredHint extends StatelessWidget {
} }
} }
/// Grünes „Komplett geladen" für einen Set-Kopf (Parent-Artikel), dessen
/// (scanbare) Komponenten alle geladen sind. Ersetzt an dieser Stelle den
/// „Kein Scanvorgang notwendig"-Hinweis.
class _SetCompleteHint extends StatelessWidget {
const _SetCompleteHint();
@override
Widget build(BuildContext context) {
final color = Colors.green.shade700;
return Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.green.withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.check_circle, size: 16, color: color),
const SizedBox(width: 6),
Flexible(
child: Text(
'Komplett geladen',
style: TextStyle(
fontSize: 13,
color: color,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
}
/// Sektions-Kopf vor den Items eines Lagers. Visuell klar getrennt /// Sektions-Kopf vor den Items eines Lagers. Visuell klar getrennt
/// (Standardlager vs. Filiale): Standardlager ist der primäre /// (Standardlager vs. Filiale): Standardlager ist der primäre
/// Arbeitsplatz und damit neutral koloriert; Filial-Sections /// Arbeitsplatz und damit neutral koloriert; Filial-Sections
@ -1166,6 +1219,7 @@ class _ItemRow extends StatelessWidget {
required this.details, required this.details,
required this.onAction, required this.onAction,
this.suppressScanHint = false, this.suppressScanHint = false,
this.setParentComplete = false,
}); });
final DeliveryItem item; final DeliveryItem item;
@ -1177,6 +1231,10 @@ class _ItemRow extends StatelessWidget {
/// (scanbaren) Komponenten darunter sehr wohl gescannt werden. /// (scanbaren) Komponenten darunter sehr wohl gescannt werden.
final bool suppressScanHint; final bool suppressScanHint;
/// `true`, wenn dies ein Set-Kopf ist, dessen Komponenten alle fertig
/// geladen sind. Dann wird der (selbst nicht scanbare) Kopf grün dargestellt.
final bool setParentComplete;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
@ -1188,6 +1246,9 @@ class _ItemRow extends StatelessWidget {
// Mengen-Zähler. Aus dem Artikel abgeleitet, damit ein in eine Lagergruppe // Mengen-Zähler. Aus dem Artikel abgeleitet, damit ein in eine Lagergruppe
// eingehängter nicht-scanbarer Set-Kopf automatisch korrekt rendert. // eingehängter nicht-scanbarer Set-Kopf automatisch korrekt rendert.
final scanNotRequired = !(article?.scannable ?? false); final scanNotRequired = !(article?.scannable ?? false);
// Effektiv „fertig": eigener Scan-Status ODER (Set-Kopf, dessen Komponenten
// alle geladen sind) → der Kopf wird dann ebenfalls grün.
final effectiveDone = item.isDone || setParentComplete;
final isExternalWarehouse = warehouse != null && !warehouse.isStandard; final isExternalWarehouse = warehouse != null && !warehouse.isStandard;
// Manueller Fallback-Button: nur für scanbare, noch offene Positionen // Manueller Fallback-Button: nur für scanbare, noch offene Positionen
// (nicht done/entfernt/pausiert) — analog dazu, was ein Barcode-Scan // (nicht done/entfernt/pausiert) — analog dazu, was ein Barcode-Scan
@ -1224,7 +1285,7 @@ class _ItemRow extends StatelessWidget {
leadingIcon = Icons.pause_circle_outline; leadingIcon = Icons.pause_circle_outline;
leadingIconColor = Colors.orange.shade800; leadingIconColor = Colors.orange.shade800;
statusBadgeLabel = 'Pausiert'; statusBadgeLabel = 'Pausiert';
} else if (item.isDone) { } else if (effectiveDone) {
cardColor = Colors.green.withValues(alpha: 0.07); cardColor = Colors.green.withValues(alpha: 0.07);
borderColor = Colors.green.withValues(alpha: 0.35); borderColor = Colors.green.withValues(alpha: 0.35);
titleColor = Colors.green.shade700; titleColor = Colors.green.shade700;
@ -1413,11 +1474,14 @@ class _ItemRow extends StatelessWidget {
), ),
), ),
], ],
// Dienstleistung (nicht-scanbar): Hinweis statt Scan/Manuell- // Set-Kopf, dessen Komponenten alle geladen sind → grünes
// Aktion. Steht an derselben Stelle wie der Manuell-Button. // „Komplett geladen". Sonst (nicht-scanbare Position, KEIN
// Bei Set-Köpfen (Parent-Artikel) bewusst unterdrückt — dort // Set-Kopf) der normale „Kein Scanvorgang notwendig"-Hinweis;
// wäre „kein Scanvorgang notwendig" irreführend. // bei Set-Köpfen ist der unterdrückt (irreführend).
if (scanNotRequired && !suppressScanHint) ...[ if (scanNotRequired && setParentComplete) ...[
const SizedBox(height: 8),
const _SetCompleteHint(),
] else if (scanNotRequired && !suppressScanHint) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
const _ScanNotRequiredHint(), const _ScanNotRequiredHint(),
], ],