@kreiseck/kasseneck-api 1.11.1 → 1.12.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 +15 -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 +156 -0
- package/dist/cjs/pos/texte.js +72 -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 +156 -0
- package/dist/esm/pos/texte.js +70 -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 +170 -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/esm/receipt/due.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { ReceiptType, VoucherAction, VoucherType } from '../enums/index.js';
|
|
2
|
+
import { VoucherCode } from '@kreiseck/validator';
|
|
3
|
+
import { toViennaWallClock } from '../vienna-time.js';
|
|
4
|
+
import { centSumme, istDeckbar, kuSaetze, planeGutscheine, topfFuerSatz, } from './gutschein-plan.js';
|
|
5
|
+
export { VOUCHER_ZERO_RATE_BUCKET_FROM } from './gutschein-plan.js';
|
|
2
6
|
/**
|
|
3
7
|
* Warum sich der Zahlbetrag nicht rechnen laesst. Fest wie ein Backend-Code;
|
|
4
8
|
* dieselbe Liste im Dart-Zwilling und in `fixtures/receipt-due-errors.json`.
|
|
@@ -34,9 +38,17 @@ export class ReceiptDueError extends Error {
|
|
|
34
38
|
reason;
|
|
35
39
|
/** Immer `'rejected'`: es ging nichts hinaus. */
|
|
36
40
|
outcome = 'rejected';
|
|
37
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Nur bei `invalid_voucher`: der Gutschein-Code, mit dem der Server
|
|
43
|
+
* denselben Beleg abwiese (`voucher_rate_mismatch`, `voucher_exceeds_total`,
|
|
44
|
+
* `voucher_not_found` …), fuer die Meldung an der Kasse.
|
|
45
|
+
*/
|
|
46
|
+
voucherErrorCode;
|
|
47
|
+
constructor(reason, message, voucherErrorCode) {
|
|
38
48
|
super(`Zahlbetrag: ${message}`);
|
|
39
49
|
this.reason = reason;
|
|
50
|
+
if (voucherErrorCode !== undefined)
|
|
51
|
+
this.voucherErrorCode = voucherErrorCode;
|
|
40
52
|
}
|
|
41
53
|
}
|
|
42
54
|
export function isReceiptDueError(error) {
|
|
@@ -51,35 +63,192 @@ export function receiptDueCents(items, vouchers, receiptType, options = {}) {
|
|
|
51
63
|
}
|
|
52
64
|
/** Wie [receiptDueCents], dazu Zaehler-Delta, Wertgutschein-Anteil und Toepfe. */
|
|
53
65
|
export function receiptDueBreakdown(items, vouchers, receiptType, options = {}) {
|
|
66
|
+
return rechne(items, vouchers, receiptType, options).ergebnis;
|
|
67
|
+
}
|
|
68
|
+
function rechne(items, vouchers, receiptType, options) {
|
|
54
69
|
const art = belegArt(receiptType);
|
|
55
70
|
const trinkgelder = trinkgeldAuftraege(options);
|
|
56
71
|
if (trinkgelder.length > 0 && !TRINKGELD_ERLAUBT.has(art)) {
|
|
57
72
|
throw new ReceiptDueError('tip_not_allowed', `Trinkgeld gibt es nur bei standard und training, nicht bei "${art}".`);
|
|
58
73
|
}
|
|
59
74
|
if (!UMSATZ.has(art)) {
|
|
60
|
-
return { dueCents: 0, counterDeltaCents: 0, valueVoucherFlowCents: 0, bucketsCents: null };
|
|
75
|
+
return { ergebnis: { dueCents: 0, counterDeltaCents: 0, valueVoucherFlowCents: 0, bucketsCents: null }, kunde: [], minusJeTopf: new Map(), auszahlungCents: 0 };
|
|
61
76
|
}
|
|
62
77
|
const positionen = items.map(innen);
|
|
63
|
-
const gutscheine = vouchers.map(gutscheinInnen);
|
|
64
78
|
// index.js createReceipt: erst `tip`, dann je Zahlart aus payments[].tipCents;
|
|
65
79
|
// jede Runde haengt ihre Positionen hinten an (die Basis bleibt die Ware).
|
|
66
80
|
for (const auftrag of trinkgelder)
|
|
67
81
|
positionen.push(...trinkgeldPositionen(auftrag, positionen, options.tipRecipient));
|
|
82
|
+
// Danach der Gutschein-Plan (gutschein-beleg.plane): seine Positionen stehen hinter Ware und Trinkgeld.
|
|
83
|
+
const plan = planeGutscheine(positionen, vouchers, art, {
|
|
84
|
+
voucherInfo: options.voucherInfo,
|
|
85
|
+
voucherSettings: options.voucherSettings,
|
|
86
|
+
voucherArticles: options.voucherArticles,
|
|
87
|
+
smallBusiness: options.smallBusiness,
|
|
88
|
+
zeroRateBucketFrom: options.zeroRateBucketFrom,
|
|
89
|
+
today: options.today ?? wienerTag(),
|
|
90
|
+
});
|
|
91
|
+
if ('fehler' in plan)
|
|
92
|
+
throw new ReceiptDueError('invalid_voucher', plan.text, plan.fehler);
|
|
93
|
+
const kunde = positionen.slice();
|
|
94
|
+
positionen.push(...plan.zusatz);
|
|
95
|
+
// gutschein-beleg.zahlbetrag: nur Wert und Rabatt, ohne Mehrzweck-Verkaeufe im 0-%-Topf.
|
|
96
|
+
const gutscheine = plan.gutscheine.filter((g) => !(g.action === VoucherAction.sell && g.imNullTopf === true));
|
|
68
97
|
const t = belegToepfe(positionen, gutscheine);
|
|
69
98
|
const fluss = wertgutscheinFlussCents(gutscheine, art);
|
|
99
|
+
const dueCents = t.zaehlerDeltaCents + fluss;
|
|
100
|
+
// Ohne Auszahlungen darf der Zahlbetrag nicht negativ werden (Einloesung hoeher als die Summe).
|
|
101
|
+
if (plan.mitPlan && dueCents + plan.auszahlungCents < 0) {
|
|
102
|
+
throw new ReceiptDueError('invalid_voucher', 'die Gutscheine uebersteigen die Summe des Belegs.', 'voucher_exceeds_total');
|
|
103
|
+
}
|
|
70
104
|
return {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
105
|
+
ergebnis: {
|
|
106
|
+
dueCents,
|
|
107
|
+
counterDeltaCents: t.zaehlerDeltaCents,
|
|
108
|
+
valueVoucherFlowCents: fluss,
|
|
109
|
+
bucketsCents: {
|
|
110
|
+
amountRateStandard: euroToCent(t.amountRateStandard),
|
|
111
|
+
amountRateReduced1: euroToCent(t.amountRateReduced1),
|
|
112
|
+
amountRateReduced2: euroToCent(t.amountRateReduced2),
|
|
113
|
+
amountRateZero: euroToCent(t.amountRateZero),
|
|
114
|
+
amountRateSpecial: euroToCent(t.amountRateSpecial),
|
|
115
|
+
amountRatOthers: euroToCent(t.amountRatOthers),
|
|
116
|
+
},
|
|
81
117
|
},
|
|
118
|
+
kunde,
|
|
119
|
+
minusJeTopf: plan.minusJeTopf,
|
|
120
|
+
auszahlungCents: plan.auszahlungCents,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Hoechstbetrag zum Vorbelegen einer Einloesung an der Kasse, in Cent, nie
|
|
125
|
+
* negativ: der groesste Betrag, den der Server fuer diesen Gutschein annimmt.
|
|
126
|
+
*
|
|
127
|
+
* Obergrenze: Saldo, offener Betrag; bei Einzweck die deckbaren Positionen im
|
|
128
|
+
* Topf seines Satzes abzueglich der schon gewaehlten Einloesungen dort. Befreit
|
|
129
|
+
* verkauft bzw. als Kleinunternehmer verteilt der Server anteilig auf alle
|
|
130
|
+
* Saetze (`kuTeile`); dann begrenzt jeder Topf: sein Anteil plus die schon
|
|
131
|
+
* gewaehlten Einloesungen dort darf seine Positionen nicht uebersteigen.
|
|
132
|
+
*
|
|
133
|
+
* Unter dieser Grenze wird jeder Kandidat mit genau der Rechnung von
|
|
134
|
+
* [receiptDueCents] geprueft (Rundung halber Cent, groesster Rest), zuerst
|
|
135
|
+
* Cent fuer Cent abwaerts (64 Schritte). Findet das nichts, sucht eine
|
|
136
|
+
* Bisektion ueber [1, Grenze] und prueft danach 16 Cent aufwaerts: am Rand
|
|
137
|
+
* des groessten Rests ist die Annahme nicht monoton. Leistung (wird ueber
|
|
138
|
+
* Positionen eingeloest), Aktion (Rabatt rechnet der Server) und jeder nicht
|
|
139
|
+
* aktive Gutschein: 0.
|
|
140
|
+
*/
|
|
141
|
+
export function voucherRedeemMaxCents(items, voucher, before = {}) {
|
|
142
|
+
if (voucher.status !== 'active')
|
|
143
|
+
return 0;
|
|
144
|
+
if (voucher.type !== 'multi_purpose' && voucher.type !== 'single_purpose')
|
|
145
|
+
return 0;
|
|
146
|
+
const saldo = Number.isInteger(voucher.balanceCents) ? voucher.balanceCents : 0;
|
|
147
|
+
const options = before.options ?? {};
|
|
148
|
+
const vorher = before.vouchers ?? [];
|
|
149
|
+
const r = rechne(items, vorher, 'standard', options);
|
|
150
|
+
// Rundung je Topf: hoechstens ein halber Cent je Topf ueber dem offenen Betrag.
|
|
151
|
+
let oben = Math.min(saldo, r.ergebnis.dueCents + r.auszahlungCents + 6, HOECHSTBETRAG_CENTS);
|
|
152
|
+
if (voucher.type === 'single_purpose') {
|
|
153
|
+
const deckbar = r.kunde.filter(istDeckbar);
|
|
154
|
+
const topfBasis = (topf) => centSumme(deckbar.filter((it) => topfFuerSatz(it.vat) === topf));
|
|
155
|
+
if (voucher.exempt === true || options.smallBusiness === true) {
|
|
156
|
+
oben = Math.min(oben, kuGrenze(r.kunde, topfBasis, r.minusJeTopf));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
if (!Number.isInteger(voucher.vatRateBp))
|
|
160
|
+
return 0;
|
|
161
|
+
const topf = topfFuerSatz(voucher.vatRateBp / 100);
|
|
162
|
+
oben = Math.min(oben, topfBasis(topf) - (r.minusJeTopf.get(topf) ?? 0));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (oben <= 0)
|
|
166
|
+
return 0;
|
|
167
|
+
const code = probeCode(vorher);
|
|
168
|
+
const info = [...(options.voucherInfo ?? []), { code, check: voucher }];
|
|
169
|
+
const passt = (cents) => {
|
|
170
|
+
try {
|
|
171
|
+
rechne(items, [...vorher, { action: 'redeem', type: voucher.type, code, amountCents: cents }], 'standard', { ...options, voucherInfo: info });
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
catch (fehler) {
|
|
175
|
+
if (fehler instanceof ReceiptDueError)
|
|
176
|
+
return false;
|
|
177
|
+
throw fehler;
|
|
178
|
+
}
|
|
82
179
|
};
|
|
180
|
+
return groessterBetrag(oben, passt);
|
|
181
|
+
}
|
|
182
|
+
/** Schritte der linearen Suche, bevor die Bisektion uebernimmt. */
|
|
183
|
+
const LINEAR_SCHRITTE = 64;
|
|
184
|
+
/** Cent, die nach der Bisektion aufwaerts geprueft werden (nicht monotoner Rand). */
|
|
185
|
+
const NACHPRUEFEN = 16;
|
|
186
|
+
/** Groesster Betrag in [1, oben], den `passt` annimmt; 0, wenn keiner gefunden wird. */
|
|
187
|
+
function groessterBetrag(oben, passt) {
|
|
188
|
+
const untenLinear = Math.max(0, oben - LINEAR_SCHRITTE);
|
|
189
|
+
for (let cents = oben; cents > untenLinear; cents--) {
|
|
190
|
+
if (passt(cents))
|
|
191
|
+
return cents;
|
|
192
|
+
}
|
|
193
|
+
// Bisektion ueber [1, untenLinear]: `ja` angenommen (0 gilt als angenommen), `nein` abgewiesen.
|
|
194
|
+
let ja = 0;
|
|
195
|
+
let nein = untenLinear + 1;
|
|
196
|
+
while (nein - ja > 1) {
|
|
197
|
+
const mitte = ja + Math.floor((nein - ja) / 2);
|
|
198
|
+
if (passt(mitte))
|
|
199
|
+
ja = mitte;
|
|
200
|
+
else
|
|
201
|
+
nein = mitte;
|
|
202
|
+
}
|
|
203
|
+
let bester = ja;
|
|
204
|
+
for (let cents = ja + 1; cents <= Math.min(untenLinear, ja + NACHPRUEFEN); cents++) {
|
|
205
|
+
if (passt(cents))
|
|
206
|
+
bester = cents;
|
|
207
|
+
}
|
|
208
|
+
return bester;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Obergrenze fuer eine anteilige Einloesung (`kuTeile`): je Satz ist der Anteil
|
|
212
|
+
* mindestens `floor(c * basis / summe)`, je Topf also mehr als
|
|
213
|
+
* `c * basisTopf / summe - saetzeImTopf`. Mit dem freien Rest des Topfs
|
|
214
|
+
* (Positionen minus schon gewaehlte Einloesungen) folgt
|
|
215
|
+
* `c <= (frei + saetzeImTopf) * summe / basisTopf`.
|
|
216
|
+
*/
|
|
217
|
+
function kuGrenze(kunde, topfBasis, minusJeTopf) {
|
|
218
|
+
const saetze = kuSaetze(kunde);
|
|
219
|
+
const summe = saetze.reduce((a, x) => a + x.basis, 0);
|
|
220
|
+
let grenze = summe;
|
|
221
|
+
const jeTopf = new Map();
|
|
222
|
+
for (const x of saetze) {
|
|
223
|
+
const topf = topfFuerSatz(x.satzBp / 100);
|
|
224
|
+
const t = jeTopf.get(topf) ?? { basis: 0, anzahl: 0 };
|
|
225
|
+
t.basis += x.basis;
|
|
226
|
+
t.anzahl += 1;
|
|
227
|
+
jeTopf.set(topf, t);
|
|
228
|
+
}
|
|
229
|
+
for (const [topf, t] of jeTopf) {
|
|
230
|
+
const frei = topfBasis(topf) - (minusJeTopf.get(topf) ?? 0);
|
|
231
|
+
grenze = Math.min(grenze, Math.floor(((frei + t.anzahl) * summe) / t.basis));
|
|
232
|
+
}
|
|
233
|
+
return Math.max(0, grenze);
|
|
234
|
+
}
|
|
235
|
+
const HOECHSTBETRAG_CENTS = 1_000_000;
|
|
236
|
+
/** Ein gueltiger Code, der unter den schon gewaehlten Gutscheinen nicht vorkommt (nur fuer die Probe-Rechnung). */
|
|
237
|
+
function probeCode(vorher) {
|
|
238
|
+
const belegt = new Set(vorher.map((v) => {
|
|
239
|
+
const g = typeof v.code === 'string' ? VoucherCode.validate(v.code) : null;
|
|
240
|
+
return g && g.ok ? g.normalized : null;
|
|
241
|
+
}));
|
|
242
|
+
for (let i = 0;; i++) {
|
|
243
|
+
const code = VoucherCode.generate(Uint8Array.from({ length: 11 }, (_, k) => (i + k * 7) & 31));
|
|
244
|
+
if (!belegt.has(code))
|
|
245
|
+
return code;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** Heute in Wien als `YYYY-MM-DD`. */
|
|
249
|
+
function wienerTag() {
|
|
250
|
+
const w = toViennaWallClock(new Date());
|
|
251
|
+
return `${w.year}-${String(w.month).padStart(2, '0')}-${String(w.day).padStart(2, '0')}`;
|
|
83
252
|
}
|
|
84
253
|
function belegArt(wert) {
|
|
85
254
|
const text = typeof wert === 'object' && wert !== null ? wert.value : wert;
|
|
@@ -102,12 +271,15 @@ function innen(item, i) {
|
|
|
102
271
|
if (typeof item.quantity !== 'number' || !Number.isFinite(item.quantity))
|
|
103
272
|
throw new ReceiptDueError('invalid_item', `Position ${i + 1} hat keine gueltige Menge.`);
|
|
104
273
|
const tip = item.kind === 'tip' ? (item.recipient?.owner === true ? 'owner' : 'staff') : null;
|
|
105
|
-
return {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
274
|
+
return {
|
|
275
|
+
amount: item.quantity,
|
|
276
|
+
priceOne: Math.round(item.priceCents) / 100,
|
|
277
|
+
vat: satz,
|
|
278
|
+
tip,
|
|
279
|
+
// `kind: null` ist wie keines (der Server prueft `!it.kind`).
|
|
280
|
+
...(item.kind != null ? { kind: item.kind } : {}),
|
|
281
|
+
...(item.articleId != null ? { articleId: item.articleId } : {}),
|
|
282
|
+
};
|
|
111
283
|
}
|
|
112
284
|
/** Trinkgeld-Auftraege in der Reihenfolge des Servers: `tip`, dann je Zahlart die Summe der `tipCents`. */
|
|
113
285
|
function trinkgeldAuftraege(options) {
|
|
@@ -227,18 +399,6 @@ function splitOwnerTip(cents, jeSatz) {
|
|
|
227
399
|
return verteilt.map(({ vat, cents: c }) => ({ vat, cents: c }));
|
|
228
400
|
}
|
|
229
401
|
const FUENF = ['amountRateStandard', 'amountRateReduced1', 'amountRateReduced2', 'amountRateZero', 'amountRateSpecial'];
|
|
230
|
-
/** `vat-buckets.bucketFuerSatz`. */
|
|
231
|
-
function topfFuerSatz(vat) {
|
|
232
|
-
switch (vat) {
|
|
233
|
-
case 20: return 'amountRateStandard';
|
|
234
|
-
case 10: return 'amountRateReduced1';
|
|
235
|
-
case 13: return 'amountRateReduced2';
|
|
236
|
-
case 0: return 'amountRateZero';
|
|
237
|
-
case 19: return 'amountRateSpecial';
|
|
238
|
-
case 4.9: return 'amountRateSpecial';
|
|
239
|
-
default: return 'amountRatOthers';
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
402
|
/** `beleg-toepfe.belegToepfe`, Schritt fuer Schritt. */
|
|
243
403
|
function belegToepfe(positionen, gutscheine) {
|
|
244
404
|
const t = { amountRateStandard: 0, amountRateReduced1: 0, amountRateReduced2: 0, amountRateZero: 0, amountRateSpecial: 0, amountRatOthers: 0 };
|
|
@@ -283,17 +443,21 @@ function belegToepfe(positionen, gutscheine) {
|
|
|
283
443
|
euroToCent(t.amountRateZero) + euroToCent(t.amountRateSpecial) + euroToCent(t.amountRatOthers);
|
|
284
444
|
return { ...t, zaehlerDeltaCents };
|
|
285
445
|
}
|
|
286
|
-
/**
|
|
446
|
+
/**
|
|
447
|
+
* `zahlungen-core.wertgutscheinFlussCents`: Eintraege im 0-%-Topf
|
|
448
|
+
* (`imNullTopf`) stehen schon als Position; eine Auszahlung ohne
|
|
449
|
+
* Topf-Position mindert den Zahlbetrag.
|
|
450
|
+
*/
|
|
287
451
|
function wertgutscheinFlussCents(gutscheine, art) {
|
|
288
452
|
const vz = art === 'cancellation' ? -1 : 1;
|
|
289
453
|
let cents = 0;
|
|
290
454
|
for (const v of gutscheine) {
|
|
291
|
-
if (v.type !== VoucherType.value || !Number.isFinite(v.value))
|
|
455
|
+
if (v.type !== VoucherType.value || v.imNullTopf === true || !Number.isFinite(v.value))
|
|
292
456
|
continue;
|
|
293
457
|
const c = euroToCent(v.value);
|
|
294
458
|
if (v.action === VoucherAction.sell)
|
|
295
459
|
cents += c;
|
|
296
|
-
else if (v.action === VoucherAction.redeem)
|
|
460
|
+
else if (v.action === VoucherAction.redeem || v.action === VoucherAction.payout)
|
|
297
461
|
cents -= c;
|
|
298
462
|
}
|
|
299
463
|
return vz * cents + 0;
|
package/dist/esm/receipt/grid.js
CHANGED
|
@@ -57,10 +57,22 @@ export function renderReceiptGrid(layout, options = {}) {
|
|
|
57
57
|
const lines = [];
|
|
58
58
|
for (const z of layout.lines) {
|
|
59
59
|
switch (z.kind) {
|
|
60
|
-
case 'text':
|
|
60
|
+
case 'text': {
|
|
61
|
+
// Haengender Einzug: ein linksbuendiger Text mit fuehrenden Leerzeichen
|
|
62
|
+
// (Vermerke unter einer Gutscheinzeile, „ Grund: …“) bricht mit
|
|
63
|
+
// demselben Einzug um, auf 32 wie auf 48 Zeichen. Passt er in eine
|
|
64
|
+
// Zeile, ergibt das dieselben Zeichen wie ohne Einzugsregel.
|
|
65
|
+
const einzug = z.align === 'left' ? z.text.length - z.text.trimStart().length : 0;
|
|
66
|
+
if (einzug > 0 && einzug < zeichen && z.text.trimStart() !== '') {
|
|
67
|
+
const vorn = ' '.repeat(einzug);
|
|
68
|
+
for (const t of wrapText(z.text.slice(einzug), zeichen - einzug))
|
|
69
|
+
lines.push({ text: ausrichten(vorn + t, zeichen, 'left'), kind: 'text', bold: z.bold });
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
61
72
|
for (const t of wrapText(z.text, zeichen))
|
|
62
73
|
lines.push({ text: ausrichten(t, zeichen, z.align), kind: 'text', bold: z.bold });
|
|
63
74
|
break;
|
|
75
|
+
}
|
|
64
76
|
case 'banner': {
|
|
65
77
|
// Der Rahmen ist Teil des Rasters: eine Zeile '=' ueber die volle Breite
|
|
66
78
|
// davor und danach. So setzt ihn jeder Weg -- Bildschirm, Bon, ePOS, PDF,
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { Voucher } from '../models/voucher.js';
|
|
2
|
+
import type { PosVoucherSettings, VoucherArticle, VoucherCheck } from '../pos/gutschein.js';
|
|
3
|
+
/**
|
|
4
|
+
* Gutscheine der neuen Form im Zahlbetrag: Zwilling von
|
|
5
|
+
* `gutschein-beleg.plane` im Backend, soweit er den Zahlbetrag bestimmt.
|
|
6
|
+
*
|
|
7
|
+
* Der Server macht aus den Wuenschen zusaetzliche Positionen (`kind`
|
|
8
|
+
* `voucher` bzw. `voucher_float`, hinten an Ware und Trinkgeld angehaengt, in
|
|
9
|
+
* der Reihenfolge der Wuensche) und Eintraege in Belegform; nur Eintraege der
|
|
10
|
+
* Art Wert (`value`) und Rabatt (`promo`) gehen danach in die Toepfe und in
|
|
11
|
+
* den Wertgutschein-Fluss (`gutschein-beleg.zahlbetrag`). Genau das steht
|
|
12
|
+
* hier, mit derselben Euro-Arithmetik:
|
|
13
|
+
*
|
|
14
|
+
* - Mehrzweck einloesen: Eintrag `value`, mindert den Zahlbetrag als Fluss.
|
|
15
|
+
* - Mehrzweck verkaufen: Eintrag `value` (+Fluss); ab dem Stichtag des
|
|
16
|
+
* 0-%-Topfs stattdessen Position im 0-%-Satz ohne Fluss.
|
|
17
|
+
* - Mehrzweck auszahlen: immer Position im 0-%-Satz (minus), kein Fluss.
|
|
18
|
+
* - Einzweck: Position im Satz (Verkauf aus der Einstellung der Kasse,
|
|
19
|
+
* Einloesung und Auszahlung aus der Pruefung des Gutscheins); als
|
|
20
|
+
* Kleinunternehmer eingeloest anteilig auf alle Saetze des Korbs.
|
|
21
|
+
* - Leistung verkaufen: Stueck mal Artikelpreis im Artikelsatz; einloesen: je
|
|
22
|
+
* Satz eine Minuszeile ueber die gewaehlten Positionen.
|
|
23
|
+
* - Aktion: Rabatt wie `promo`, Betrag aus der Pruefung, hoechstens die Summe.
|
|
24
|
+
*
|
|
25
|
+
* Kein Fehler wird still geschluckt: was der Server mit einem Gutschein-Code
|
|
26
|
+
* abwiese, liefert `fehler` mit genau diesem Code (`voucher_*`). Der
|
|
27
|
+
* Klartext-Code steht nie in einem Text.
|
|
28
|
+
*/
|
|
29
|
+
/** Position in der inneren Form des Backends (`beleg-toepfe.interneForm`). */
|
|
30
|
+
export interface Innen {
|
|
31
|
+
amount: number;
|
|
32
|
+
priceOne: number;
|
|
33
|
+
vat: number;
|
|
34
|
+
tip: 'staff' | 'owner' | null;
|
|
35
|
+
/** `kind` der Position (Trinkgeld, Rabatt, Gutscheinzeile); Ware hat keines. */
|
|
36
|
+
kind?: string;
|
|
37
|
+
articleId?: string;
|
|
38
|
+
}
|
|
39
|
+
/** Gutschein-Eintrag in Belegform, soweit Toepfe und Fluss ihn lesen. */
|
|
40
|
+
export interface Gutschein {
|
|
41
|
+
action: string;
|
|
42
|
+
type: string;
|
|
43
|
+
value: number;
|
|
44
|
+
/** Betrag steht schon als Position im 0-%-Topf: kein Fluss mehr. */
|
|
45
|
+
imNullTopf?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/** Je Einloesung bzw. Auszahlung die gepruefte Sicht des Gutscheins (aus `checkVoucher`), zugeordnet ueber den Code. */
|
|
48
|
+
export interface VoucherDueInfo {
|
|
49
|
+
/** Der Code, wie er gesendet wird (tolerant: Gross/klein, Bindestriche, `O`/`0`, `I`/`L`/`1`). */
|
|
50
|
+
code: string;
|
|
51
|
+
check: VoucherCheck;
|
|
52
|
+
}
|
|
53
|
+
/** Artikel fuer den Verkauf eines Dienstleistungsgutscheins: Preis und Satz wie im Artikelstamm. */
|
|
54
|
+
export type VoucherDueArticle = Pick<VoucherArticle, 'id' | 'priceCents' | 'vatRateBp'>;
|
|
55
|
+
/**
|
|
56
|
+
* Stichtag des 0-%-Topfs fuer Mehrzweck-Verkaeufe (Backend
|
|
57
|
+
* `gutschein-core.NULLTOPF_AB`). Solange er in der Zukunft liegt, zaehlt ein
|
|
58
|
+
* Mehrzweck-Verkauf als Fluss; ab dem Tag als Position im 0-%-Satz. Der
|
|
59
|
+
* Zahlbetrag bleibt gleich, Toepfe und Fluss verschieben sich.
|
|
60
|
+
*/
|
|
61
|
+
export declare const VOUCHER_ZERO_RATE_BUCKET_FROM = "2099-12-31";
|
|
62
|
+
export interface PlanOptionen {
|
|
63
|
+
voucherInfo?: readonly VoucherDueInfo[];
|
|
64
|
+
voucherSettings?: PosVoucherSettings;
|
|
65
|
+
voucherArticles?: readonly VoucherDueArticle[];
|
|
66
|
+
smallBusiness?: boolean;
|
|
67
|
+
zeroRateBucketFrom?: string;
|
|
68
|
+
/** Wiener Tag `YYYY-MM-DD`; der Aufrufer setzt den heutigen, wenn er fehlt. */
|
|
69
|
+
today: string;
|
|
70
|
+
}
|
|
71
|
+
export interface Plan {
|
|
72
|
+
/** Positionen, die der Server hinten anhaengt. */
|
|
73
|
+
zusatz: Innen[];
|
|
74
|
+
/** Eintraege, die Toepfe und Fluss lesen (`gutschein-beleg.zahlbetrag`). */
|
|
75
|
+
gutscheine: Gutschein[];
|
|
76
|
+
/** Summe der Auszahlungen in Cent. */
|
|
77
|
+
auszahlungCents: number;
|
|
78
|
+
/** Mindestens ein Wunsch der neuen Form: der Server rechnet mit `plane`. */
|
|
79
|
+
mitPlan: boolean;
|
|
80
|
+
/** Summe der Einloese-Minuszeilen je Topf (Deckung, Vorbelegung). */
|
|
81
|
+
minusJeTopf: Map<string, number>;
|
|
82
|
+
}
|
|
83
|
+
export interface PlanFehler {
|
|
84
|
+
fehler: string;
|
|
85
|
+
text: string;
|
|
86
|
+
}
|
|
87
|
+
type TopfName = 'amountRateStandard' | 'amountRateReduced1' | 'amountRateReduced2' | 'amountRateZero' | 'amountRateSpecial' | 'amountRatOthers';
|
|
88
|
+
/** `vat-buckets.bucketFuerSatz`. */
|
|
89
|
+
export declare function topfFuerSatz(vat: number): TopfName;
|
|
90
|
+
/** Position, die ein Gutschein decken darf: weder Trinkgeld noch Gutscheinzeile. */
|
|
91
|
+
export declare function istDeckbar(p: Innen): boolean;
|
|
92
|
+
/** Wie beleg-toepfe: Euro in Positionsreihenfolge aufsummieren, erst die Summe auf Cent runden. */
|
|
93
|
+
export declare function centSumme(liste: readonly Innen[]): number;
|
|
94
|
+
/** Die Saetze, auf die `kuTeile` verteilt: je Satz `centSumme` der deckbaren Positionen, nur positive. */
|
|
95
|
+
export declare function kuSaetze(kunde: readonly Innen[]): Array<{
|
|
96
|
+
satzBp: number;
|
|
97
|
+
basis: number;
|
|
98
|
+
}>;
|
|
99
|
+
/**
|
|
100
|
+
* Wuensche und gelesene Eintraege in Positionen und Belegeintraege, wie
|
|
101
|
+
* `plane` sie baut. `kunde` sind Ware und Trinkgeld in innerer Form.
|
|
102
|
+
*/
|
|
103
|
+
export declare function planeGutscheine(kunde: readonly Innen[], vouchers: readonly Voucher[], art: string, o: PlanOptionen): Plan | PlanFehler;
|
|
104
|
+
export {};
|