feat: abweichende Lieferadresse und Empfänger vor Ort anzeigen

Belege können im ERP eine eigene Lieferadresse (LieferAdressId) mit
anderer Person tragen als der Besteller. Die App zeigt jetzt überall den
Empfänger vor Ort:

- TourDetails.recipientOf liefert Empfänger (Name aus der Lieferadresse,
  sonst Besteller), Lieferanschrift und differsFromOrderer.
- Sortieren, Auswählen, Beladen und Liefer-Übersicht: Empfängername +
  Lieferadresse, bei Abweichung „Bestellt von: <Kunde>".
- Lieferdetails: hervorgehobene Karte „Abweichende Lieferadresse" mit
  Empfänger, Anschrift und Kontakt; Kunden-Block als „Besteller (Kunde)"
  mit Adresse, Kundennummer und Kontaktdaten.
- Maps navigiert zur Lieferadresse statt zur Kundenadresse; AppBar,
  Unterschrift und Beladen-Avatar nutzen den Empfänger.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Dennis Nemec
2026-09-25 15:51:31 +02:00
parent 99247b4b67
commit 857c92031a
14 changed files with 600 additions and 61 deletions

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hl_lieferservice/domain/entity/article.dart';
import 'package:hl_lieferservice/domain/entity/customer.dart';
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';
@ -13,6 +12,7 @@ import 'package:hl_lieferservice/feature/cars/bloc/cars_state.dart';
import 'package:hl_lieferservice/feature/delivery/detail/presentation/delivery_detail_page.dart';
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_info.dart';
import 'package:hl_lieferservice/feature/delivery/pickup/presentation/filiale_pickup_scan_page.dart';
import 'package:hl_lieferservice/widget/orderer_hint.dart';
/// Entscheidet beim Tap auf eine Lieferung, wohin navigiert wird:
///
@ -408,6 +408,7 @@ class _NextDeliveryCard extends StatelessWidget {
final theme = Theme.of(context);
final delivery = entry.delivery;
final customer = details.customerOf(delivery);
final recipient = details.recipientOf(delivery);
final pendingExternal = details.pendingExternalWarehouseGroups(delivery);
final hasPendingExternal = pendingExternal.isNotEmpty;
@ -432,15 +433,19 @@ class _NextDeliveryCard extends StatelessWidget {
children: [
_HeaderRow(
position: entry.position,
customer: customer,
name: recipient.name,
desiredTime: delivery.desiredTime,
),
const SizedBox(height: 10),
_InfoRow(
icon: Icons.location_on_outlined,
text: delivery.deliveryAddressSnapshot.oneLine,
text: recipient.address.oneLine,
emphasized: true,
),
if (recipient.differsFromOrderer)
OrdererHint(
ordererName: customer?.name ?? '⟨Unbekannter Kunde⟩',
),
if (delivery.specialAgreements != null &&
delivery.specialAgreements!.isNotEmpty) ...[
const SizedBox(height: 6),
@ -492,12 +497,12 @@ class _NextDeliveryCard extends StatelessWidget {
class _HeaderRow extends StatelessWidget {
const _HeaderRow({
required this.position,
required this.customer,
required this.name,
required this.desiredTime,
});
final int position;
final Customer? customer;
final String name;
final String? desiredTime;
@override
@ -524,7 +529,7 @@ class _HeaderRow extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
customer?.name ?? '⟨Unbekannter Kunde⟩',
name,
style: theme.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.w700,
),
@ -791,6 +796,7 @@ class _DeliveryTile extends StatelessWidget {
final theme = Theme.of(context);
final delivery = entry.delivery;
final customer = details.customerOf(delivery);
final recipient = details.recipientOf(delivery);
final pendingExternal = details.pendingExternalWarehouseGroups(delivery);
final hasPendingExternal = pendingExternal.isNotEmpty;
@ -828,7 +834,7 @@ class _DeliveryTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
customer?.name ?? '⟨Unbekannter Kunde⟩',
recipient.name,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w600,
@ -840,12 +846,16 @@ class _DeliveryTile extends StatelessWidget {
),
const SizedBox(height: 2),
Text(
delivery.deliveryAddressSnapshot.oneLine,
recipient.address.oneLine,
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.onSurfaceVariant,
),
),
if (recipient.differsFromOrderer)
OrdererHint(
ordererName: customer?.name ?? '⟨Unbekannter Kunde⟩',
),
const SizedBox(height: 4),
Row(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -13,6 +13,7 @@ import 'package:hl_lieferservice/feature/delivery/model/delivery_phase.dart';
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_fail_page.dart';
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
import 'package:hl_lieferservice/widget/orderer_hint.dart';
/// Page für die erste Phase bei Mehr-Auto-Teams: Auswählen der eigenen
/// Lieferungen aus dem gemeinsamen Tour-Pool.
@ -241,6 +242,7 @@ class _DeliverySelectionPageState extends State<DeliverySelectionPage> {
itemBuilder: (context, index) {
final delivery = available[index];
final customer = details.customerOf(delivery);
final recipient = details.recipientOf(delivery);
final isSelected = _selectedIds.contains(delivery.id);
return CheckboxListTile(
key: ValueKey('available-${delivery.id}'),
@ -258,12 +260,13 @@ class _DeliverySelectionPageState extends State<DeliverySelectionPage> {
});
},
title: Text(
customer?.name ?? '⟨Unbekannter Kunde⟩',
recipient.name,
style: const TextStyle(fontWeight: FontWeight.w600),
),
subtitle: Text(
delivery.deliveryAddressSnapshot.oneLine,
style: const TextStyle(fontSize: 12),
subtitle: RecipientSubtitle(
recipient: recipient,
ordererName: customer?.name ?? '⟨Unbekannter Kunde⟩',
addressStyle: const TextStyle(fontSize: 12),
),
controlAffinity: ListTileControlAffinity.leading,
);
@ -289,6 +292,7 @@ class _DeliverySelectionPageState extends State<DeliverySelectionPage> {
final isOwn = delivery.assignedCarId == widget.selectedCarId;
final plate = _plateFor(delivery.assignedCarId);
final customer = details.customerOf(delivery);
final recipient = details.recipientOf(delivery);
return Material(
color: isOwn
@ -310,15 +314,16 @@ class _DeliverySelectionPageState extends State<DeliverySelectionPage> {
: theme.colorScheme.onSurfaceVariant,
),
title: Text(
customer?.name ?? '⟨Unbekannter Kunde⟩',
recipient.name,
style: TextStyle(
fontWeight: FontWeight.w600,
color: isOwn ? theme.colorScheme.primary : null,
),
),
subtitle: Text(
delivery.deliveryAddressSnapshot.oneLine,
style: const TextStyle(fontSize: 12),
subtitle: RecipientSubtitle(
recipient: recipient,
ordererName: customer?.name ?? '⟨Unbekannter Kunde⟩',
addressStyle: const TextStyle(fontSize: 12),
),
trailing: _plateBadge(context, plate, own: isOwn),
),

View File

@ -14,6 +14,7 @@ import 'package:hl_lieferservice/feature/delivery/overview/widget/sortable_deliv
import 'package:hl_lieferservice/feature/delivery/overview/presentation/delivery_fail_page.dart';
import 'package:hl_lieferservice/widget/home/presentation/home_drawer.dart';
import 'package:hl_lieferservice/widget/phase_stepper/phase_stepper.dart';
import 'package:hl_lieferservice/widget/orderer_hint.dart';
/// Page für die zweite Phase des Lieferprozesses (Sortieren). Der Fahrer
/// legt per Drag&Drop die Reihenfolge fest, ändert lokal so oft er möchte
@ -173,6 +174,7 @@ class _DeliverySortPageState extends State<DeliverySortPage> {
Widget _singleDeliveryHint(Delivery single, TourDetails details) {
final customer = details.customerOf(single);
final recipient = details.recipientOf(single);
return Column(
children: [
_hintCard(
@ -191,10 +193,11 @@ class _DeliverySortPageState extends State<DeliverySortPage> {
),
),
),
title: Text(customer?.name ?? '⟨Unbekannter Kunde⟩'),
subtitle: Text(
single.deliveryAddressSnapshot.oneLine,
style: const TextStyle(fontSize: 11),
title: Text(recipient.name),
subtitle: RecipientSubtitle(
recipient: recipient,
ordererName: customer?.name ?? '⟨Unbekannter Kunde⟩',
addressStyle: const TextStyle(fontSize: 11),
),
),
],

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:hl_lieferservice/domain/entity/delivery.dart';
import 'package:hl_lieferservice/domain/entity/tour_details.dart';
import 'package:hl_lieferservice/widget/orderer_hint.dart';
/// Drag&Drop-Liste der heutigen Lieferungen eines Fahrzeugs.
///
@ -100,6 +101,7 @@ class _SortableDeliveryListState extends State<SortableDeliveryList> {
);
}
final customer = widget.details.customerOf(delivery);
final recipient = widget.details.recipientOf(delivery);
return ListTile(
key: Key('reorder-item-${delivery.id}'),
leading: CircleAvatar(
@ -111,10 +113,11 @@ class _SortableDeliveryListState extends State<SortableDeliveryList> {
),
),
),
title: Text(customer?.name ?? '⟨Unbekannter Kunde⟩'),
subtitle: Text(
delivery.deliveryAddressSnapshot.oneLine,
style: const TextStyle(fontSize: 11),
title: Text(recipient.name),
subtitle: RecipientSubtitle(
recipient: recipient,
ordererName: customer?.name ?? '⟨Unbekannter Kunde⟩',
addressStyle: const TextStyle(fontSize: 11),
),
trailing: const Icon(Icons.drag_handle),
);