feat(abschluss): interne Notiz nach den Unterschriften abfragen
- Nach "Abschliessen" oeffnet sich ein Blatt "Interne Notiz" (optional, max. 2000 Zeichen). Ohne Eingabe genuegt ein Tipp auf "Ohne Notiz abschliessen"; "Zurueck zur Unterschrift" bricht nur das Abschliessen ab - Die Notiz reist im Abschluss-Aufruf mit (internalNote) und landet im Lieferbericht, nicht in den Kunden-Notizen - API-Client aus aktueller Backend-Spec neu generiert - Widget-Tests fuer das Blatt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@ -203,6 +203,7 @@ class TourRepositoryImpl implements TourRepository {
|
|||||||
String? paymentMethodId,
|
String? paymentMethodId,
|
||||||
String? actorCarId,
|
String? actorCarId,
|
||||||
bool paymentCollected = false,
|
bool paymentCollected = false,
|
||||||
|
String? internalNote,
|
||||||
}) async {
|
}) async {
|
||||||
// multipart/form-data: zwei Signatur-PNGs + ein JSON-Feld mit den
|
// multipart/form-data: zwei Signatur-PNGs + ein JSON-Feld mit den
|
||||||
// Bestätigungen. Direkt über die Dio-Instanz, weil der dart-dio-Generator
|
// Bestätigungen. Direkt über die Dio-Instanz, weil der dart-dio-Generator
|
||||||
@ -215,6 +216,7 @@ class TourRepositoryImpl implements TourRepository {
|
|||||||
'paymentCollected': paymentCollected,
|
'paymentCollected': paymentCollected,
|
||||||
if (paymentMethodId != null) 'paymentMethodId': paymentMethodId,
|
if (paymentMethodId != null) 'paymentMethodId': paymentMethodId,
|
||||||
if (actorCarId != null) 'authorCarId': actorCarId,
|
if (actorCarId != null) 'authorCarId': actorCarId,
|
||||||
|
if (internalNote != null) 'internalNote': internalNote,
|
||||||
};
|
};
|
||||||
final form = FormData.fromMap({
|
final form = FormData.fromMap({
|
||||||
'customer_signature': MultipartFile.fromBytes(
|
'customer_signature': MultipartFile.fromBytes(
|
||||||
|
|||||||
@ -101,6 +101,7 @@ abstract interface class TourRepository {
|
|||||||
String? paymentMethodId,
|
String? paymentMethodId,
|
||||||
String? actorCarId,
|
String? actorCarId,
|
||||||
bool paymentCollected = false,
|
bool paymentCollected = false,
|
||||||
|
String? internalNote,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Legt eine neue Notiz an einer Lieferung an.
|
/// Legt eine neue Notiz an einer Lieferung an.
|
||||||
|
|||||||
@ -1102,6 +1102,7 @@ class TourBloc extends Bloc<TourEvent, TourState> {
|
|||||||
paymentMethodId: event.paymentMethodId,
|
paymentMethodId: event.paymentMethodId,
|
||||||
actorCarId: event.actorCarId,
|
actorCarId: event.actorCarId,
|
||||||
paymentCollected: event.paymentCollected,
|
paymentCollected: event.paymentCollected,
|
||||||
|
internalNote: event.internalNote,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -225,6 +225,7 @@ class CompleteDelivery extends TourEvent {
|
|||||||
this.paymentMethodId,
|
this.paymentMethodId,
|
||||||
this.actorCarId,
|
this.actorCarId,
|
||||||
this.paymentCollected = false,
|
this.paymentCollected = false,
|
||||||
|
this.internalNote,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String deliveryId;
|
final String deliveryId;
|
||||||
@ -242,6 +243,10 @@ class CompleteDelivery extends TourEvent {
|
|||||||
/// Fahrer hat das Vor-Ort-Inkasso (Bar/EC) des offenen Betrags bestätigt.
|
/// Fahrer hat das Vor-Ort-Inkasso (Bar/EC) des offenen Betrags bestätigt.
|
||||||
/// `false`, wenn kein Inkasso anfiel (offen == 0 oder „Auf Rechnung").
|
/// `false`, wenn kein Inkasso anfiel (offen == 0 oder „Auf Rechnung").
|
||||||
final bool paymentCollected;
|
final bool paymentCollected;
|
||||||
|
|
||||||
|
/// Optionale interne Notiz des Fahrers (nach den Unterschriften erfasst,
|
||||||
|
/// nur für den Lieferbericht). `null` = keine.
|
||||||
|
final String? internalNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Legt eine neue (Text- oder Bild-)Notiz an einer Lieferung an. Aktuell
|
/// Legt eine neue (Text- oder Bild-)Notiz an einer Lieferung an. Aktuell
|
||||||
|
|||||||
@ -422,6 +422,7 @@ class _BottomNav extends StatelessWidget {
|
|||||||
acknowledgedNoteIds: result.acknowledgedNoteIds,
|
acknowledgedNoteIds: result.acknowledgedNoteIds,
|
||||||
paymentMethodId: payment?.paymentMethodId,
|
paymentMethodId: payment?.paymentMethodId,
|
||||||
paymentCollected: payment?.isCollectedOnSite ?? false,
|
paymentCollected: payment?.isCollectedOnSite ?? false,
|
||||||
|
internalNote: result.internalNote,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
Navigator.of(routeContext).pop();
|
Navigator.of(routeContext).pop();
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import 'package:signature/signature.dart';
|
|||||||
import 'package:hl_lieferservice/domain/entity/delivery.dart';
|
import 'package:hl_lieferservice/domain/entity/delivery.dart';
|
||||||
import 'package:hl_lieferservice/domain/entity/delivery_note.dart';
|
import 'package:hl_lieferservice/domain/entity/delivery_note.dart';
|
||||||
import 'package:hl_lieferservice/domain/entity/tour_details.dart';
|
import 'package:hl_lieferservice/domain/entity/tour_details.dart';
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/detail/presentation/widget/internal_note_sheet.dart';
|
||||||
|
|
||||||
/// Daten, die der Abschluss-Flow an den Aufrufer zurückgibt: beide
|
/// Daten, die der Abschluss-Flow an den Aufrufer zurückgibt: beide
|
||||||
/// Unterschriften als PNG plus die dokumentierten Bestätigungen des Kunden.
|
/// Unterschriften als PNG plus die dokumentierten Bestätigungen des Kunden.
|
||||||
@ -17,6 +18,7 @@ class SignatureResult {
|
|||||||
required this.receiptConfirmed,
|
required this.receiptConfirmed,
|
||||||
required this.notesAcknowledged,
|
required this.notesAcknowledged,
|
||||||
required this.acknowledgedNoteIds,
|
required this.acknowledgedNoteIds,
|
||||||
|
this.internalNote,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Uint8List customerSignaturePng;
|
final Uint8List customerSignaturePng;
|
||||||
@ -24,6 +26,10 @@ class SignatureResult {
|
|||||||
final bool receiptConfirmed;
|
final bool receiptConfirmed;
|
||||||
final bool notesAcknowledged;
|
final bool notesAcknowledged;
|
||||||
final List<String> acknowledgedNoteIds;
|
final List<String> acknowledgedNoteIds;
|
||||||
|
|
||||||
|
/// Optionale interne Notiz des Fahrers, abgefragt nach beiden
|
||||||
|
/// Unterschriften. `null` = keine.
|
||||||
|
final String? internalNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mehrstufiger Unterschrift-Flow zum Abschließen einer Lieferung.
|
/// Mehrstufiger Unterschrift-Flow zum Abschließen einer Lieferung.
|
||||||
@ -34,7 +40,8 @@ class SignatureResult {
|
|||||||
/// Stufe 1 (Kunde): sieht die Anmerkungen zur Lieferung, hakt zwei
|
/// Stufe 1 (Kunde): sieht die Anmerkungen zur Lieferung, hakt zwei
|
||||||
/// Bestätigungen ab (Anmerkungen-Kenntnisnahme — nur Pflicht, wenn Notizen
|
/// Bestätigungen ab (Anmerkungen-Kenntnisnahme — nur Pflicht, wenn Notizen
|
||||||
/// vorhanden; Empfangsbestätigung — immer Pflicht) und unterschreibt.
|
/// vorhanden; Empfangsbestätigung — immer Pflicht) und unterschreibt.
|
||||||
/// Stufe 2 (Fahrer): unterschreibt.
|
/// Stufe 2 (Fahrer): unterschreibt. Danach fragt ein Blatt optional eine
|
||||||
|
/// interne Notiz ab (nur für den Lieferbericht, nicht für den Kunden).
|
||||||
///
|
///
|
||||||
/// Erst nach beiden Unterschriften ruft die View [onSigned] mit dem
|
/// Erst nach beiden Unterschriften ruft die View [onSigned] mit dem
|
||||||
/// vollständigen [SignatureResult] auf — der Aufrufer triggert dann den
|
/// vollständigen [SignatureResult] auf — der Aufrufer triggert dann den
|
||||||
@ -133,6 +140,11 @@ class _SignatureViewState extends State<SignatureView> {
|
|||||||
final customerPng = await _customerController.toPngBytes();
|
final customerPng = await _customerController.toPngBytes();
|
||||||
final driverPng = await _driverController.toPngBytes();
|
final driverPng = await _driverController.toPngBytes();
|
||||||
if (customerPng == null || driverPng == null) return;
|
if (customerPng == null || driverPng == null) return;
|
||||||
|
if (!mounted) return;
|
||||||
|
// Nach beiden Unterschriften: optional interne Notiz. „Zurück" im
|
||||||
|
// Blatt bricht nur das Abschließen ab, die Unterschriften bleiben.
|
||||||
|
final decision = await showInternalNoteSheet(context);
|
||||||
|
if (decision == null || !mounted) return;
|
||||||
widget.onSigned(
|
widget.onSigned(
|
||||||
SignatureResult(
|
SignatureResult(
|
||||||
customerSignaturePng: customerPng,
|
customerSignaturePng: customerPng,
|
||||||
@ -141,6 +153,7 @@ class _SignatureViewState extends State<SignatureView> {
|
|||||||
notesAcknowledged: _notesEmpty ? false : _notesAccepted,
|
notesAcknowledged: _notesEmpty ? false : _notesAccepted,
|
||||||
acknowledgedNoteIds:
|
acknowledgedNoteIds:
|
||||||
_notesEmpty ? const [] : _notes.map((n) => n.id).toList(),
|
_notesEmpty ? const [] : _notes.map((n) => n.id).toList(),
|
||||||
|
internalNote: decision.note,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,145 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// Entscheidung im Blatt „Interne Notiz". `null` als Ergebnis von
|
||||||
|
/// [showInternalNoteSheet] heißt: zurück zur Unterschrift, nichts abschließen.
|
||||||
|
class InternalNoteDecision {
|
||||||
|
const InternalNoteDecision(this.note);
|
||||||
|
|
||||||
|
/// Getrimmter Text oder `null` = ohne Notiz abschließen.
|
||||||
|
final String? note;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maximale Länge — deckungsgleich mit dem Backend (2000 Zeichen).
|
||||||
|
const int internalNoteMaxLength = 2000;
|
||||||
|
|
||||||
|
/// Fragt nach beiden Unterschriften optional eine interne Notiz ab.
|
||||||
|
///
|
||||||
|
/// Ohne Eingabe ist der große Button „Ohne Notiz abschließen" — der Fahrer
|
||||||
|
/// braucht also nur einen Tipp, wenn er nichts notieren will. Die Notiz ist
|
||||||
|
/// nur für interne Zwecke (Lieferbericht) und nicht Teil der Bestätigung
|
||||||
|
/// durch den Kunden.
|
||||||
|
Future<InternalNoteDecision?> showInternalNoteSheet(BuildContext context) {
|
||||||
|
return showModalBottomSheet<InternalNoteDecision>(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
useSafeArea: true,
|
||||||
|
isDismissible: false,
|
||||||
|
enableDrag: false,
|
||||||
|
builder: (_) => const _InternalNoteSheet(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InternalNoteSheet extends StatefulWidget {
|
||||||
|
const _InternalNoteSheet();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_InternalNoteSheet> createState() => _InternalNoteSheetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InternalNoteSheetState extends State<_InternalNoteSheet> {
|
||||||
|
final TextEditingController _controller = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
// Button-Label hängt vom Text ab („Ohne" vs. „Mit Notiz").
|
||||||
|
_controller.addListener(() => setState(() {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
String? get _note {
|
||||||
|
final text = _controller.text.trim();
|
||||||
|
return text.isEmpty ? null : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final hasNote = _note != null;
|
||||||
|
return Padding(
|
||||||
|
// Tastatur schiebt das Blatt hoch, statt die Buttons zu verdecken.
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||||
|
),
|
||||||
|
// Unten Abstand zur System-Navigationsleiste, sonst liegt
|
||||||
|
// „Zurück zur Unterschrift" darunter.
|
||||||
|
child: SafeArea(
|
||||||
|
top: false,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 20, 20, 12),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.sticky_note_2_outlined,
|
||||||
|
color: theme.colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
'Interne Notiz',
|
||||||
|
style: theme.textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text(
|
||||||
|
'Optional. Nur für interne Zwecke, der Kunde sieht sie nicht. '
|
||||||
|
'Die Notiz erscheint im Lieferbericht.',
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextField(
|
||||||
|
controller: _controller,
|
||||||
|
minLines: 3,
|
||||||
|
maxLines: 6,
|
||||||
|
maxLength: internalNoteMaxLength,
|
||||||
|
textCapitalization: TextCapitalization.sentences,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: 'z. B. Kunde wünscht Rückruf wegen Montage',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
FilledButton.icon(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
minimumSize: const Size.fromHeight(56),
|
||||||
|
textStyle: const TextStyle(
|
||||||
|
fontSize: 17,
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed:
|
||||||
|
() =>
|
||||||
|
Navigator.of(context).pop(InternalNoteDecision(_note)),
|
||||||
|
icon: const Icon(Icons.check),
|
||||||
|
label: Text(
|
||||||
|
hasNote ? 'Mit Notiz abschließen' : 'Ohne Notiz abschließen',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
minimumSize: const Size.fromHeight(44),
|
||||||
|
),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
child: const Text('Zurück zur Unterschrift'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2209,6 +2209,13 @@
|
|||||||
"format": "uuid",
|
"format": "uuid",
|
||||||
"description": "Fahrzeug des Akteurs (Audit-Spur). Muss zum Account geh\u00f6ren."
|
"description": "Fahrzeug des Akteurs (Audit-Spur). Muss zum Account geh\u00f6ren."
|
||||||
},
|
},
|
||||||
|
"internalNote": {
|
||||||
|
"type": [
|
||||||
|
"string",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"description": "Optionale interne Notiz des Fahrers, abgefragt nach beiden\nUnterschriften. Nur f\u00fcr interne Zwecke (Lieferbericht), nicht Teil\nder Kunden-Best\u00e4tigung. Leer/Whitespace = keine Notiz; max. 2000 Zeichen."
|
||||||
|
},
|
||||||
"notesAcknowledged": {
|
"notesAcknowledged": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "\u201eAnmerkungen zur Lieferung zur Kenntnis genommen.\" \u2014 Pflicht nur, wenn\nNotizen existieren (das pr\u00fcft der Server)."
|
"description": "\u201eAnmerkungen zur Lieferung zur Kenntnis genommen.\" \u2014 Pflicht nur, wenn\nNotizen existieren (das pr\u00fcft der Server)."
|
||||||
|
|||||||
@ -197,13 +197,3 @@ lib/src/model/update_service_request.dart
|
|||||||
lib/src/model/warehouse.dart
|
lib/src/model/warehouse.dart
|
||||||
lib/src/serializers.dart
|
lib/src/serializers.dart
|
||||||
pubspec.yaml
|
pubspec.yaml
|
||||||
test/completed_deliveries_response_test.dart
|
|
||||||
test/completed_delivery_item_test.dart
|
|
||||||
test/delivery_details_test.dart
|
|
||||||
test/delivery_payment_response_test.dart
|
|
||||||
test/delivery_payment_test.dart
|
|
||||||
test/pending_review_response_test.dart
|
|
||||||
test/positions_modified_response_test.dart
|
|
||||||
test/record_delivery_payment_request_test.dart
|
|
||||||
test/resolve_review_request_test.dart
|
|
||||||
test/reviewed_item_response_test.dart
|
|
||||||
|
|||||||
@ -10,6 +10,7 @@ Name | Type | Description | Notes
|
|||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**acknowledgedNoteIds** | **BuiltList<String>** | Notiz-IDs, die zum Abschlusszeitpunkt sichtbar waren und mit-bestätigt wurden (Audit-Robustheit). | [optional]
|
**acknowledgedNoteIds** | **BuiltList<String>** | Notiz-IDs, die zum Abschlusszeitpunkt sichtbar waren und mit-bestätigt wurden (Audit-Robustheit). | [optional]
|
||||||
**authorCarId** | **String** | Fahrzeug des Akteurs (Audit-Spur). Muss zum Account gehören. | [optional]
|
**authorCarId** | **String** | Fahrzeug des Akteurs (Audit-Spur). Muss zum Account gehören. | [optional]
|
||||||
|
**internalNote** | **String** | Optionale interne Notiz des Fahrers, abgefragt nach beiden Unterschriften. Nur für interne Zwecke (Lieferbericht), nicht Teil der Kunden-Bestätigung. Leer/Whitespace = keine Notiz; max. 2000 Zeichen. | [optional]
|
||||||
**notesAcknowledged** | **bool** | „Anmerkungen zur Lieferung zur Kenntnis genommen.\" — Pflicht nur, wenn Notizen existieren (das prüft der Server). | [optional]
|
**notesAcknowledged** | **bool** | „Anmerkungen zur Lieferung zur Kenntnis genommen.\" — Pflicht nur, wenn Notizen existieren (das prüft der Server). | [optional]
|
||||||
**paymentCollected** | **bool** | Inkasso-Bestätigung des Fahrers: „der offene Betrag wurde erhalten (bar) bzw. über das EC-Gerät abgerechnet.\" Pflicht nur, wenn beim Abschluss ein offener Betrag > 0 besteht UND die Methode ein Vor-Ort- Inkasso ist (Bar/EC) — das prüft der Server. Der kassierte Betrag wird server-seitig autoritativ berechnet (nicht vom Client übernommen). | [optional]
|
**paymentCollected** | **bool** | Inkasso-Bestätigung des Fahrers: „der offene Betrag wurde erhalten (bar) bzw. über das EC-Gerät abgerechnet.\" Pflicht nur, wenn beim Abschluss ein offener Betrag > 0 besteht UND die Methode ein Vor-Ort- Inkasso ist (Bar/EC) — das prüft der Server. Der kassierte Betrag wird server-seitig autoritativ berechnet (nicht vom Client übernommen). | [optional]
|
||||||
**paymentMethodId** | **String** | Optionale Zahlungsmethode, die der Fahrer beim Abschluss gewählt hat. `None` = die am Beleg hinterlegte Methode bleibt. Falls gesetzt, muss sie existieren **und** aktiv sein (vom Server geprüft). | [optional]
|
**paymentMethodId** | **String** | Optionale Zahlungsmethode, die der Fahrer beim Abschluss gewählt hat. `None` = die am Beleg hinterlegte Methode bleibt. Falls gesetzt, muss sie existieren **und** aktiv sein (vom Server geprüft). | [optional]
|
||||||
|
|||||||
@ -14,6 +14,7 @@ part 'complete_delivery_acknowledgements.g.dart';
|
|||||||
/// Properties:
|
/// Properties:
|
||||||
/// * [acknowledgedNoteIds] - Notiz-IDs, die zum Abschlusszeitpunkt sichtbar waren und mit-bestätigt wurden (Audit-Robustheit).
|
/// * [acknowledgedNoteIds] - Notiz-IDs, die zum Abschlusszeitpunkt sichtbar waren und mit-bestätigt wurden (Audit-Robustheit).
|
||||||
/// * [authorCarId] - Fahrzeug des Akteurs (Audit-Spur). Muss zum Account gehören.
|
/// * [authorCarId] - Fahrzeug des Akteurs (Audit-Spur). Muss zum Account gehören.
|
||||||
|
/// * [internalNote] - Optionale interne Notiz des Fahrers, abgefragt nach beiden Unterschriften. Nur für interne Zwecke (Lieferbericht), nicht Teil der Kunden-Bestätigung. Leer/Whitespace = keine Notiz; max. 2000 Zeichen.
|
||||||
/// * [notesAcknowledged] - „Anmerkungen zur Lieferung zur Kenntnis genommen.\" — Pflicht nur, wenn Notizen existieren (das prüft der Server).
|
/// * [notesAcknowledged] - „Anmerkungen zur Lieferung zur Kenntnis genommen.\" — Pflicht nur, wenn Notizen existieren (das prüft der Server).
|
||||||
/// * [paymentCollected] - Inkasso-Bestätigung des Fahrers: „der offene Betrag wurde erhalten (bar) bzw. über das EC-Gerät abgerechnet.\" Pflicht nur, wenn beim Abschluss ein offener Betrag > 0 besteht UND die Methode ein Vor-Ort- Inkasso ist (Bar/EC) — das prüft der Server. Der kassierte Betrag wird server-seitig autoritativ berechnet (nicht vom Client übernommen).
|
/// * [paymentCollected] - Inkasso-Bestätigung des Fahrers: „der offene Betrag wurde erhalten (bar) bzw. über das EC-Gerät abgerechnet.\" Pflicht nur, wenn beim Abschluss ein offener Betrag > 0 besteht UND die Methode ein Vor-Ort- Inkasso ist (Bar/EC) — das prüft der Server. Der kassierte Betrag wird server-seitig autoritativ berechnet (nicht vom Client übernommen).
|
||||||
/// * [paymentMethodId] - Optionale Zahlungsmethode, die der Fahrer beim Abschluss gewählt hat. `None` = die am Beleg hinterlegte Methode bleibt. Falls gesetzt, muss sie existieren **und** aktiv sein (vom Server geprüft).
|
/// * [paymentMethodId] - Optionale Zahlungsmethode, die der Fahrer beim Abschluss gewählt hat. `None` = die am Beleg hinterlegte Methode bleibt. Falls gesetzt, muss sie existieren **und** aktiv sein (vom Server geprüft).
|
||||||
@ -28,6 +29,10 @@ abstract class CompleteDeliveryAcknowledgements implements Built<CompleteDeliver
|
|||||||
@BuiltValueField(wireName: r'authorCarId')
|
@BuiltValueField(wireName: r'authorCarId')
|
||||||
String? get authorCarId;
|
String? get authorCarId;
|
||||||
|
|
||||||
|
/// Optionale interne Notiz des Fahrers, abgefragt nach beiden Unterschriften. Nur für interne Zwecke (Lieferbericht), nicht Teil der Kunden-Bestätigung. Leer/Whitespace = keine Notiz; max. 2000 Zeichen.
|
||||||
|
@BuiltValueField(wireName: r'internalNote')
|
||||||
|
String? get internalNote;
|
||||||
|
|
||||||
/// „Anmerkungen zur Lieferung zur Kenntnis genommen.\" — Pflicht nur, wenn Notizen existieren (das prüft der Server).
|
/// „Anmerkungen zur Lieferung zur Kenntnis genommen.\" — Pflicht nur, wenn Notizen existieren (das prüft der Server).
|
||||||
@BuiltValueField(wireName: r'notesAcknowledged')
|
@BuiltValueField(wireName: r'notesAcknowledged')
|
||||||
bool? get notesAcknowledged;
|
bool? get notesAcknowledged;
|
||||||
@ -81,6 +86,13 @@ class _$CompleteDeliveryAcknowledgementsSerializer implements PrimitiveSerialize
|
|||||||
specifiedType: const FullType.nullable(String),
|
specifiedType: const FullType.nullable(String),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (object.internalNote != null) {
|
||||||
|
yield r'internalNote';
|
||||||
|
yield serializers.serialize(
|
||||||
|
object.internalNote,
|
||||||
|
specifiedType: const FullType.nullable(String),
|
||||||
|
);
|
||||||
|
}
|
||||||
if (object.notesAcknowledged != null) {
|
if (object.notesAcknowledged != null) {
|
||||||
yield r'notesAcknowledged';
|
yield r'notesAcknowledged';
|
||||||
yield serializers.serialize(
|
yield serializers.serialize(
|
||||||
@ -145,6 +157,14 @@ class _$CompleteDeliveryAcknowledgementsSerializer implements PrimitiveSerialize
|
|||||||
if (valueDes == null) continue;
|
if (valueDes == null) continue;
|
||||||
result.authorCarId = valueDes;
|
result.authorCarId = valueDes;
|
||||||
break;
|
break;
|
||||||
|
case r'internalNote':
|
||||||
|
final valueDes = serializers.deserialize(
|
||||||
|
value,
|
||||||
|
specifiedType: const FullType.nullable(String),
|
||||||
|
) as String?;
|
||||||
|
if (valueDes == null) continue;
|
||||||
|
result.internalNote = valueDes;
|
||||||
|
break;
|
||||||
case r'notesAcknowledged':
|
case r'notesAcknowledged':
|
||||||
final valueDes = serializers.deserialize(
|
final valueDes = serializers.deserialize(
|
||||||
value,
|
value,
|
||||||
|
|||||||
@ -13,6 +13,8 @@ class _$CompleteDeliveryAcknowledgements
|
|||||||
@override
|
@override
|
||||||
final String? authorCarId;
|
final String? authorCarId;
|
||||||
@override
|
@override
|
||||||
|
final String? internalNote;
|
||||||
|
@override
|
||||||
final bool? notesAcknowledged;
|
final bool? notesAcknowledged;
|
||||||
@override
|
@override
|
||||||
final bool? paymentCollected;
|
final bool? paymentCollected;
|
||||||
@ -28,6 +30,7 @@ class _$CompleteDeliveryAcknowledgements
|
|||||||
_$CompleteDeliveryAcknowledgements._(
|
_$CompleteDeliveryAcknowledgements._(
|
||||||
{this.acknowledgedNoteIds,
|
{this.acknowledgedNoteIds,
|
||||||
this.authorCarId,
|
this.authorCarId,
|
||||||
|
this.internalNote,
|
||||||
this.notesAcknowledged,
|
this.notesAcknowledged,
|
||||||
this.paymentCollected,
|
this.paymentCollected,
|
||||||
this.paymentMethodId,
|
this.paymentMethodId,
|
||||||
@ -48,6 +51,7 @@ class _$CompleteDeliveryAcknowledgements
|
|||||||
return other is CompleteDeliveryAcknowledgements &&
|
return other is CompleteDeliveryAcknowledgements &&
|
||||||
acknowledgedNoteIds == other.acknowledgedNoteIds &&
|
acknowledgedNoteIds == other.acknowledgedNoteIds &&
|
||||||
authorCarId == other.authorCarId &&
|
authorCarId == other.authorCarId &&
|
||||||
|
internalNote == other.internalNote &&
|
||||||
notesAcknowledged == other.notesAcknowledged &&
|
notesAcknowledged == other.notesAcknowledged &&
|
||||||
paymentCollected == other.paymentCollected &&
|
paymentCollected == other.paymentCollected &&
|
||||||
paymentMethodId == other.paymentMethodId &&
|
paymentMethodId == other.paymentMethodId &&
|
||||||
@ -59,6 +63,7 @@ class _$CompleteDeliveryAcknowledgements
|
|||||||
var _$hash = 0;
|
var _$hash = 0;
|
||||||
_$hash = $jc(_$hash, acknowledgedNoteIds.hashCode);
|
_$hash = $jc(_$hash, acknowledgedNoteIds.hashCode);
|
||||||
_$hash = $jc(_$hash, authorCarId.hashCode);
|
_$hash = $jc(_$hash, authorCarId.hashCode);
|
||||||
|
_$hash = $jc(_$hash, internalNote.hashCode);
|
||||||
_$hash = $jc(_$hash, notesAcknowledged.hashCode);
|
_$hash = $jc(_$hash, notesAcknowledged.hashCode);
|
||||||
_$hash = $jc(_$hash, paymentCollected.hashCode);
|
_$hash = $jc(_$hash, paymentCollected.hashCode);
|
||||||
_$hash = $jc(_$hash, paymentMethodId.hashCode);
|
_$hash = $jc(_$hash, paymentMethodId.hashCode);
|
||||||
@ -72,6 +77,7 @@ class _$CompleteDeliveryAcknowledgements
|
|||||||
return (newBuiltValueToStringHelper(r'CompleteDeliveryAcknowledgements')
|
return (newBuiltValueToStringHelper(r'CompleteDeliveryAcknowledgements')
|
||||||
..add('acknowledgedNoteIds', acknowledgedNoteIds)
|
..add('acknowledgedNoteIds', acknowledgedNoteIds)
|
||||||
..add('authorCarId', authorCarId)
|
..add('authorCarId', authorCarId)
|
||||||
|
..add('internalNote', internalNote)
|
||||||
..add('notesAcknowledged', notesAcknowledged)
|
..add('notesAcknowledged', notesAcknowledged)
|
||||||
..add('paymentCollected', paymentCollected)
|
..add('paymentCollected', paymentCollected)
|
||||||
..add('paymentMethodId', paymentMethodId)
|
..add('paymentMethodId', paymentMethodId)
|
||||||
@ -96,6 +102,10 @@ class CompleteDeliveryAcknowledgementsBuilder
|
|||||||
String? get authorCarId => _$this._authorCarId;
|
String? get authorCarId => _$this._authorCarId;
|
||||||
set authorCarId(String? authorCarId) => _$this._authorCarId = authorCarId;
|
set authorCarId(String? authorCarId) => _$this._authorCarId = authorCarId;
|
||||||
|
|
||||||
|
String? _internalNote;
|
||||||
|
String? get internalNote => _$this._internalNote;
|
||||||
|
set internalNote(String? internalNote) => _$this._internalNote = internalNote;
|
||||||
|
|
||||||
bool? _notesAcknowledged;
|
bool? _notesAcknowledged;
|
||||||
bool? get notesAcknowledged => _$this._notesAcknowledged;
|
bool? get notesAcknowledged => _$this._notesAcknowledged;
|
||||||
set notesAcknowledged(bool? notesAcknowledged) =>
|
set notesAcknowledged(bool? notesAcknowledged) =>
|
||||||
@ -125,6 +135,7 @@ class CompleteDeliveryAcknowledgementsBuilder
|
|||||||
if ($v != null) {
|
if ($v != null) {
|
||||||
_acknowledgedNoteIds = $v.acknowledgedNoteIds?.toBuilder();
|
_acknowledgedNoteIds = $v.acknowledgedNoteIds?.toBuilder();
|
||||||
_authorCarId = $v.authorCarId;
|
_authorCarId = $v.authorCarId;
|
||||||
|
_internalNote = $v.internalNote;
|
||||||
_notesAcknowledged = $v.notesAcknowledged;
|
_notesAcknowledged = $v.notesAcknowledged;
|
||||||
_paymentCollected = $v.paymentCollected;
|
_paymentCollected = $v.paymentCollected;
|
||||||
_paymentMethodId = $v.paymentMethodId;
|
_paymentMethodId = $v.paymentMethodId;
|
||||||
@ -154,6 +165,7 @@ class CompleteDeliveryAcknowledgementsBuilder
|
|||||||
_$CompleteDeliveryAcknowledgements._(
|
_$CompleteDeliveryAcknowledgements._(
|
||||||
acknowledgedNoteIds: _acknowledgedNoteIds?.build(),
|
acknowledgedNoteIds: _acknowledgedNoteIds?.build(),
|
||||||
authorCarId: authorCarId,
|
authorCarId: authorCarId,
|
||||||
|
internalNote: internalNote,
|
||||||
notesAcknowledged: notesAcknowledged,
|
notesAcknowledged: notesAcknowledged,
|
||||||
paymentCollected: paymentCollected,
|
paymentCollected: paymentCollected,
|
||||||
paymentMethodId: paymentMethodId,
|
paymentMethodId: paymentMethodId,
|
||||||
|
|||||||
59
test/internal_note_sheet_test.dart
Normal file
59
test/internal_note_sheet_test.dart
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:hl_lieferservice/feature/delivery/detail/presentation/widget/internal_note_sheet.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late InternalNoteDecision? result;
|
||||||
|
late bool closed;
|
||||||
|
|
||||||
|
Future<void> open(WidgetTester tester) async {
|
||||||
|
result = null;
|
||||||
|
closed = false;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Builder(
|
||||||
|
builder: (context) => Scaffold(
|
||||||
|
body: TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
result = await showInternalNoteSheet(context);
|
||||||
|
closed = true;
|
||||||
|
},
|
||||||
|
child: const Text('open'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.tap(find.text('open'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('ohne Eingabe: ein Tipp schließt ohne Notiz ab', (tester) async {
|
||||||
|
await open(tester);
|
||||||
|
await tester.tap(find.text('Ohne Notiz abschließen'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(closed, isTrue);
|
||||||
|
expect(result, isNotNull);
|
||||||
|
expect(result!.note, isNull);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('mit Eingabe: Button wechselt, Text wird getrimmt',
|
||||||
|
(tester) async {
|
||||||
|
await open(tester);
|
||||||
|
await tester.enterText(find.byType(TextField), ' Rückruf wegen Montage ');
|
||||||
|
await tester.pump();
|
||||||
|
expect(find.text('Mit Notiz abschließen'), findsOneWidget);
|
||||||
|
await tester.tap(find.text('Mit Notiz abschließen'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(result!.note, 'Rückruf wegen Montage');
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Zurück bricht das Abschließen ab', (tester) async {
|
||||||
|
await open(tester);
|
||||||
|
await tester.tap(find.text('Zurück zur Unterschrift'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(closed, isTrue);
|
||||||
|
expect(result, isNull);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user