cancelReceipt method
- required String originalReceiptId,
- required String reason,
- List<
CancellationItem> ? items, - String? returnDisposition,
- Map<
int, String> ? itemReturnDispositions, - String? note,
- List<
KeckPaymentInput> ? payments, - KasseneckReceipt? original,
Storno-Beleg zu einem bestehenden Beleg — voll oder in Teilen.
Ohne items ist es ein Vollstorno. Eine leere Positionsliste ist
dagegen ein Fehler: sonst wuerde aus einem missglueckten Teilstorno still
ein Vollstorno.
Die Restmengen und die Reichweite des Rechts haelt der Server; die Kasse bietet nur an, was sie fuer moeglich haelt.
reason ist ein Code aus cancellationReasons (input_error …, wie unter
/v3); ein unbekannter wirft, bevor etwas hinausgeht.
payments sind die Rueckzahlungen je Zahlung (Betraege negativ,
refundOf = id der Originalzahlung). Ohne Angabe spiegelt der Server
die Restbetraege jeder Originalzahlung; ein Teilstorno eines Belegs mit
mehreren Zahlungen braucht sie (cancellation_payments_required). Eine
Einzel-Zahlungsart am Storno gibt es unter /v3 nicht mehr.
Karten-Storno: provider, providerPaymentId und providerData der
Zahlung beschreiben die ERSTATTUNG am Terminal. Eine Karten-Rueckbuchung
ueber einen Anbieter braucht einen Bezug: ihre eigene providerPaymentId
oder, mit original, die Kennung der erstatteten Kartenzahlung dort
(cardRefundReference). Fehlt beides, wirft der Aufruf vor dem Senden.
returnDisposition sagt, wohin die Ware der stornierten Artikelzeilen
geht (Lager): restock, defective oder disposed aus
returnDispositions, fuer alle Positionen. itemReturnDispositions
waehlt je Position abweichend (Schluessel = Index im Original, wie in
items; nur fuer Positionen in items). Fehlt beides, bucht der Server
restock; Zeilen ohne articleId bucht er nie. Ein unbekannter Wert
wirft, bevor etwas hinausgeht. Ohne Wahl ist die Nutzlast dieselbe wie
vor 10.2.
Implementation
Future<CancelReceiptResult> cancelReceipt({
required String originalReceiptId,
required String reason,
List<CancellationItem>? items,
String? returnDisposition,
Map<int, String>? itemReturnDispositions,
String? note,
List<KeckPaymentInput>? payments,
KasseneckReceipt? original,
}) async {
const name = Aufrufe.cancelReceipt;
if (originalReceiptId.trim().isEmpty) {
throw const KasseneckValidationError(name, 'originalReceiptId fehlt', 'request');
}
if (!cancellationReasons.containsKey(reason)) {
throw const KasseneckValidationError(name, 'Storno-Grund fehlt oder ist unbekannt', 'request');
}
if (items != null) {
if (items.isEmpty) {
throw const KasseneckValidationError(name, 'positionen muss eine nicht leere Liste sein', 'request');
}
if (items.any((p) => p.index < 0 || p.quantity < 1)) {
throw const KasseneckValidationError(name, 'Storno-Menge muss eine ganze Zahl >= 1 sein', 'request');
}
}
final nutzlast = cancellationPayload(
name,
items: items,
returnDisposition: returnDisposition,
itemReturnDispositions: itemReturnDispositions,
);
if (note != null && note.length > _anmerkungHoechstlaenge) {
throw const KasseneckValidationError(name, 'Anmerkung ist zu lang', 'request');
}
if (payments != null) {
final fehler = paymentsError(payments, cancellation: true);
if (fehler != null) throw KasseneckValidationError(name, fehler, 'request');
}
if (original != null && original.receiptId != originalReceiptId) {
throw KasseneckValidationError(
name, 'original (${original.receiptId}) ist nicht der Beleg originalReceiptId ($originalReceiptId)', 'request');
}
if (payments != null) assertCardRefunds(payments, original);
final daten = await _signiertRufen(
name,
params: {
'originalReceiptId': originalReceiptId,
'reason': reason,
'items': ?nutzlast.items,
'returnDisposition': ?nutzlast.returnDisposition,
if (note != null && note.isNotEmpty) 'note': note,
if (payments != null) 'payments': [for (final z in payments) z.toJson()],
},
);
// Ab hier ist der Storno-Beleg ausgestellt und signiert. Scheitert das
// Lesen, kommt `response_unreadable` (Ausgang unklar) mit der Kennung,
// sofern die Antwort sie mitbrachte: ohne sie ist der gesetzlich
// vorgeschriebene Storno-Beleg da, aber fuer die Kasse unerreichbar, und
// ein zweiter Storno waere eine zweite Ruecknahme.
final CancelReceiptResult ergebnis = readSignedResponse(
name,
() => CancelReceiptResult.fromResponse(name, daten, () => _belegAus(daten, name)),
receiptId: () => _kennungAus(daten),
);
// Die Storno-Antwort trägt weder Layout noch Testkennzeichen. Damit der
// Storno einer Testkasse nie wie ein gültiger Beleg gedruckt wird, gelten
// die Kennzeichen des Originals, und eine Test-Umgebung steht für
// TESTKASSE (wie `KasseneckApi.cancelReceipt` mit `kr_test_`).
final beleg = ergebnis.receipt;
if (original?.testCashregister == true || testEnvironment) beleg.testCashregister = true;
if (original?.testSignature == true && !beleg.testCashregister) beleg.testSignature = true;
return ergebnis;
}