feat(delivery): Abschluss-Navigation, Mengen-Hinweis, Set-Handling
A) Nach erfolgreichem Abschluss (aktiv→completed) poppt die Detail-Page automatisch zurück zur Übersicht. Scaffold ist jetzt StatefulWidget mit BlocListener<TourBloc>; nur „gearmt", wenn die Lieferung beim Öffnen aktiv war → erneutes Öffnen einer fertigen Lieferung poppt nicht. B) Step „Info": Artikelliste zeigt weiter die Ursprungsmenge (requiredQuantity). Bei entfernten/teilweise gutgeschriebenen Positionen erscheint pro Zeile ein „Menge geändert"-Hinweis + ein tappbares Banner, das zu Step 3 „Artikel" springt. C) Beladen: nicht-scanbare Set-Köpfe (Parent-Komponenten) werden jetzt IMMER mit ihrem Set gezeigt — als Kopf in der Lagergruppe ihrer Komponenten statt isoliert unter „Dienstleistungen". _ItemRow leitet scanNotRequired aus der Artikel-Scanbarkeit ab. D) Step „Übersicht": Wording der Zahlungsweise-Sperre bei offen==0 präzisiert („Keine Zahlung mehr offen (bereits bezahlt)"). E) Step „Artikel": Komponenten eines Sets sind einzeln nicht mehr entfernbar (kein Button + Hinweis). Das Entfernen/Wiederherstellen läuft nur über den Oberartikel und kaskadiert auf das ganze Set (ganz oder gar nix). Set-Entfernen ist blockiert, solange eine Komponente noch nicht verladen ist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -9,6 +9,9 @@ import 'package:hl_lieferservice/domain/entity/delivery_item.dart';
|
||||
import 'package:hl_lieferservice/domain/entity/tour_details.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/bloc/tour_bloc.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/bloc/tour_event.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/detail/bloc/workflow_bloc.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/detail/bloc/workflow_event.dart';
|
||||
import 'package:hl_lieferservice/feature/delivery/detail/bloc/workflow_state.dart';
|
||||
|
||||
/// Step 1 — Informationen zur Lieferung.
|
||||
///
|
||||
@ -716,16 +719,77 @@ class _ArticleList extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
return Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
for (int i = 0; i < items.length; i++) ...[
|
||||
_ArticleRow(item: items[i], details: details),
|
||||
if (i < items.length - 1)
|
||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||
],
|
||||
// Hat sich bei mindestens einem Artikel die tatsächliche Menge geändert
|
||||
// (entfernt oder teilweise gutgeschrieben)? Dann ein Banner mit Verweis
|
||||
// auf Step 3 „Artikel", wo die Änderungen verwaltet werden.
|
||||
final anyChanged = items.any(_quantityChanged);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (anyChanged) ...[
|
||||
const _QuantityChangedBanner(),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
Card(
|
||||
margin: EdgeInsets.zero,
|
||||
child: Column(
|
||||
children: [
|
||||
for (int i = 0; i < items.length; i++) ...[
|
||||
_ArticleRow(item: items[i], details: details),
|
||||
if (i < items.length - 1)
|
||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// `true`, wenn die tatsächlich auszuliefernde Menge von der ursprünglich
|
||||
/// bestellten abweicht — also die Position ganz entfernt oder teilweise
|
||||
/// gutgeschrieben wurde.
|
||||
bool _quantityChanged(DeliveryItem item) =>
|
||||
item.isRemoved || item.scanProgress.creditedQuantity > 0;
|
||||
|
||||
/// Tappbares Banner über der Artikelliste: weist darauf hin, dass sich die
|
||||
/// Menge mindestens eines Artikels geändert hat, und springt zu Step 3
|
||||
/// („Artikel"), wo die Änderungen sichtbar/verwaltbar sind.
|
||||
class _QuantityChangedBanner extends StatelessWidget {
|
||||
const _QuantityChangedBanner();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final amber = Colors.amber.shade800;
|
||||
return InkWell(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
onTap: () => context
|
||||
.read<DeliveryWorkflowBloc>()
|
||||
.add(const WorkflowGoToStep(WorkflowStep.articles)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: amber.withValues(alpha: 0.10),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: amber.withValues(alpha: 0.4)),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.edit_note, color: amber),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'Bei mindestens einem Artikel hat sich die Menge geändert. '
|
||||
'Details unter Schritt 3 „Artikel".',
|
||||
style: theme.textTheme.bodySmall?.copyWith(color: amber),
|
||||
),
|
||||
),
|
||||
Icon(Icons.chevron_right, color: amber, size: 20),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -744,6 +808,8 @@ class _ArticleRow extends StatelessWidget {
|
||||
final warehouse = details.warehouseOf(item.warehouseId);
|
||||
final isScannable = article?.scannable ?? false;
|
||||
final removed = item.isRemoved;
|
||||
// Menge tatsächlich verändert (entfernt oder teilweise gutgeschrieben)?
|
||||
final changed = _quantityChanged(item);
|
||||
|
||||
return ListTile(
|
||||
// Komponenten um eine Stufe eingerückt (gehören zum Oberartikel darüber).
|
||||
@ -756,6 +822,8 @@ class _ArticleRow extends StatelessWidget {
|
||||
foregroundColor: removed
|
||||
? theme.colorScheme.onSurfaceVariant
|
||||
: theme.colorScheme.onPrimary,
|
||||
// Bewusst die URSPRÜNGLICH bestellte Menge (requiredQuantity), nicht
|
||||
// die reduzierte — Änderungen werden separat als Hinweis ausgewiesen.
|
||||
child: Text(
|
||||
'${item.requiredQuantity}×',
|
||||
style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold),
|
||||
@ -769,19 +837,47 @@ class _ArticleRow extends StatelessWidget {
|
||||
color: removed ? theme.colorScheme.onSurfaceVariant : null,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
[
|
||||
article?.articleNumber ?? item.articleId,
|
||||
if (warehouse != null) warehouse.name,
|
||||
if (!isScannable) 'Dienstleistung',
|
||||
if (item.unitPrice > 0)
|
||||
'${item.unitPrice.toStringAsFixed(2)} € / Stück',
|
||||
].join(' · '),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
decoration: removed ? TextDecoration.lineThrough : null,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
[
|
||||
article?.articleNumber ?? item.articleId,
|
||||
if (warehouse != null) warehouse.name,
|
||||
if (!isScannable) 'Dienstleistung',
|
||||
if (item.unitPrice > 0)
|
||||
'${item.unitPrice.toStringAsFixed(2)} € / Stück',
|
||||
].join(' · '),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
decoration: removed ? TextDecoration.lineThrough : null,
|
||||
),
|
||||
),
|
||||
if (changed)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.edit_note, size: 14, color: Colors.amber.shade800),
|
||||
const SizedBox(width: 4),
|
||||
Flexible(
|
||||
child: Text(
|
||||
removed
|
||||
? 'Menge geändert: entfernt (Ursprung ${item.requiredQuantity}×)'
|
||||
: 'Menge geändert: jetzt ${item.deliveredQuantity}× '
|
||||
'(Ursprung ${item.requiredQuantity}×)',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.amber.shade800,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
trailing: isScannable
|
||||
? Text(
|
||||
|
||||
Reference in New Issue
Block a user