@kreiseck/kasseneck-api 1.11.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +2 -1
- package/dist/cjs/client/aufrufe.d.ts +4 -4
- package/dist/cjs/client/aufrufe.js +15 -0
- package/dist/cjs/client/receipts.d.ts +5 -0
- package/dist/cjs/client/receipts.js +65 -6
- package/dist/cjs/enums/voucher-action.d.ts +2 -0
- package/dist/cjs/enums/voucher-action.js +2 -0
- package/dist/cjs/enums/voucher-type.d.ts +10 -0
- package/dist/cjs/enums/voucher-type.js +10 -0
- package/dist/cjs/index.d.ts +3 -3
- package/dist/cjs/index.js +8 -3
- package/dist/cjs/models/index.d.ts +2 -2
- package/dist/cjs/models/index.js +6 -2
- package/dist/cjs/models/receipt-errors.d.ts +1 -1
- package/dist/cjs/models/receipt-errors.js +2 -0
- package/dist/cjs/models/receipt-item.d.ts +11 -3
- package/dist/cjs/models/receipt-item.js +22 -1
- package/dist/cjs/models/receipt.d.ts +13 -1
- package/dist/cjs/models/receipt.js +18 -2
- package/dist/cjs/models/voucher.d.ts +57 -4
- package/dist/cjs/models/voucher.js +111 -3
- package/dist/cjs/partner/fehler.d.ts +1 -1
- package/dist/cjs/partner/fehler.js +2 -0
- package/dist/cjs/partner/index.d.ts +1 -1
- package/dist/cjs/partner/typen.d.ts +7 -1
- package/dist/cjs/partner/typen.js +3 -0
- package/dist/cjs/pos/client.d.ts +2 -1
- package/dist/cjs/pos/client.js +3 -1
- package/dist/cjs/pos/gutschein.d.ts +90 -0
- package/dist/cjs/pos/gutschein.js +164 -0
- package/dist/cjs/pos/index.d.ts +3 -2
- package/dist/cjs/pos/index.js +13 -2
- package/dist/cjs/pos/settings.d.ts +3 -0
- package/dist/cjs/pos/settings.js +12 -10
- package/dist/cjs/pos/texte.d.ts +229 -0
- package/dist/cjs/pos/texte.js +96 -1
- package/dist/cjs/react/index.js +8 -2
- package/dist/cjs/receipt/due.d.ts +83 -2
- package/dist/cjs/receipt/due.js +202 -36
- package/dist/cjs/receipt/grid.js +13 -1
- package/dist/cjs/receipt/gutschein-plan.d.ts +104 -0
- package/dist/cjs/receipt/gutschein-plan.js +360 -0
- package/dist/cjs/receipt/index.d.ts +1 -1
- package/dist/cjs/receipt/index.js +3 -0
- package/dist/cjs/receipt/layout.js +175 -8
- package/dist/cjs/register/pairing.d.ts +21 -1
- package/dist/cjs/register/pairing.js +17 -0
- package/dist/cjs/stored/bon-layout.d.ts +8 -0
- package/dist/cjs/stored/bon-layout.js +23 -0
- package/dist/cjs/stored/draht.d.ts +3 -1
- package/dist/cjs/stored/draht.js +53 -6
- package/dist/cjs/stored/einstellungen.js +2 -1
- package/dist/cjs/stored/gutscheine.d.ts +3 -0
- package/dist/cjs/stored/gutscheine.js +60 -0
- package/dist/cjs/stored/index.js +2 -17
- package/dist/cjs/stored/vokabular.d.ts +6 -4
- package/dist/cjs/stored/vokabular.js +48 -2
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/client/aufrufe.d.ts +4 -4
- package/dist/esm/client/aufrufe.js +15 -0
- package/dist/esm/client/receipts.d.ts +5 -0
- package/dist/esm/client/receipts.js +66 -8
- package/dist/esm/enums/voucher-action.d.ts +2 -0
- package/dist/esm/enums/voucher-action.js +2 -0
- package/dist/esm/enums/voucher-type.d.ts +10 -0
- package/dist/esm/enums/voucher-type.js +10 -0
- package/dist/esm/index.d.ts +3 -3
- package/dist/esm/index.js +3 -3
- package/dist/esm/models/index.d.ts +2 -2
- package/dist/esm/models/index.js +2 -2
- package/dist/esm/models/receipt-errors.d.ts +1 -1
- package/dist/esm/models/receipt-errors.js +2 -0
- package/dist/esm/models/receipt-item.d.ts +11 -3
- package/dist/esm/models/receipt-item.js +20 -1
- package/dist/esm/models/receipt.d.ts +13 -1
- package/dist/esm/models/receipt.js +18 -2
- package/dist/esm/models/voucher.d.ts +57 -4
- package/dist/esm/models/voucher.js +108 -3
- package/dist/esm/partner/fehler.d.ts +1 -1
- package/dist/esm/partner/fehler.js +2 -0
- package/dist/esm/partner/index.d.ts +1 -1
- package/dist/esm/partner/typen.d.ts +7 -1
- package/dist/esm/partner/typen.js +3 -0
- package/dist/esm/pos/client.d.ts +2 -1
- package/dist/esm/pos/client.js +3 -1
- package/dist/esm/pos/gutschein.d.ts +90 -0
- package/dist/esm/pos/gutschein.js +158 -0
- package/dist/esm/pos/index.d.ts +3 -2
- package/dist/esm/pos/index.js +4 -2
- package/dist/esm/pos/settings.d.ts +3 -0
- package/dist/esm/pos/settings.js +2 -0
- package/dist/esm/pos/texte.d.ts +229 -0
- package/dist/esm/pos/texte.js +94 -0
- package/dist/esm/react/index.js +8 -2
- package/dist/esm/receipt/due.d.ts +83 -2
- package/dist/esm/receipt/due.js +198 -34
- package/dist/esm/receipt/grid.js +13 -1
- package/dist/esm/receipt/gutschein-plan.d.ts +104 -0
- package/dist/esm/receipt/gutschein-plan.js +352 -0
- package/dist/esm/receipt/index.d.ts +1 -1
- package/dist/esm/receipt/index.js +1 -1
- package/dist/esm/receipt/layout.js +175 -8
- package/dist/esm/register/pairing.d.ts +21 -1
- package/dist/esm/register/pairing.js +16 -0
- package/dist/esm/stored/bon-layout.d.ts +8 -0
- package/dist/esm/stored/bon-layout.js +20 -0
- package/dist/esm/stored/draht.d.ts +3 -1
- package/dist/esm/stored/draht.js +52 -6
- package/dist/esm/stored/einstellungen.js +3 -2
- package/dist/esm/stored/gutscheine.d.ts +3 -0
- package/dist/esm/stored/gutscheine.js +56 -0
- package/dist/esm/stored/index.js +2 -17
- package/dist/esm/stored/vokabular.d.ts +6 -4
- package/dist/esm/stored/vokabular.js +48 -2
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/fixtures/expected/voucher-cancellation.grid32.txt +30 -0
- package/fixtures/expected/voucher-cancellation.grid48.txt +29 -0
- package/fixtures/expected/voucher-cancellation.lines.json +252 -0
- package/fixtures/expected/voucher-cancellation.sheet32.json +206 -0
- package/fixtures/expected/voucher-cancellation.sheet48.json +200 -0
- package/fixtures/expected/voucher-multi-purpose-redeem.grid32.txt +25 -0
- package/fixtures/expected/voucher-multi-purpose-redeem.grid48.txt +25 -0
- package/fixtures/expected/voucher-multi-purpose-redeem.lines.json +259 -0
- package/fixtures/expected/voucher-multi-purpose-redeem.sheet32.json +176 -0
- package/fixtures/expected/voucher-multi-purpose-redeem.sheet48.json +176 -0
- package/fixtures/expected/voucher-multi-purpose-sale.grid32.txt +24 -0
- package/fixtures/expected/voucher-multi-purpose-sale.grid48.txt +23 -0
- package/fixtures/expected/voucher-multi-purpose-sale.lines.json +216 -0
- package/fixtures/expected/voucher-multi-purpose-sale.sheet32.json +170 -0
- package/fixtures/expected/voucher-multi-purpose-sale.sheet48.json +164 -0
- package/fixtures/expected/voucher-payout.grid32.txt +23 -0
- package/fixtures/expected/voucher-payout.grid48.txt +22 -0
- package/fixtures/expected/voucher-payout.lines.json +216 -0
- package/fixtures/expected/voucher-payout.sheet32.json +164 -0
- package/fixtures/expected/voucher-payout.sheet48.json +158 -0
- package/fixtures/expected/voucher-service-redeem.grid32.txt +26 -0
- package/fixtures/expected/voucher-service-redeem.grid48.txt +25 -0
- package/fixtures/expected/voucher-service-redeem.lines.json +261 -0
- package/fixtures/expected/voucher-service-redeem.sheet32.json +182 -0
- package/fixtures/expected/voucher-service-redeem.sheet48.json +176 -0
- package/fixtures/expected/voucher-single-purpose-redeem-exempt.grid32.txt +30 -0
- package/fixtures/expected/voucher-single-purpose-redeem-exempt.grid48.txt +29 -0
- package/fixtures/expected/voucher-single-purpose-redeem-exempt.lines.json +331 -0
- package/fixtures/expected/voucher-single-purpose-redeem-exempt.sheet32.json +206 -0
- package/fixtures/expected/voucher-single-purpose-redeem-exempt.sheet48.json +200 -0
- package/fixtures/expected/voucher-single-purpose-redeem.grid32.txt +25 -0
- package/fixtures/expected/voucher-single-purpose-redeem.grid48.txt +24 -0
- package/fixtures/expected/voucher-single-purpose-redeem.lines.json +246 -0
- package/fixtures/expected/voucher-single-purpose-redeem.sheet32.json +176 -0
- package/fixtures/expected/voucher-single-purpose-redeem.sheet48.json +170 -0
- package/fixtures/expected/voucher-single-purpose-sale.grid32.txt +26 -0
- package/fixtures/expected/voucher-single-purpose-sale.grid48.txt +24 -0
- package/fixtures/expected/voucher-single-purpose-sale.lines.json +225 -0
- package/fixtures/expected/voucher-single-purpose-sale.sheet32.json +182 -0
- package/fixtures/expected/voucher-single-purpose-sale.sheet48.json +170 -0
- package/fixtures/hobex-hps-codes.json +1 -1
- package/fixtures/invoice-api.schema.json +1 -1
- package/fixtures/invoice-texts.json +1 -1
- package/fixtures/manifest.json +64 -0
- package/fixtures/pos-texts.json +257 -1
- package/fixtures/receipt-due-generated.json +1 -1
- package/fixtures/receipt-due-voucher-generated.json +494 -0
- package/fixtures/receipts/voucher-cancellation.json +76 -0
- package/fixtures/receipts/voucher-multi-purpose-redeem.json +62 -0
- package/fixtures/receipts/voucher-multi-purpose-sale.json +63 -0
- package/fixtures/receipts/voucher-payout.json +65 -0
- package/fixtures/receipts/voucher-service-redeem.json +82 -0
- package/fixtures/receipts/voucher-single-purpose-redeem-exempt.json +86 -0
- package/fixtures/receipts/voucher-single-purpose-redeem.json +71 -0
- package/fixtures/receipts/voucher-single-purpose-sale.json +88 -0
- package/fixtures/surface.json +35 -5
- package/fixtures/v3/antworten/belege.json +3405 -3
- package/fixtures/v3/antworten/belegmail.json +5 -2
- package/fixtures/v3/antworten/kasse-belege.json +3423 -3
- package/fixtures/v3/antworten/kasse.json +621 -14
- package/fixtures/v3/antworten/lager.json +1 -1
- package/fixtures/v3/antworten/rechnungen.json +5 -2
- package/fixtures/v3/antworten/storno.json +214 -3
- package/fixtures/v3/stored/belege.json +5 -2
- package/fixtures/v3/stored/kasse.json +225 -4
- package/fixtures/v3/stored/rechnungen.json +5 -2
- package/fixtures/v3/v3-vokabular.json +1375 -23
- package/fixtures/v3/zahlbetrag-faelle.json +293 -3
- package/package.json +4 -1
package/dist/cjs/pos/texte.js
CHANGED
|
@@ -22,11 +22,12 @@
|
|
|
22
22
|
* `placeholders` und `structure`).
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.RETURN_DISPOSITION_LABELS = exports.LABELS = exports.CANCELLATION_PAYMENT_ERROR_MESSAGES = exports.RECEIPT_EMAIL_ERROR_MESSAGES = exports.CALLS_WITH_EFFECT = exports.ERROR_OUTCOME_RULES = exports.ERROR_CODE_RULES = exports.ERROR_RULES = exports.MESSAGES = void 0;
|
|
25
|
+
exports.RETURN_DISPOSITION_LABELS = exports.LABELS = exports.VOUCHER_ERROR_MESSAGES = exports.CANCELLATION_PAYMENT_ERROR_MESSAGES = exports.RECEIPT_EMAIL_ERROR_MESSAGES = exports.CALLS_WITH_EFFECT = exports.ERROR_OUTCOME_RULES = exports.ERROR_CODE_RULES = exports.ERROR_RULES = exports.MESSAGES = void 0;
|
|
26
26
|
exports.findErrorRule = findErrorRule;
|
|
27
27
|
exports.messageOutcome = messageOutcome;
|
|
28
28
|
exports.receiptEmailErrorMessage = receiptEmailErrorMessage;
|
|
29
29
|
exports.cancellationPaymentErrorMessage = cancellationPaymentErrorMessage;
|
|
30
|
+
exports.voucherErrorMessage = voucherErrorMessage;
|
|
30
31
|
exports.labelText = labelText;
|
|
31
32
|
exports.messageText = messageText;
|
|
32
33
|
exports.messageAppliesTo = messageAppliesTo;
|
|
@@ -187,6 +188,39 @@ const MELDUNGEN_ROH = {
|
|
|
187
188
|
'cancellation.tip_source_missing': { text: 'Bitte wählen: Woher kommt das Trinkgeld zurück?' },
|
|
188
189
|
// Beleg mit Wertgutschein: keine Frage, das Trinkgeld geht mit (Ruling R59, asksRefundTip).
|
|
189
190
|
'cancellation.tip_refunded_with_voucher': { text: 'Beleg mit Gutschein: Das Trinkgeld wird mitstorniert.' },
|
|
191
|
+
// --- Gutscheine: Fehlersätze (Zuordnung in VOUCHER_ERROR_MESSAGES) ---------
|
|
192
|
+
'voucher_error.not_found': { text: 'Diesen Gutschein gibt es in diesem Betrieb nicht.' },
|
|
193
|
+
'voucher_error.invalid': { text: 'Dieser Gutschein ist nicht gültig.' },
|
|
194
|
+
'voucher_error.invalid_code': { text: 'Der Code ist nicht vollständig oder falsch abgetippt.' },
|
|
195
|
+
'voucher_error.expired': { text: 'Der Gutschein ist abgelaufen.' },
|
|
196
|
+
'voucher_error.used_up': { text: 'Der Gutschein ist bereits aufgebraucht.' },
|
|
197
|
+
'voucher_error.blocked': { text: 'Der Gutschein ist gesperrt.' },
|
|
198
|
+
'voucher_error.cancelled': { text: 'Der Gutschein wurde storniert.' },
|
|
199
|
+
'voucher_error.insufficient_balance': { text: 'Auf dem Gutschein ist nicht genug Guthaben.' },
|
|
200
|
+
'voucher_error.exceeds_total': { text: 'Der Gutschein deckt mehr, als offen ist. Bitte den Betrag anpassen.' },
|
|
201
|
+
'voucher_error.balance_changed': { text: 'Der Gutschein wurde gerade anderswo verwendet. Bitte neu prüfen.' },
|
|
202
|
+
'voucher_error.rate_mismatch': { text: 'Dieser Gutschein gilt nur für Leistungen mit {rate} %.', placeholders: ['rate'] },
|
|
203
|
+
'voucher_error.item_mismatch': { text: 'Dieser Gutschein gilt nicht für die gewählten Positionen.' },
|
|
204
|
+
'voucher_error.already_used': { text: 'Dieser Gutschein wurde bereits verwendet.' },
|
|
205
|
+
'voucher_error.partial_cancel': { text: 'Belege mit Gutschein lassen sich nur ganz stornieren.' },
|
|
206
|
+
'voucher_error.settings_missing': { text: 'Die Gutschein-Einstellungen fehlen. Bitte die Kasse neu laden.' },
|
|
207
|
+
'voucher_error.too_many_checks': { text: 'Zu viele Versuche. Bitte in einer Stunde wieder.' },
|
|
208
|
+
'voucher_error.sale_off': { text: 'Gutscheine verkaufen geht hier noch nicht.' },
|
|
209
|
+
// Gutscheine an der Kasse, Stufe 3 (1.13.0): Sätze der Kasse selbst (nicht des Servers).
|
|
210
|
+
'voucher.reentry_needed': { text: 'Die Karte ist belastet, der Beleg fehlt noch: zu jedem Gutschein den Code erneut eingeben, dann den Beleg abschließen.' },
|
|
211
|
+
'voucher.reentry_mismatch': { text: 'Mit diesen Gutscheinen ergibt der Beleg nicht mehr den belasteten Betrag – die Karte zurückbuchen.' },
|
|
212
|
+
'voucher.not_this_voucher': { text: 'Das ist nicht der Gutschein ····-{suffix}.', placeholders: ['suffix'] },
|
|
213
|
+
'voucher.reentry_no_permission': { text: 'Die Karte ist belastet, der Beleg fehlt noch: den Gutschein-Code kann nur jemand mit dem Recht zum Einlösen nachtragen – sonst die Karte zurückbuchen.' },
|
|
214
|
+
'voucher.already_covered': { text: 'Diese Positionen deckt schon ein anderer Gutschein.' },
|
|
215
|
+
'voucher.payout_reason_too_long': { text: 'Der Grund hat höchstens 200 Zeichen.' },
|
|
216
|
+
'voucher.payout_reason_missing': { text: 'Für die Auszahlung fehlt der Grund.' },
|
|
217
|
+
'voucher.payout_check_first': { text: 'Die letzte Auszahlung ging unklar aus und kann schon gebucht sein. Erst unter „Belege“ die Liste neu laden und nachsehen, dann ist eine neue möglich.' },
|
|
218
|
+
'voucher.sale_max_amount': { text: 'Höchstens {amount} je Gutschein.', placeholders: ['amount'] },
|
|
219
|
+
'voucher.sale_not_at_terminal': { text: 'Gutscheine mit Karte am Terminal zu verkaufen geht an dieser Kasse noch nicht – bar kassieren.' },
|
|
220
|
+
'voucher.no_redeem_with_sale': { text: 'Auf einem Beleg, der Gutscheine verkauft, lässt sich kein Gutschein einlösen.' },
|
|
221
|
+
'voucher.code_not_on_receipt': { text: 'Der Code steht nicht auf dem Bon – jetzt auf den Gutschein schreiben.' },
|
|
222
|
+
'cancellation.voucher_sale_cancelled': { text: 'Der Gutschein ····-{suffix} wird mitstorniert und gilt danach nicht mehr.', placeholders: ['suffix'] },
|
|
223
|
+
'cancellation.promo_valid_again': { text: 'Der Aktionsgutschein ····-{suffix} gilt danach wieder.', placeholders: ['suffix'] },
|
|
190
224
|
// --- Getrennt zahlen -----------------------------------------------------
|
|
191
225
|
// Ein Tisch zahlt in Teilen, ein Beleg fuer alles. Das Teure ist eine schon
|
|
192
226
|
// belastete Karte: jeder Satz, der sie betrifft, nennt den Betrag, und keiner
|
|
@@ -554,6 +588,34 @@ function cancellationPaymentErrorMessage(code) {
|
|
|
554
588
|
const fehler = exports.CANCELLATION_PAYMENT_ERROR_MESSAGES;
|
|
555
589
|
return (code !== undefined && code !== null && fehler[code]) || 'cancellation.failed';
|
|
556
590
|
}
|
|
591
|
+
/**
|
|
592
|
+
* Gutschein-Fehler (`/v3`): welcher `code` des Backends welchen Satz bekommt.
|
|
593
|
+
* Wie RECEIPT_EMAIL_ERROR_MESSAGES: beide Kassen entscheiden am Code. Ein
|
|
594
|
+
* unbekannter Code geht den allgemeinen Weg des Aufrufs.
|
|
595
|
+
*/
|
|
596
|
+
exports.VOUCHER_ERROR_MESSAGES = {
|
|
597
|
+
voucher_not_found: 'voucher_error.not_found',
|
|
598
|
+
voucher_invalid: 'voucher_error.invalid',
|
|
599
|
+
voucher_invalid_code: 'voucher_error.invalid_code',
|
|
600
|
+
voucher_expired: 'voucher_error.expired',
|
|
601
|
+
voucher_used_up: 'voucher_error.used_up',
|
|
602
|
+
voucher_blocked: 'voucher_error.blocked',
|
|
603
|
+
voucher_cancelled: 'voucher_error.cancelled',
|
|
604
|
+
voucher_insufficient_balance: 'voucher_error.insufficient_balance',
|
|
605
|
+
voucher_exceeds_total: 'voucher_error.exceeds_total',
|
|
606
|
+
voucher_balance_changed: 'voucher_error.balance_changed',
|
|
607
|
+
voucher_rate_mismatch: 'voucher_error.rate_mismatch',
|
|
608
|
+
voucher_item_mismatch: 'voucher_error.item_mismatch',
|
|
609
|
+
voucher_already_used: 'voucher_error.already_used',
|
|
610
|
+
voucher_partial_cancel: 'voucher_error.partial_cancel',
|
|
611
|
+
voucher_settings_missing: 'voucher_error.settings_missing',
|
|
612
|
+
too_many_voucher_checks: 'voucher_error.too_many_checks',
|
|
613
|
+
};
|
|
614
|
+
/** Der Satz zu einem Gutschein-`code`; `undefined` für jeden anderen Code. */
|
|
615
|
+
function voucherErrorMessage(code) {
|
|
616
|
+
const fehler = exports.VOUCHER_ERROR_MESSAGES;
|
|
617
|
+
return code !== undefined && code !== null ? fehler[code] : undefined;
|
|
618
|
+
}
|
|
557
619
|
/**
|
|
558
620
|
* Beschriftungen: Knoepfe, Ueberschriften, Zeilennamen – was kein Satz ist.
|
|
559
621
|
*
|
|
@@ -831,6 +893,39 @@ const BESCHRIFTUNGEN_ROH = {
|
|
|
831
893
|
'shift.collected_outside_drawer': { text: 'Kassiert, aber nicht in der Kassenlade' },
|
|
832
894
|
'shift.non_cash_short': { text: 'unbar' },
|
|
833
895
|
'shift.voucher_redeemed': { text: 'Gutschein eingelöst' },
|
|
896
|
+
// Gutscheine an der Kasse (1.12.0)
|
|
897
|
+
'voucher.button': { text: 'Gutschein' },
|
|
898
|
+
'voucher.redeem': { text: 'Gutschein einlösen' },
|
|
899
|
+
'voucher.sell': { text: 'Gutschein verkaufen' },
|
|
900
|
+
'voucher.payout': { text: 'Gutschein auszahlen' },
|
|
901
|
+
'voucher.code': { text: 'Code' },
|
|
902
|
+
'voucher.scan': { text: 'Code scannen' },
|
|
903
|
+
'voucher.check': { text: 'Prüfen' },
|
|
904
|
+
'voucher.amount': { text: 'Betrag' },
|
|
905
|
+
'voucher.balance': { text: 'Rest am Gutschein: {amount}', placeholders: ['amount'] },
|
|
906
|
+
'voucher.remaining_quantity': { text: '{n} von {total} übrig', placeholders: ['n', 'total'] },
|
|
907
|
+
'voucher.reason': { text: 'Grund' },
|
|
908
|
+
'voucher.multi_purpose': { text: 'Mehrzweckgutschein' },
|
|
909
|
+
'voucher.single_purpose': { text: 'Einzweckgutschein {rate} %', placeholders: ['rate'] },
|
|
910
|
+
'voucher.service': { text: 'Leistungsgutschein' },
|
|
911
|
+
'voucher.promotion': { text: 'Aktionsgutschein' },
|
|
912
|
+
'voucher.payment_line': { text: 'Gutschein ····-{suffix}', placeholders: ['suffix'] },
|
|
913
|
+
'voucher.covers_items': { text: 'Deckt: {items}', placeholders: ['items'] },
|
|
914
|
+
'voucher.code_shown_once': { text: 'Code nur jetzt sichtbar: {code}', placeholders: ['code'] },
|
|
915
|
+
'voucher.not_in_split': { text: 'Gutscheine gehen beim getrennten Kassieren nicht.' },
|
|
916
|
+
'voucher.payout_not_possible': { text: 'Dieser Gutschein lässt sich nicht auszahlen.' },
|
|
917
|
+
'voucher.add_article': { text: '{article} hinzufügen', placeholders: ['article'] },
|
|
918
|
+
'voucher.payout_confirm': { text: '{amount} bar auszahlen?', placeholders: ['amount'] },
|
|
919
|
+
'cancellation.voucher_back': { text: '{amount} geht zurück auf den Gutschein', placeholders: ['amount'] },
|
|
920
|
+
// Gutscheine an der Kasse, Stufe 3 (1.13.0)
|
|
921
|
+
'voucher.apply': { text: 'Übernehmen' },
|
|
922
|
+
'voucher.redeemable_until': { text: 'einlösbar bis {date}', placeholders: ['date'] },
|
|
923
|
+
'voucher.remove_line': { text: '{line} entfernen', placeholders: ['line'] },
|
|
924
|
+
'voucher.reenter_code': { text: 'Code erneut eingeben' },
|
|
925
|
+
'voucher.refundable_until': { text: 'rückzahlbar bis {date}', placeholders: ['date'] },
|
|
926
|
+
'voucher.code_done': { text: 'Fertig' },
|
|
927
|
+
'voucher.article': { text: 'Artikel' },
|
|
928
|
+
'voucher.add': { text: 'Hinzufügen' },
|
|
834
929
|
'shift.all_payments': { text: 'Alle Zahlungen dieser Schicht (bar + unbar)' },
|
|
835
930
|
'shift.receipt_count_one': { text: '1 Beleg' },
|
|
836
931
|
'shift.receipt_count_other': { text: '{count} Belege', placeholders: ['count'] },
|
package/dist/cjs/react/index.js
CHANGED
|
@@ -48,8 +48,14 @@ function HiddenQr({ data, text = 'Antippen zum Anzeigen', children }) {
|
|
|
48
48
|
}
|
|
49
49
|
function Zeile({ zeile, renderQr, qrVerdeckt, qrVerdecktText }) {
|
|
50
50
|
switch (zeile.kind) {
|
|
51
|
-
case 'text':
|
|
52
|
-
|
|
51
|
+
case 'text': {
|
|
52
|
+
// Haengender Einzug wie im Zeichenraster: fuehrende Leerzeichen eines
|
|
53
|
+
// linksbuendigen Textes werden zum Einzug aller Zeilen (HTML liesse sie
|
|
54
|
+
// sonst wegfallen). Ohne fuehrende Leerzeichen bleibt alles wie bisher.
|
|
55
|
+
const einzug = zeile.align === 'left' && zeile.text.trimStart() !== '' ? zeile.text.length - zeile.text.trimStart().length : 0;
|
|
56
|
+
const text = einzug > 0 ? zeile.text.trimStart() : zeile.text;
|
|
57
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: "keck-receipt-text", style: { textAlign: AUSRICHTUNG[zeile.align], fontWeight: zeile.bold ? 'bold' : 'normal', ...(einzug > 0 ? { paddingLeft: `${einzug}ch` } : {}) }, children: text.length > 0 ? text : ' ' }));
|
|
58
|
+
}
|
|
53
59
|
case 'columns':
|
|
54
60
|
return ((0, jsx_runtime_1.jsx)("div", { className: "keck-receipt-row", style: { display: 'flex', width: '100%', ...(zeile.bold === true ? { fontWeight: 'bold' } : {}) }, children: zeile.columns.map((spalte, index) => ((0, jsx_runtime_1.jsx)("div", { className: "keck-receipt-cell", style: {
|
|
55
61
|
// Die Breite ist ein Zwoelftel-Anteil (wie beim Bondrucker);
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { ReceiptType, type ReceiptTypeKey, KeckPaymentMethod } from '../enums/index.js';
|
|
2
2
|
import type { ReceiptItem } from '../models/receipt-item.js';
|
|
3
3
|
import type { Voucher } from '../models/voucher.js';
|
|
4
|
+
import type { PosVoucherSettings, VoucherCheck } from '../pos/gutschein.js';
|
|
5
|
+
import { type VoucherDueArticle, type VoucherDueInfo } from './gutschein-plan.js';
|
|
6
|
+
export { VOUCHER_ZERO_RATE_BUCKET_FROM, type VoucherDueArticle, type VoucherDueInfo } from './gutschein-plan.js';
|
|
4
7
|
/**
|
|
5
8
|
* Zahlbetrag eines Belegs in ganzen Cent: Zwilling von `createReceipt` im
|
|
6
9
|
* Backend (Nachtrag §8), also `beleg-toepfe.belegToepfe(...).zaehlerDeltaCents`
|
|
@@ -34,6 +37,19 @@ import type { Voucher } from '../models/voucher.js';
|
|
|
34
37
|
* sobald Trinkgeld ohne `recipients` vorkommt. Ohne angemeldeten Benutzer
|
|
35
38
|
* (Geraete-Schluessel) gilt `'staff'`.
|
|
36
39
|
*
|
|
40
|
+
* **Gutscheine der neuen Form** (Mehrzweck, Einzweck, Leistung, Aktion,
|
|
41
|
+
* Auszahlung): der Server macht daraus Positionen und Eintraege
|
|
42
|
+
* (`gutschein-beleg.plane`, Zwilling in `gutschein-plan.ts`). Was er dafuer
|
|
43
|
+
* aus seinen Daten liest, gibt die Kasse mit: je Einloesung/Auszahlung die
|
|
44
|
+
* Pruefung aus `checkVoucher` (`voucherInfo`, ueber den Code zugeordnet), fuer
|
|
45
|
+
* einen Einzweck-Verkauf die Gutschein-Einstellung der Kasse
|
|
46
|
+
* (`voucherSettings`, Satz), fuer einen Leistungsgutschein-Verkauf den Artikel
|
|
47
|
+
* (`voucherArticles`, Preis und Satz) und `smallBusiness` fuer ein
|
|
48
|
+
* Kleinunternehmer-Konto. Was fehlt oder was der Server abwiese, wirft
|
|
49
|
+
* `invalid_voucher` mit dem erwarteten Server-Code in `voucherErrorCode`.
|
|
50
|
+
* Ein Beleg nur aus Auszahlungen hat einen negativen Zahlbetrag (genau eine
|
|
51
|
+
* Barzahlung ueber diesen Betrag).
|
|
52
|
+
*
|
|
37
53
|
* **Nicht rechenbar:** jede unbrauchbare Eingabe wirft [ReceiptDueError]
|
|
38
54
|
* (`code: 'receipt_due_unavailable'`, `reason` im Einzelnen), nie still eine
|
|
39
55
|
* falsche Zahl. Dann ist nichts gesendet.
|
|
@@ -62,6 +78,42 @@ export interface ReceiptDueOptions {
|
|
|
62
78
|
}>;
|
|
63
79
|
/** Pflicht, sobald Trinkgeld ohne `recipients` vorkommt (siehe Modulkommentar). */
|
|
64
80
|
tipRecipient?: ReceiptDueTipRecipient;
|
|
81
|
+
/**
|
|
82
|
+
* Je Einloesung bzw. Auszahlung der neuen Form die Pruefung aus
|
|
83
|
+
* `checkVoucher`, ueber den Code zugeordnet. Fehlt sie fuer einen Code,
|
|
84
|
+
* wirft die Rechnung `invalid_voucher` mit `voucherErrorCode:
|
|
85
|
+
* 'voucher_not_found'` (die Kasse hat den Code nicht geprueft).
|
|
86
|
+
*/
|
|
87
|
+
voucherInfo?: readonly VoucherDueInfo[];
|
|
88
|
+
/**
|
|
89
|
+
* Gutschein-Einstellung der Kasse (`getKasseSettings`, Block `vouchers`):
|
|
90
|
+
* Pflicht fuer jeden Verkauf eines Mehrzweck- oder Einzweckgutscheins (der
|
|
91
|
+
* Server verkauft nur mit Einstellung). Fehlt sie oder ist keine Art
|
|
92
|
+
* gewaehlt: `voucher_settings_missing`; eine andere Art als die
|
|
93
|
+
* eingestellte: `voucher_invalid`.
|
|
94
|
+
*/
|
|
95
|
+
voucherSettings?: PosVoucherSettings;
|
|
96
|
+
/**
|
|
97
|
+
* Artikel fuer den Verkauf von Dienstleistungsgutscheinen (Preis und Satz,
|
|
98
|
+
* wie im Artikelstamm). Fehlt der verkaufte Artikel hier:
|
|
99
|
+
* `voucher_item_mismatch`, wie beim Server fuer einen unbekannten Artikel.
|
|
100
|
+
*/
|
|
101
|
+
voucherArticles?: readonly VoucherDueArticle[];
|
|
102
|
+
/**
|
|
103
|
+
* Das Konto ist Kleinunternehmer (Konto-Fakten): Einzweck/Leistung werden zu
|
|
104
|
+
* 0 % verkauft, eine Einzweck-Einloesung verteilt sich anteilig auf alle
|
|
105
|
+
* Saetze des Korbs. Dasselbe gilt fuer einen befreit verkauften Gutschein
|
|
106
|
+
* (`VoucherCheck.exempt`) in jedem Konto, wie der Server (`kuEingefroren || ku`).
|
|
107
|
+
*/
|
|
108
|
+
smallBusiness?: boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Stichtag des 0-%-Topfs fuer Mehrzweck-Verkaeufe (`YYYY-MM-DD`). Vorgabe
|
|
111
|
+
* [VOUCHER_ZERO_RATE_BUCKET_FROM], wie im Backend. Nur angeben, wenn der
|
|
112
|
+
* Server ihn nachweislich anders fuehrt (Test-Umgebung).
|
|
113
|
+
*/
|
|
114
|
+
zeroRateBucketFrom?: string;
|
|
115
|
+
/** Wiener Tag des Belegs (`YYYY-MM-DD`); Vorgabe: heute in Wien. */
|
|
116
|
+
today?: string;
|
|
65
117
|
}
|
|
66
118
|
/** Die sechs RKSV-Toepfe in Cent, Namen wie am Beleg des Backends. */
|
|
67
119
|
export interface ReceiptDueBuckets {
|
|
@@ -108,7 +160,13 @@ export declare class ReceiptDueError extends Error {
|
|
|
108
160
|
readonly reason: ReceiptDueErrorReason;
|
|
109
161
|
/** Immer `'rejected'`: es ging nichts hinaus. */
|
|
110
162
|
readonly outcome: "rejected";
|
|
111
|
-
|
|
163
|
+
/**
|
|
164
|
+
* Nur bei `invalid_voucher`: der Gutschein-Code, mit dem der Server
|
|
165
|
+
* denselben Beleg abwiese (`voucher_rate_mismatch`, `voucher_exceeds_total`,
|
|
166
|
+
* `voucher_not_found` …), fuer die Meldung an der Kasse.
|
|
167
|
+
*/
|
|
168
|
+
readonly voucherErrorCode?: string;
|
|
169
|
+
constructor(reason: ReceiptDueErrorReason, message: string, voucherErrorCode?: string);
|
|
112
170
|
}
|
|
113
171
|
export declare function isReceiptDueError(error: unknown): error is ReceiptDueError;
|
|
114
172
|
type BelegArt = ReceiptType | ReceiptTypeKey | string;
|
|
@@ -116,4 +174,27 @@ type BelegArt = ReceiptType | ReceiptTypeKey | string;
|
|
|
116
174
|
export declare function receiptDueCents(items: readonly ReceiptItem[], vouchers: readonly Voucher[], receiptType: BelegArt, options?: ReceiptDueOptions): number;
|
|
117
175
|
/** Wie [receiptDueCents], dazu Zaehler-Delta, Wertgutschein-Anteil und Toepfe. */
|
|
118
176
|
export declare function receiptDueBreakdown(items: readonly ReceiptItem[], vouchers: readonly Voucher[], receiptType: BelegArt, options?: ReceiptDueOptions): ReceiptDueBreakdown;
|
|
119
|
-
|
|
177
|
+
/** Schon gewaehlte Gutscheine und Optionen des Belegs, vor dem Gutschein, der vorbelegt wird. */
|
|
178
|
+
export interface VoucherRedeemContext {
|
|
179
|
+
vouchers?: readonly Voucher[];
|
|
180
|
+
options?: ReceiptDueOptions;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Hoechstbetrag zum Vorbelegen einer Einloesung an der Kasse, in Cent, nie
|
|
184
|
+
* negativ: der groesste Betrag, den der Server fuer diesen Gutschein annimmt.
|
|
185
|
+
*
|
|
186
|
+
* Obergrenze: Saldo, offener Betrag; bei Einzweck die deckbaren Positionen im
|
|
187
|
+
* Topf seines Satzes abzueglich der schon gewaehlten Einloesungen dort. Befreit
|
|
188
|
+
* verkauft bzw. als Kleinunternehmer verteilt der Server anteilig auf alle
|
|
189
|
+
* Saetze (`kuTeile`); dann begrenzt jeder Topf: sein Anteil plus die schon
|
|
190
|
+
* gewaehlten Einloesungen dort darf seine Positionen nicht uebersteigen.
|
|
191
|
+
*
|
|
192
|
+
* Unter dieser Grenze wird jeder Kandidat mit genau der Rechnung von
|
|
193
|
+
* [receiptDueCents] geprueft (Rundung halber Cent, groesster Rest), zuerst
|
|
194
|
+
* Cent fuer Cent abwaerts (64 Schritte). Findet das nichts, sucht eine
|
|
195
|
+
* Bisektion ueber [1, Grenze] und prueft danach 16 Cent aufwaerts: am Rand
|
|
196
|
+
* des groessten Rests ist die Annahme nicht monoton. Leistung (wird ueber
|
|
197
|
+
* Positionen eingeloest), Aktion (Rabatt rechnet der Server) und jeder nicht
|
|
198
|
+
* aktive Gutschein: 0.
|
|
199
|
+
*/
|
|
200
|
+
export declare function voucherRedeemMaxCents(items: readonly ReceiptItem[], voucher: VoucherCheck, before?: VoucherRedeemContext): number;
|
package/dist/cjs/receipt/due.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ReceiptDueError = exports.RECEIPT_DUE_ERROR_REASONS = void 0;
|
|
3
|
+
exports.ReceiptDueError = exports.RECEIPT_DUE_ERROR_REASONS = exports.VOUCHER_ZERO_RATE_BUCKET_FROM = void 0;
|
|
4
4
|
exports.isReceiptDueError = isReceiptDueError;
|
|
5
5
|
exports.receiptDueCents = receiptDueCents;
|
|
6
6
|
exports.receiptDueBreakdown = receiptDueBreakdown;
|
|
7
|
+
exports.voucherRedeemMaxCents = voucherRedeemMaxCents;
|
|
7
8
|
const index_js_1 = require("../enums/index.js");
|
|
9
|
+
const validator_1 = require("@kreiseck/validator");
|
|
10
|
+
const vienna_time_js_1 = require("../vienna-time.js");
|
|
11
|
+
const gutschein_plan_js_1 = require("./gutschein-plan.js");
|
|
12
|
+
var gutschein_plan_js_2 = require("./gutschein-plan.js");
|
|
13
|
+
Object.defineProperty(exports, "VOUCHER_ZERO_RATE_BUCKET_FROM", { enumerable: true, get: function () { return gutschein_plan_js_2.VOUCHER_ZERO_RATE_BUCKET_FROM; } });
|
|
8
14
|
/**
|
|
9
15
|
* Warum sich der Zahlbetrag nicht rechnen laesst. Fest wie ein Backend-Code;
|
|
10
16
|
* dieselbe Liste im Dart-Zwilling und in `fixtures/receipt-due-errors.json`.
|
|
@@ -40,9 +46,17 @@ class ReceiptDueError extends Error {
|
|
|
40
46
|
reason;
|
|
41
47
|
/** Immer `'rejected'`: es ging nichts hinaus. */
|
|
42
48
|
outcome = 'rejected';
|
|
43
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Nur bei `invalid_voucher`: der Gutschein-Code, mit dem der Server
|
|
51
|
+
* denselben Beleg abwiese (`voucher_rate_mismatch`, `voucher_exceeds_total`,
|
|
52
|
+
* `voucher_not_found` …), fuer die Meldung an der Kasse.
|
|
53
|
+
*/
|
|
54
|
+
voucherErrorCode;
|
|
55
|
+
constructor(reason, message, voucherErrorCode) {
|
|
44
56
|
super(`Zahlbetrag: ${message}`);
|
|
45
57
|
this.reason = reason;
|
|
58
|
+
if (voucherErrorCode !== undefined)
|
|
59
|
+
this.voucherErrorCode = voucherErrorCode;
|
|
46
60
|
}
|
|
47
61
|
}
|
|
48
62
|
exports.ReceiptDueError = ReceiptDueError;
|
|
@@ -58,35 +72,192 @@ function receiptDueCents(items, vouchers, receiptType, options = {}) {
|
|
|
58
72
|
}
|
|
59
73
|
/** Wie [receiptDueCents], dazu Zaehler-Delta, Wertgutschein-Anteil und Toepfe. */
|
|
60
74
|
function receiptDueBreakdown(items, vouchers, receiptType, options = {}) {
|
|
75
|
+
return rechne(items, vouchers, receiptType, options).ergebnis;
|
|
76
|
+
}
|
|
77
|
+
function rechne(items, vouchers, receiptType, options) {
|
|
61
78
|
const art = belegArt(receiptType);
|
|
62
79
|
const trinkgelder = trinkgeldAuftraege(options);
|
|
63
80
|
if (trinkgelder.length > 0 && !TRINKGELD_ERLAUBT.has(art)) {
|
|
64
81
|
throw new ReceiptDueError('tip_not_allowed', `Trinkgeld gibt es nur bei standard und training, nicht bei "${art}".`);
|
|
65
82
|
}
|
|
66
83
|
if (!UMSATZ.has(art)) {
|
|
67
|
-
return { dueCents: 0, counterDeltaCents: 0, valueVoucherFlowCents: 0, bucketsCents: null };
|
|
84
|
+
return { ergebnis: { dueCents: 0, counterDeltaCents: 0, valueVoucherFlowCents: 0, bucketsCents: null }, kunde: [], minusJeTopf: new Map(), auszahlungCents: 0 };
|
|
68
85
|
}
|
|
69
86
|
const positionen = items.map(innen);
|
|
70
|
-
const gutscheine = vouchers.map(gutscheinInnen);
|
|
71
87
|
// index.js createReceipt: erst `tip`, dann je Zahlart aus payments[].tipCents;
|
|
72
88
|
// jede Runde haengt ihre Positionen hinten an (die Basis bleibt die Ware).
|
|
73
89
|
for (const auftrag of trinkgelder)
|
|
74
90
|
positionen.push(...trinkgeldPositionen(auftrag, positionen, options.tipRecipient));
|
|
91
|
+
// Danach der Gutschein-Plan (gutschein-beleg.plane): seine Positionen stehen hinter Ware und Trinkgeld.
|
|
92
|
+
const plan = (0, gutschein_plan_js_1.planeGutscheine)(positionen, vouchers, art, {
|
|
93
|
+
voucherInfo: options.voucherInfo,
|
|
94
|
+
voucherSettings: options.voucherSettings,
|
|
95
|
+
voucherArticles: options.voucherArticles,
|
|
96
|
+
smallBusiness: options.smallBusiness,
|
|
97
|
+
zeroRateBucketFrom: options.zeroRateBucketFrom,
|
|
98
|
+
today: options.today ?? wienerTag(),
|
|
99
|
+
});
|
|
100
|
+
if ('fehler' in plan)
|
|
101
|
+
throw new ReceiptDueError('invalid_voucher', plan.text, plan.fehler);
|
|
102
|
+
const kunde = positionen.slice();
|
|
103
|
+
positionen.push(...plan.zusatz);
|
|
104
|
+
// gutschein-beleg.zahlbetrag: nur Wert und Rabatt, ohne Mehrzweck-Verkaeufe im 0-%-Topf.
|
|
105
|
+
const gutscheine = plan.gutscheine.filter((g) => !(g.action === index_js_1.VoucherAction.sell && g.imNullTopf === true));
|
|
75
106
|
const t = belegToepfe(positionen, gutscheine);
|
|
76
107
|
const fluss = wertgutscheinFlussCents(gutscheine, art);
|
|
108
|
+
const dueCents = t.zaehlerDeltaCents + fluss;
|
|
109
|
+
// Ohne Auszahlungen darf der Zahlbetrag nicht negativ werden (Einloesung hoeher als die Summe).
|
|
110
|
+
if (plan.mitPlan && dueCents + plan.auszahlungCents < 0) {
|
|
111
|
+
throw new ReceiptDueError('invalid_voucher', 'die Gutscheine uebersteigen die Summe des Belegs.', 'voucher_exceeds_total');
|
|
112
|
+
}
|
|
77
113
|
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
ergebnis: {
|
|
115
|
+
dueCents,
|
|
116
|
+
counterDeltaCents: t.zaehlerDeltaCents,
|
|
117
|
+
valueVoucherFlowCents: fluss,
|
|
118
|
+
bucketsCents: {
|
|
119
|
+
amountRateStandard: euroToCent(t.amountRateStandard),
|
|
120
|
+
amountRateReduced1: euroToCent(t.amountRateReduced1),
|
|
121
|
+
amountRateReduced2: euroToCent(t.amountRateReduced2),
|
|
122
|
+
amountRateZero: euroToCent(t.amountRateZero),
|
|
123
|
+
amountRateSpecial: euroToCent(t.amountRateSpecial),
|
|
124
|
+
amountRatOthers: euroToCent(t.amountRatOthers),
|
|
125
|
+
},
|
|
88
126
|
},
|
|
127
|
+
kunde,
|
|
128
|
+
minusJeTopf: plan.minusJeTopf,
|
|
129
|
+
auszahlungCents: plan.auszahlungCents,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Hoechstbetrag zum Vorbelegen einer Einloesung an der Kasse, in Cent, nie
|
|
134
|
+
* negativ: der groesste Betrag, den der Server fuer diesen Gutschein annimmt.
|
|
135
|
+
*
|
|
136
|
+
* Obergrenze: Saldo, offener Betrag; bei Einzweck die deckbaren Positionen im
|
|
137
|
+
* Topf seines Satzes abzueglich der schon gewaehlten Einloesungen dort. Befreit
|
|
138
|
+
* verkauft bzw. als Kleinunternehmer verteilt der Server anteilig auf alle
|
|
139
|
+
* Saetze (`kuTeile`); dann begrenzt jeder Topf: sein Anteil plus die schon
|
|
140
|
+
* gewaehlten Einloesungen dort darf seine Positionen nicht uebersteigen.
|
|
141
|
+
*
|
|
142
|
+
* Unter dieser Grenze wird jeder Kandidat mit genau der Rechnung von
|
|
143
|
+
* [receiptDueCents] geprueft (Rundung halber Cent, groesster Rest), zuerst
|
|
144
|
+
* Cent fuer Cent abwaerts (64 Schritte). Findet das nichts, sucht eine
|
|
145
|
+
* Bisektion ueber [1, Grenze] und prueft danach 16 Cent aufwaerts: am Rand
|
|
146
|
+
* des groessten Rests ist die Annahme nicht monoton. Leistung (wird ueber
|
|
147
|
+
* Positionen eingeloest), Aktion (Rabatt rechnet der Server) und jeder nicht
|
|
148
|
+
* aktive Gutschein: 0.
|
|
149
|
+
*/
|
|
150
|
+
function voucherRedeemMaxCents(items, voucher, before = {}) {
|
|
151
|
+
if (voucher.status !== 'active')
|
|
152
|
+
return 0;
|
|
153
|
+
if (voucher.type !== 'multi_purpose' && voucher.type !== 'single_purpose')
|
|
154
|
+
return 0;
|
|
155
|
+
const saldo = Number.isInteger(voucher.balanceCents) ? voucher.balanceCents : 0;
|
|
156
|
+
const options = before.options ?? {};
|
|
157
|
+
const vorher = before.vouchers ?? [];
|
|
158
|
+
const r = rechne(items, vorher, 'standard', options);
|
|
159
|
+
// Rundung je Topf: hoechstens ein halber Cent je Topf ueber dem offenen Betrag.
|
|
160
|
+
let oben = Math.min(saldo, r.ergebnis.dueCents + r.auszahlungCents + 6, HOECHSTBETRAG_CENTS);
|
|
161
|
+
if (voucher.type === 'single_purpose') {
|
|
162
|
+
const deckbar = r.kunde.filter(gutschein_plan_js_1.istDeckbar);
|
|
163
|
+
const topfBasis = (topf) => (0, gutschein_plan_js_1.centSumme)(deckbar.filter((it) => (0, gutschein_plan_js_1.topfFuerSatz)(it.vat) === topf));
|
|
164
|
+
if (voucher.exempt === true || options.smallBusiness === true) {
|
|
165
|
+
oben = Math.min(oben, kuGrenze(r.kunde, topfBasis, r.minusJeTopf));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
if (!Number.isInteger(voucher.vatRateBp))
|
|
169
|
+
return 0;
|
|
170
|
+
const topf = (0, gutschein_plan_js_1.topfFuerSatz)(voucher.vatRateBp / 100);
|
|
171
|
+
oben = Math.min(oben, topfBasis(topf) - (r.minusJeTopf.get(topf) ?? 0));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (oben <= 0)
|
|
175
|
+
return 0;
|
|
176
|
+
const code = probeCode(vorher);
|
|
177
|
+
const info = [...(options.voucherInfo ?? []), { code, check: voucher }];
|
|
178
|
+
const passt = (cents) => {
|
|
179
|
+
try {
|
|
180
|
+
rechne(items, [...vorher, { action: 'redeem', type: voucher.type, code, amountCents: cents }], 'standard', { ...options, voucherInfo: info });
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
catch (fehler) {
|
|
184
|
+
if (fehler instanceof ReceiptDueError)
|
|
185
|
+
return false;
|
|
186
|
+
throw fehler;
|
|
187
|
+
}
|
|
89
188
|
};
|
|
189
|
+
return groessterBetrag(oben, passt);
|
|
190
|
+
}
|
|
191
|
+
/** Schritte der linearen Suche, bevor die Bisektion uebernimmt. */
|
|
192
|
+
const LINEAR_SCHRITTE = 64;
|
|
193
|
+
/** Cent, die nach der Bisektion aufwaerts geprueft werden (nicht monotoner Rand). */
|
|
194
|
+
const NACHPRUEFEN = 16;
|
|
195
|
+
/** Groesster Betrag in [1, oben], den `passt` annimmt; 0, wenn keiner gefunden wird. */
|
|
196
|
+
function groessterBetrag(oben, passt) {
|
|
197
|
+
const untenLinear = Math.max(0, oben - LINEAR_SCHRITTE);
|
|
198
|
+
for (let cents = oben; cents > untenLinear; cents--) {
|
|
199
|
+
if (passt(cents))
|
|
200
|
+
return cents;
|
|
201
|
+
}
|
|
202
|
+
// Bisektion ueber [1, untenLinear]: `ja` angenommen (0 gilt als angenommen), `nein` abgewiesen.
|
|
203
|
+
let ja = 0;
|
|
204
|
+
let nein = untenLinear + 1;
|
|
205
|
+
while (nein - ja > 1) {
|
|
206
|
+
const mitte = ja + Math.floor((nein - ja) / 2);
|
|
207
|
+
if (passt(mitte))
|
|
208
|
+
ja = mitte;
|
|
209
|
+
else
|
|
210
|
+
nein = mitte;
|
|
211
|
+
}
|
|
212
|
+
let bester = ja;
|
|
213
|
+
for (let cents = ja + 1; cents <= Math.min(untenLinear, ja + NACHPRUEFEN); cents++) {
|
|
214
|
+
if (passt(cents))
|
|
215
|
+
bester = cents;
|
|
216
|
+
}
|
|
217
|
+
return bester;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Obergrenze fuer eine anteilige Einloesung (`kuTeile`): je Satz ist der Anteil
|
|
221
|
+
* mindestens `floor(c * basis / summe)`, je Topf also mehr als
|
|
222
|
+
* `c * basisTopf / summe - saetzeImTopf`. Mit dem freien Rest des Topfs
|
|
223
|
+
* (Positionen minus schon gewaehlte Einloesungen) folgt
|
|
224
|
+
* `c <= (frei + saetzeImTopf) * summe / basisTopf`.
|
|
225
|
+
*/
|
|
226
|
+
function kuGrenze(kunde, topfBasis, minusJeTopf) {
|
|
227
|
+
const saetze = (0, gutschein_plan_js_1.kuSaetze)(kunde);
|
|
228
|
+
const summe = saetze.reduce((a, x) => a + x.basis, 0);
|
|
229
|
+
let grenze = summe;
|
|
230
|
+
const jeTopf = new Map();
|
|
231
|
+
for (const x of saetze) {
|
|
232
|
+
const topf = (0, gutschein_plan_js_1.topfFuerSatz)(x.satzBp / 100);
|
|
233
|
+
const t = jeTopf.get(topf) ?? { basis: 0, anzahl: 0 };
|
|
234
|
+
t.basis += x.basis;
|
|
235
|
+
t.anzahl += 1;
|
|
236
|
+
jeTopf.set(topf, t);
|
|
237
|
+
}
|
|
238
|
+
for (const [topf, t] of jeTopf) {
|
|
239
|
+
const frei = topfBasis(topf) - (minusJeTopf.get(topf) ?? 0);
|
|
240
|
+
grenze = Math.min(grenze, Math.floor(((frei + t.anzahl) * summe) / t.basis));
|
|
241
|
+
}
|
|
242
|
+
return Math.max(0, grenze);
|
|
243
|
+
}
|
|
244
|
+
const HOECHSTBETRAG_CENTS = 1_000_000;
|
|
245
|
+
/** Ein gueltiger Code, der unter den schon gewaehlten Gutscheinen nicht vorkommt (nur fuer die Probe-Rechnung). */
|
|
246
|
+
function probeCode(vorher) {
|
|
247
|
+
const belegt = new Set(vorher.map((v) => {
|
|
248
|
+
const g = typeof v.code === 'string' ? validator_1.VoucherCode.validate(v.code) : null;
|
|
249
|
+
return g && g.ok ? g.normalized : null;
|
|
250
|
+
}));
|
|
251
|
+
for (let i = 0;; i++) {
|
|
252
|
+
const code = validator_1.VoucherCode.generate(Uint8Array.from({ length: 11 }, (_, k) => (i + k * 7) & 31));
|
|
253
|
+
if (!belegt.has(code))
|
|
254
|
+
return code;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/** Heute in Wien als `YYYY-MM-DD`. */
|
|
258
|
+
function wienerTag() {
|
|
259
|
+
const w = (0, vienna_time_js_1.toViennaWallClock)(new Date());
|
|
260
|
+
return `${w.year}-${String(w.month).padStart(2, '0')}-${String(w.day).padStart(2, '0')}`;
|
|
90
261
|
}
|
|
91
262
|
function belegArt(wert) {
|
|
92
263
|
const text = typeof wert === 'object' && wert !== null ? wert.value : wert;
|
|
@@ -109,12 +280,15 @@ function innen(item, i) {
|
|
|
109
280
|
if (typeof item.quantity !== 'number' || !Number.isFinite(item.quantity))
|
|
110
281
|
throw new ReceiptDueError('invalid_item', `Position ${i + 1} hat keine gueltige Menge.`);
|
|
111
282
|
const tip = item.kind === 'tip' ? (item.recipient?.owner === true ? 'owner' : 'staff') : null;
|
|
112
|
-
return {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
283
|
+
return {
|
|
284
|
+
amount: item.quantity,
|
|
285
|
+
priceOne: Math.round(item.priceCents) / 100,
|
|
286
|
+
vat: satz,
|
|
287
|
+
tip,
|
|
288
|
+
// `kind: null` ist wie keines (der Server prueft `!it.kind`).
|
|
289
|
+
...(item.kind != null ? { kind: item.kind } : {}),
|
|
290
|
+
...(item.articleId != null ? { articleId: item.articleId } : {}),
|
|
291
|
+
};
|
|
118
292
|
}
|
|
119
293
|
/** Trinkgeld-Auftraege in der Reihenfolge des Servers: `tip`, dann je Zahlart die Summe der `tipCents`. */
|
|
120
294
|
function trinkgeldAuftraege(options) {
|
|
@@ -234,18 +408,6 @@ function splitOwnerTip(cents, jeSatz) {
|
|
|
234
408
|
return verteilt.map(({ vat, cents: c }) => ({ vat, cents: c }));
|
|
235
409
|
}
|
|
236
410
|
const FUENF = ['amountRateStandard', 'amountRateReduced1', 'amountRateReduced2', 'amountRateZero', 'amountRateSpecial'];
|
|
237
|
-
/** `vat-buckets.bucketFuerSatz`. */
|
|
238
|
-
function topfFuerSatz(vat) {
|
|
239
|
-
switch (vat) {
|
|
240
|
-
case 20: return 'amountRateStandard';
|
|
241
|
-
case 10: return 'amountRateReduced1';
|
|
242
|
-
case 13: return 'amountRateReduced2';
|
|
243
|
-
case 0: return 'amountRateZero';
|
|
244
|
-
case 19: return 'amountRateSpecial';
|
|
245
|
-
case 4.9: return 'amountRateSpecial';
|
|
246
|
-
default: return 'amountRatOthers';
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
411
|
/** `beleg-toepfe.belegToepfe`, Schritt fuer Schritt. */
|
|
250
412
|
function belegToepfe(positionen, gutscheine) {
|
|
251
413
|
const t = { amountRateStandard: 0, amountRateReduced1: 0, amountRateReduced2: 0, amountRateZero: 0, amountRateSpecial: 0, amountRatOthers: 0 };
|
|
@@ -254,7 +416,7 @@ function belegToepfe(positionen, gutscheine) {
|
|
|
254
416
|
if (p.tip === 'staff')
|
|
255
417
|
personal += p.amount * p.priceOne;
|
|
256
418
|
else
|
|
257
|
-
t[topfFuerSatz(p.vat)] += p.priceOne * p.amount;
|
|
419
|
+
t[(0, gutschein_plan_js_1.topfFuerSatz)(p.vat)] += p.priceOne * p.amount;
|
|
258
420
|
}
|
|
259
421
|
const gesamt = t.amountRateStandard + t.amountRateReduced1 + t.amountRateReduced2 + t.amountRateZero + t.amountRateSpecial;
|
|
260
422
|
if (gutscheine.length > 0) {
|
|
@@ -290,17 +452,21 @@ function belegToepfe(positionen, gutscheine) {
|
|
|
290
452
|
euroToCent(t.amountRateZero) + euroToCent(t.amountRateSpecial) + euroToCent(t.amountRatOthers);
|
|
291
453
|
return { ...t, zaehlerDeltaCents };
|
|
292
454
|
}
|
|
293
|
-
/**
|
|
455
|
+
/**
|
|
456
|
+
* `zahlungen-core.wertgutscheinFlussCents`: Eintraege im 0-%-Topf
|
|
457
|
+
* (`imNullTopf`) stehen schon als Position; eine Auszahlung ohne
|
|
458
|
+
* Topf-Position mindert den Zahlbetrag.
|
|
459
|
+
*/
|
|
294
460
|
function wertgutscheinFlussCents(gutscheine, art) {
|
|
295
461
|
const vz = art === 'cancellation' ? -1 : 1;
|
|
296
462
|
let cents = 0;
|
|
297
463
|
for (const v of gutscheine) {
|
|
298
|
-
if (v.type !== index_js_1.VoucherType.value || !Number.isFinite(v.value))
|
|
464
|
+
if (v.type !== index_js_1.VoucherType.value || v.imNullTopf === true || !Number.isFinite(v.value))
|
|
299
465
|
continue;
|
|
300
466
|
const c = euroToCent(v.value);
|
|
301
467
|
if (v.action === index_js_1.VoucherAction.sell)
|
|
302
468
|
cents += c;
|
|
303
|
-
else if (v.action === index_js_1.VoucherAction.redeem)
|
|
469
|
+
else if (v.action === index_js_1.VoucherAction.redeem || v.action === index_js_1.VoucherAction.payout)
|
|
304
470
|
cents -= c;
|
|
305
471
|
}
|
|
306
472
|
return vz * cents + 0;
|
package/dist/cjs/receipt/grid.js
CHANGED
|
@@ -63,10 +63,22 @@ function renderReceiptGrid(layout, options = {}) {
|
|
|
63
63
|
const lines = [];
|
|
64
64
|
for (const z of layout.lines) {
|
|
65
65
|
switch (z.kind) {
|
|
66
|
-
case 'text':
|
|
66
|
+
case 'text': {
|
|
67
|
+
// Haengender Einzug: ein linksbuendiger Text mit fuehrenden Leerzeichen
|
|
68
|
+
// (Vermerke unter einer Gutscheinzeile, „ Grund: …“) bricht mit
|
|
69
|
+
// demselben Einzug um, auf 32 wie auf 48 Zeichen. Passt er in eine
|
|
70
|
+
// Zeile, ergibt das dieselben Zeichen wie ohne Einzugsregel.
|
|
71
|
+
const einzug = z.align === 'left' ? z.text.length - z.text.trimStart().length : 0;
|
|
72
|
+
if (einzug > 0 && einzug < zeichen && z.text.trimStart() !== '') {
|
|
73
|
+
const vorn = ' '.repeat(einzug);
|
|
74
|
+
for (const t of (0, escpos_js_1.wrapText)(z.text.slice(einzug), zeichen - einzug))
|
|
75
|
+
lines.push({ text: ausrichten(vorn + t, zeichen, 'left'), kind: 'text', bold: z.bold });
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
67
78
|
for (const t of (0, escpos_js_1.wrapText)(z.text, zeichen))
|
|
68
79
|
lines.push({ text: ausrichten(t, zeichen, z.align), kind: 'text', bold: z.bold });
|
|
69
80
|
break;
|
|
81
|
+
}
|
|
70
82
|
case 'banner': {
|
|
71
83
|
// Der Rahmen ist Teil des Rasters: eine Zeile '=' ueber die volle Breite
|
|
72
84
|
// davor und danach. So setzt ihn jeder Weg -- Bildschirm, Bon, ePOS, PDF,
|