sell method
- required List<
KasseneckItem> items, - required List<
KeckPaymentInput> payments, - int? tipCents,
- bool tipKeptByEmployee = false,
- List<
String> ? customerLines, - List<
String> ? legalNotices, - String? drawerId,
- List<
KeckVoucher> ? vouchers,
Normalbeleg (Verkauf): der signierte Beleg samt Belegkopf.
drawerId: Lade, in deren Kassenstand der Beleg zaehlt (Schichtbetrieb,
Geraete-Einstellung drawerId). Ohne Angabe gilt die Lade des Geraets
bzw. die Hauptlade der Kasse. Leer oder nur Leerraum wirft vor dem Senden.
Unter /v3 gehen die Zahlungen immer als payments hinaus (siehe
KeckPaymentInput: Karte mit Anbieter und Kennung, Bar mit
tenderedCents), nie die Einzelfelder paymentMethod,
creditCardProvider, cardPaymentId, cardPaymentData aus 0.x; mixed
wird nie gesendet. Den Zahlbetrag rechnet der Server; weicht die Summe
ab, kommt payments_sum_mismatch mit expectedCents
(paymentsExpectedCents), und es wurde nichts signiert.
tipKeptByEmployee: Bar-Trinkgeld „gleich behalten“ -- die
Mitarbeiterin steckt das Trinkgeld beim Verkauf gleich ein. Der Beleg
bleibt, wie er ist; im Kassenstand zaehlt der Betrag nicht ins Bar-Soll
und gilt sofort als „schon genommen“. Nur mit Trinkgeld; dann geht
tip: {cents, keptByEmployee: true} hinaus, sonst bleibt die Nutzlast
Byte fuer Byte wie bisher. Der Server nimmt es nur bei Mitarbeiter-
Trinkgeld, bar und sofort erhalten. Vorbelegung: posTipKeptByDefault.
vouchers: Gutscheine des Belegs (Altform oder neue Form: Mehrzweck,
Einzweck, Leistung, Aktion, Auszahlung). Gepruefte Wuensche gehen roh
hinaus (KeckVoucher.toPayload); was der Server abwiese
(Serverfelder, Gutscheinzeilen in items, Verkauf und Einloesung
zusammen, Grenzen), wirft vor dem Senden. Ein Gutscheinverkauf und eine
Auszahlung brauchen keine Positionen; ein Beleg nur aus Auszahlungen
nimmt genau eine negative Barzahlung. Den Zahlbetrag rechnet
receiptDueCents mit den Pruefungen aus checkVoucher.
Implementation
Future<KasseneckReceipt> sell({
required List<KasseneckItem> items,
required List<KeckPaymentInput> payments,
int? tipCents,
bool tipKeptByEmployee = false,
List<String>? customerLines,
List<String>? legalNotices,
String? drawerId,
List<KeckVoucher>? vouchers,
}) async {
const name = Aufrufe.createReceipt;
final gutscheine = vouchers ?? const <KeckVoucher>[];
final zeilenFehler = voucherItemError(items);
if (zeilenFehler != null) throw KasseneckValidationError(name, zeilenFehler, 'request');
if (items.isEmpty && !vouchersWithoutItems(gutscheine)) {
// Ein leerer Verkauf ist kein Verkauf, und der Fehler soll fallen,
// bevor irgendetwas in die Signaturkette geraet.
throw const KasseneckValidationError(name, 'Positionen fehlen', 'request');
}
if (items.any((p) => !p.isValid)) {
throw const KasseneckValidationError(name, 'Ungueltige Position uebergeben', 'request');
}
if (tipCents != null && tipCents < 0) {
throw const KasseneckValidationError(name, 'Trinkgeld muss >= 0 sein', 'request');
}
final mitTrinkgeld = tipCents != null && tipCents > 0;
if (mitTrinkgeld && items.isEmpty) {
throw const KasseneckValidationError(name, 'Trinkgeld: Beleg braucht mindestens eine Position', 'request');
}
if (gutscheine.isNotEmpty) {
final gutscheinFehler = voucherWishError(gutscheine, items, training: false);
if (gutscheinFehler != null) throw KasseneckValidationError(name, gutscheinFehler, 'request');
}
final fehler = paymentsError(payments,
cancellation: false, payout: isPayoutOnly(gutscheine, items, withTip: mitTrinkgeld));
if (fehler != null) throw KasseneckValidationError(name, fehler, 'request');
if (drawerId != null) _ladeKennung(name, drawerId);
final daten = await _signiertRufen(
name,
params: {
'receiptType': 'standard',
'items': items.map((p) => p.toJson()).toList(),
if (gutscheine.isNotEmpty) 'vouchers': [for (final v in gutscheine) v.toPayload()],
'payments': [for (final z in payments) z.toJson()],
if (tipCents != null && tipCents > 0) 'tip': tipKeptByEmployee ? {'cents': tipCents, 'keptByEmployee': true} : tipCents,
if (customerLines != null && customerLines.isNotEmpty) 'customerDetails': customerLines.join('\n'),
if (legalNotices != null && legalNotices.isNotEmpty) 'legalMessage': legalNotices.join('\n'),
'drawerId': ?drawerId,
},
);
// Ab hier ist der Beleg signiert: wer ihn nicht lesen kann, bekommt
// `response_unreadable` (Ausgang unklar) mit der Kennung, nie einen
// gewoehnlichen Fehler, der zum zweiten Verkauf einluede.
return readSignedResponse(name, () => _belegAus(daten, name), receiptId: () => _kennungAus(daten));
}