@kreiseck/kasseneck-api 0.6.7
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/LICENSE +201 -0
- package/NOTICE +2 -0
- package/README.md +206 -0
- package/dist/cjs/client/api.d.ts +75 -0
- package/dist/cjs/client/api.js +40 -0
- package/dist/cjs/client/auth.d.ts +52 -0
- package/dist/cjs/client/auth.js +72 -0
- package/dist/cjs/client/cashregisters.d.ts +21 -0
- package/dist/cjs/client/cashregisters.js +36 -0
- package/dist/cjs/client/errors.d.ts +159 -0
- package/dist/cjs/client/errors.js +226 -0
- package/dist/cjs/client/index.d.ts +8 -0
- package/dist/cjs/client/index.js +44 -0
- package/dist/cjs/client/receipts.d.ts +245 -0
- package/dist/cjs/client/receipts.js +452 -0
- package/dist/cjs/client/reports.d.ts +43 -0
- package/dist/cjs/client/reports.js +73 -0
- package/dist/cjs/client/status.d.ts +43 -0
- package/dist/cjs/client/status.js +47 -0
- package/dist/cjs/client/transport.d.ts +117 -0
- package/dist/cjs/client/transport.js +312 -0
- package/dist/cjs/enums/credit-card-provider.d.ts +22 -0
- package/dist/cjs/enums/credit-card-provider.js +23 -0
- package/dist/cjs/enums/define-enum.d.ts +15 -0
- package/dist/cjs/enums/define-enum.js +16 -0
- package/dist/cjs/enums/index.d.ts +7 -0
- package/dist/cjs/enums/index.js +17 -0
- package/dist/cjs/enums/keck-payment-method.d.ts +57 -0
- package/dist/cjs/enums/keck-payment-method.js +23 -0
- package/dist/cjs/enums/receipt-type.d.ts +41 -0
- package/dist/cjs/enums/receipt-type.js +18 -0
- package/dist/cjs/enums/stripe-link-mode.d.ts +17 -0
- package/dist/cjs/enums/stripe-link-mode.js +18 -0
- package/dist/cjs/enums/vat-rate.d.ts +40 -0
- package/dist/cjs/enums/vat-rate.js +19 -0
- package/dist/cjs/enums/voucher-action.d.ts +10 -0
- package/dist/cjs/enums/voucher-action.js +11 -0
- package/dist/cjs/enums/voucher-type.d.ts +9 -0
- package/dist/cjs/enums/voucher-type.js +10 -0
- package/dist/cjs/index.d.ts +5 -0
- package/dist/cjs/index.js +97 -0
- package/dist/cjs/kasse/artikel.d.ts +83 -0
- package/dist/cjs/kasse/artikel.js +79 -0
- package/dist/cjs/kasse/client.d.ts +10 -0
- package/dist/cjs/kasse/client.js +38 -0
- package/dist/cjs/kasse/drucker.d.ts +54 -0
- package/dist/cjs/kasse/drucker.js +41 -0
- package/dist/cjs/kasse/index.d.ts +11 -0
- package/dist/cjs/kasse/index.js +38 -0
- package/dist/cjs/kasse/settings.d.ts +115 -0
- package/dist/cjs/kasse/settings.js +62 -0
- package/dist/cjs/models/cancellation.d.ts +51 -0
- package/dist/cjs/models/cancellation.js +45 -0
- package/dist/cjs/models/cashregister.d.ts +77 -0
- package/dist/cjs/models/cashregister.js +53 -0
- package/dist/cjs/models/enum-payload.d.ts +26 -0
- package/dist/cjs/models/enum-payload.js +50 -0
- package/dist/cjs/models/hobex-receipt.d.ts +63 -0
- package/dist/cjs/models/hobex-receipt.js +86 -0
- package/dist/cjs/models/index.d.ts +10 -0
- package/dist/cjs/models/index.js +46 -0
- package/dist/cjs/models/receipt-company.d.ts +71 -0
- package/dist/cjs/models/receipt-company.js +52 -0
- package/dist/cjs/models/receipt-item.d.ts +100 -0
- package/dist/cjs/models/receipt-item.js +108 -0
- package/dist/cjs/models/receipt-summary.d.ts +92 -0
- package/dist/cjs/models/receipt-summary.js +54 -0
- package/dist/cjs/models/receipt.d.ts +108 -0
- package/dist/cjs/models/receipt.js +119 -0
- package/dist/cjs/models/report-month.d.ts +32 -0
- package/dist/cjs/models/report-month.js +65 -0
- package/dist/cjs/models/stripe-url-session.d.ts +18 -0
- package/dist/cjs/models/stripe-url-session.js +20 -0
- package/dist/cjs/models/voucher.d.ts +41 -0
- package/dist/cjs/models/voucher.js +56 -0
- package/dist/cjs/money.d.ts +23 -0
- package/dist/cjs/money.js +35 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/payments/hobex.d.ts +87 -0
- package/dist/cjs/payments/hobex.js +167 -0
- package/dist/cjs/payments/index.d.ts +33 -0
- package/dist/cjs/payments/index.js +42 -0
- package/dist/cjs/payments/stripe.d.ts +61 -0
- package/dist/cjs/payments/stripe.js +157 -0
- package/dist/cjs/printing/escpos.d.ts +219 -0
- package/dist/cjs/printing/escpos.js +636 -0
- package/dist/cjs/printing/index.d.ts +3 -0
- package/dist/cjs/printing/index.js +33 -0
- package/dist/cjs/printing/printable.d.ts +27 -0
- package/dist/cjs/printing/printable.js +90 -0
- package/dist/cjs/printing/webusb.d.ts +89 -0
- package/dist/cjs/printing/webusb.js +110 -0
- package/dist/cjs/react/index.d.ts +46 -0
- package/dist/cjs/react/index.js +51 -0
- package/dist/cjs/receipt/discount.d.ts +12 -0
- package/dist/cjs/receipt/discount.js +54 -0
- package/dist/cjs/receipt/epos.d.ts +53 -0
- package/dist/cjs/receipt/epos.js +95 -0
- package/dist/cjs/receipt/grid.d.ts +47 -0
- package/dist/cjs/receipt/grid.js +122 -0
- package/dist/cjs/receipt/index.d.ts +4 -0
- package/dist/cjs/receipt/index.js +29 -0
- package/dist/cjs/receipt/layout-escpos.d.ts +35 -0
- package/dist/cjs/receipt/layout-escpos.js +67 -0
- package/dist/cjs/receipt/layout.d.ts +180 -0
- package/dist/cjs/receipt/layout.js +594 -0
- package/dist/cjs/register/index.d.ts +51 -0
- package/dist/cjs/register/index.js +61 -0
- package/dist/cjs/register/pairing.d.ts +248 -0
- package/dist/cjs/register/pairing.js +279 -0
- package/dist/cjs/register/session.d.ts +37 -0
- package/dist/cjs/register/session.js +52 -0
- package/dist/cjs/vienna-time.d.ts +23 -0
- package/dist/cjs/vienna-time.js +114 -0
- package/dist/esm/client/api.d.ts +75 -0
- package/dist/esm/client/api.js +37 -0
- package/dist/esm/client/auth.d.ts +52 -0
- package/dist/esm/client/auth.js +68 -0
- package/dist/esm/client/cashregisters.d.ts +21 -0
- package/dist/esm/client/cashregisters.js +33 -0
- package/dist/esm/client/errors.d.ts +159 -0
- package/dist/esm/client/errors.js +212 -0
- package/dist/esm/client/index.d.ts +8 -0
- package/dist/esm/client/index.js +8 -0
- package/dist/esm/client/receipts.d.ts +245 -0
- package/dist/esm/client/receipts.js +438 -0
- package/dist/esm/client/reports.d.ts +43 -0
- package/dist/esm/client/reports.js +69 -0
- package/dist/esm/client/status.d.ts +43 -0
- package/dist/esm/client/status.js +43 -0
- package/dist/esm/client/transport.d.ts +117 -0
- package/dist/esm/client/transport.js +307 -0
- package/dist/esm/enums/credit-card-provider.d.ts +22 -0
- package/dist/esm/enums/credit-card-provider.js +20 -0
- package/dist/esm/enums/define-enum.d.ts +15 -0
- package/dist/esm/enums/define-enum.js +13 -0
- package/dist/esm/enums/index.d.ts +7 -0
- package/dist/esm/enums/index.js +7 -0
- package/dist/esm/enums/keck-payment-method.d.ts +57 -0
- package/dist/esm/enums/keck-payment-method.js +20 -0
- package/dist/esm/enums/receipt-type.d.ts +41 -0
- package/dist/esm/enums/receipt-type.js +15 -0
- package/dist/esm/enums/stripe-link-mode.d.ts +17 -0
- package/dist/esm/enums/stripe-link-mode.js +15 -0
- package/dist/esm/enums/vat-rate.d.ts +40 -0
- package/dist/esm/enums/vat-rate.js +16 -0
- package/dist/esm/enums/voucher-action.d.ts +10 -0
- package/dist/esm/enums/voucher-action.js +8 -0
- package/dist/esm/enums/voucher-type.d.ts +9 -0
- package/dist/esm/enums/voucher-type.js +7 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/kasse/artikel.d.ts +83 -0
- package/dist/esm/kasse/artikel.js +70 -0
- package/dist/esm/kasse/client.d.ts +10 -0
- package/dist/esm/kasse/client.js +33 -0
- package/dist/esm/kasse/drucker.d.ts +54 -0
- package/dist/esm/kasse/drucker.js +36 -0
- package/dist/esm/kasse/index.d.ts +11 -0
- package/dist/esm/kasse/index.js +13 -0
- package/dist/esm/kasse/settings.d.ts +115 -0
- package/dist/esm/kasse/settings.js +58 -0
- package/dist/esm/models/cancellation.d.ts +51 -0
- package/dist/esm/models/cancellation.js +40 -0
- package/dist/esm/models/cashregister.d.ts +77 -0
- package/dist/esm/models/cashregister.js +50 -0
- package/dist/esm/models/enum-payload.d.ts +26 -0
- package/dist/esm/models/enum-payload.js +44 -0
- package/dist/esm/models/hobex-receipt.d.ts +63 -0
- package/dist/esm/models/hobex-receipt.js +80 -0
- package/dist/esm/models/index.d.ts +10 -0
- package/dist/esm/models/index.js +10 -0
- package/dist/esm/models/receipt-company.d.ts +71 -0
- package/dist/esm/models/receipt-company.js +48 -0
- package/dist/esm/models/receipt-item.d.ts +100 -0
- package/dist/esm/models/receipt-item.js +101 -0
- package/dist/esm/models/receipt-summary.d.ts +92 -0
- package/dist/esm/models/receipt-summary.js +49 -0
- package/dist/esm/models/receipt.d.ts +108 -0
- package/dist/esm/models/receipt.js +113 -0
- package/dist/esm/models/report-month.d.ts +32 -0
- package/dist/esm/models/report-month.js +58 -0
- package/dist/esm/models/stripe-url-session.d.ts +18 -0
- package/dist/esm/models/stripe-url-session.js +16 -0
- package/dist/esm/models/voucher.d.ts +41 -0
- package/dist/esm/models/voucher.js +51 -0
- package/dist/esm/money.d.ts +23 -0
- package/dist/esm/money.js +31 -0
- package/dist/esm/payments/hobex.d.ts +87 -0
- package/dist/esm/payments/hobex.js +162 -0
- package/dist/esm/payments/index.d.ts +33 -0
- package/dist/esm/payments/index.js +33 -0
- package/dist/esm/payments/stripe.d.ts +61 -0
- package/dist/esm/payments/stripe.js +153 -0
- package/dist/esm/printing/escpos.d.ts +219 -0
- package/dist/esm/printing/escpos.js +616 -0
- package/dist/esm/printing/index.d.ts +3 -0
- package/dist/esm/printing/index.js +3 -0
- package/dist/esm/printing/printable.d.ts +27 -0
- package/dist/esm/printing/printable.js +87 -0
- package/dist/esm/printing/webusb.d.ts +89 -0
- package/dist/esm/printing/webusb.js +101 -0
- package/dist/esm/react/index.d.ts +46 -0
- package/dist/esm/react/index.js +47 -0
- package/dist/esm/receipt/discount.d.ts +12 -0
- package/dist/esm/receipt/discount.js +51 -0
- package/dist/esm/receipt/epos.d.ts +53 -0
- package/dist/esm/receipt/epos.js +86 -0
- package/dist/esm/receipt/grid.d.ts +47 -0
- package/dist/esm/receipt/grid.js +116 -0
- package/dist/esm/receipt/index.d.ts +4 -0
- package/dist/esm/receipt/index.js +4 -0
- package/dist/esm/receipt/layout-escpos.d.ts +35 -0
- package/dist/esm/receipt/layout-escpos.js +64 -0
- package/dist/esm/receipt/layout.d.ts +180 -0
- package/dist/esm/receipt/layout.js +583 -0
- package/dist/esm/register/index.d.ts +51 -0
- package/dist/esm/register/index.js +51 -0
- package/dist/esm/register/pairing.d.ts +248 -0
- package/dist/esm/register/pairing.js +270 -0
- package/dist/esm/register/session.d.ts +37 -0
- package/dist/esm/register/session.js +48 -0
- package/dist/esm/vienna-time.d.ts +23 -0
- package/dist/esm/vienna-time.js +110 -0
- package/fixtures/belege/langer-artikelname.json +43 -0
- package/fixtures/belege/null-ausfall.json +41 -0
- package/fixtures/belege/null-jahr.json +41 -0
- package/fixtures/belege/null-monat.json +41 -0
- package/fixtures/belege/null-pruef.json +41 -0
- package/fixtures/belege/null-schluss.json +41 -0
- package/fixtures/belege/null-start.json +40 -0
- package/fixtures/belege/rabattzeilen.json +61 -0
- package/fixtures/belege/signaturausfall-verkauf.json +49 -0
- package/fixtures/belege/storno-teil.json +48 -0
- package/fixtures/belege/storno-voll.json +54 -0
- package/fixtures/belege/testkasse-verkauf.json +50 -0
- package/fixtures/belege/testsignatur-verkauf.json +50 -0
- package/fixtures/belege/training.json +49 -0
- package/fixtures/belege/verkauf-bar.json +49 -0
- package/fixtures/belege/verkauf-karte.json +49 -0
- package/fixtures/belege/verkauf-kleinunternehmer.json +42 -0
- package/fixtures/erwartet/langer-artikelname.grid32.txt +25 -0
- package/fixtures/erwartet/langer-artikelname.grid48.txt +24 -0
- package/fixtures/erwartet/langer-artikelname.lines.json +216 -0
- package/fixtures/erwartet/null-ausfall.grid32.txt +28 -0
- package/fixtures/erwartet/null-ausfall.grid48.txt +25 -0
- package/fixtures/erwartet/null-ausfall.lines.json +220 -0
- package/fixtures/erwartet/null-jahr.grid32.txt +29 -0
- package/fixtures/erwartet/null-jahr.grid48.txt +25 -0
- package/fixtures/erwartet/null-jahr.lines.json +220 -0
- package/fixtures/erwartet/null-monat.grid32.txt +28 -0
- package/fixtures/erwartet/null-monat.grid48.txt +25 -0
- package/fixtures/erwartet/null-monat.lines.json +220 -0
- package/fixtures/erwartet/null-pruef.grid32.txt +28 -0
- package/fixtures/erwartet/null-pruef.grid48.txt +25 -0
- package/fixtures/erwartet/null-pruef.lines.json +220 -0
- package/fixtures/erwartet/null-schluss.grid32.txt +28 -0
- package/fixtures/erwartet/null-schluss.grid48.txt +25 -0
- package/fixtures/erwartet/null-schluss.lines.json +220 -0
- package/fixtures/erwartet/null-start.grid32.txt +28 -0
- package/fixtures/erwartet/null-start.grid48.txt +25 -0
- package/fixtures/erwartet/null-start.lines.json +220 -0
- package/fixtures/erwartet/rabattzeilen.grid32.txt +27 -0
- package/fixtures/erwartet/rabattzeilen.grid48.txt +26 -0
- package/fixtures/erwartet/rabattzeilen.lines.json +286 -0
- package/fixtures/erwartet/signaturausfall-verkauf.grid32.txt +28 -0
- package/fixtures/erwartet/signaturausfall-verkauf.grid48.txt +26 -0
- package/fixtures/erwartet/signaturausfall-verkauf.lines.json +266 -0
- package/fixtures/erwartet/storno-teil.grid32.txt +27 -0
- package/fixtures/erwartet/storno-teil.grid48.txt +26 -0
- package/fixtures/erwartet/storno-teil.lines.json +237 -0
- package/fixtures/erwartet/storno-voll.grid32.txt +29 -0
- package/fixtures/erwartet/storno-voll.grid48.txt +28 -0
- package/fixtures/erwartet/storno-voll.lines.json +277 -0
- package/fixtures/erwartet/testkasse-verkauf.grid32.txt +28 -0
- package/fixtures/erwartet/testkasse-verkauf.grid48.txt +27 -0
- package/fixtures/erwartet/testkasse-verkauf.lines.json +270 -0
- package/fixtures/erwartet/testsignatur-verkauf.grid32.txt +30 -0
- package/fixtures/erwartet/testsignatur-verkauf.grid48.txt +27 -0
- package/fixtures/erwartet/testsignatur-verkauf.lines.json +270 -0
- package/fixtures/erwartet/training.grid32.txt +33 -0
- package/fixtures/erwartet/training.grid48.txt +30 -0
- package/fixtures/erwartet/training.lines.json +277 -0
- package/fixtures/erwartet/verkauf-bar.grid32.txt +25 -0
- package/fixtures/erwartet/verkauf-bar.grid48.txt +24 -0
- package/fixtures/erwartet/verkauf-bar.lines.json +256 -0
- package/fixtures/erwartet/verkauf-karte.grid32.txt +25 -0
- package/fixtures/erwartet/verkauf-karte.grid48.txt +24 -0
- package/fixtures/erwartet/verkauf-karte.lines.json +256 -0
- package/fixtures/erwartet/verkauf-kleinunternehmer.grid32.txt +26 -0
- package/fixtures/erwartet/verkauf-kleinunternehmer.grid48.txt +25 -0
- package/fixtures/erwartet/verkauf-kleinunternehmer.lines.json +226 -0
- package/fixtures/manifest.json +107 -0
- package/package.json +135 -0
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { ReceiptType, KeckPaymentMethod, CreditCardProvider, VoucherAction, VoucherType, } from '../enums/index.js';
|
|
2
|
+
import { fromReceiptCompanyPayload, fromReceiptPayload, toReceiptItemPayload, toVoucherPayload, receiptItemIsValid, voucherIsValid, fromReceiptSummaryPayload, isCancellationReason, } from '../models/index.js';
|
|
3
|
+
import { parseServerTimeStamp, toViennaWallClock } from '../vienna-time.js';
|
|
4
|
+
import { euroToCents } from '../money.js';
|
|
5
|
+
import { KasseneckValidationError } from './errors.js';
|
|
6
|
+
/**
|
|
7
|
+
* Gemeinsame Umsetzung aller Belegarten (Zwilling von `_createReceipt`).
|
|
8
|
+
* Bewusst **nicht** Teil der Paketoberflaeche: der Belegtyp gehoert nicht in
|
|
9
|
+
* die Hand des Aufrufers, sondern zu einem der benannten Aufrufe darunter.
|
|
10
|
+
*/
|
|
11
|
+
export async function createReceipt(rufen, options) {
|
|
12
|
+
return belegAusHuelle(await rufen('createReceipt', createReceiptParams(options)), 'createReceipt');
|
|
13
|
+
}
|
|
14
|
+
/** Wie [createReceipt], liest aus derselben Antwort zusaetzlich die Firmendaten. */
|
|
15
|
+
async function createReceiptWithCompany(rufen, options) {
|
|
16
|
+
return belegMitFirmaAusHuelle(await rufen('createReceipt', createReceiptParams(options)), 'createReceipt');
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Baut die Nutzlast von `createReceipt` und prueft die Eingabe. Wirft, bevor
|
|
20
|
+
* irgendetwas rausgeht — ein Beleg ist nicht folgenlos wiederholbar.
|
|
21
|
+
*/
|
|
22
|
+
function createReceiptParams(options) {
|
|
23
|
+
const typ = belegtyp(options.receiptType);
|
|
24
|
+
const { items, vouchers } = options;
|
|
25
|
+
if (typ.needsItems) {
|
|
26
|
+
// Ein reiner Gutscheinverkauf ist ein Umsatz ohne Positionen — deshalb
|
|
27
|
+
// zaehlt er hier wie eine Position (wie im Flutter-Vorbild).
|
|
28
|
+
const hatVerkaufsgutschein = vouchers?.some((v) => v.action === VoucherAction.sell) ?? false;
|
|
29
|
+
if ((items == null || items.length === 0) && !hatVerkaufsgutschein) {
|
|
30
|
+
throw eingabefehler(`Positionen sind Pflicht bei receiptType "${typ.value}" und duerfen nicht leer sein.`);
|
|
31
|
+
}
|
|
32
|
+
if (items?.some((item) => !receiptItemIsValid(item)) ?? false) {
|
|
33
|
+
throw eingabefehler('Ungueltige Position uebergeben.');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const params = { receiptType: typ.value };
|
|
37
|
+
if (vouchers != null && vouchers.length > 0) {
|
|
38
|
+
if (!typ.allowsVouchers) {
|
|
39
|
+
throw eingabefehler(`Gutscheine sind nicht erlaubt bei receiptType "${typ.value}".`);
|
|
40
|
+
}
|
|
41
|
+
if (vouchers.some((voucher) => !voucherIsValid(voucher))) {
|
|
42
|
+
throw eingabefehler('Ungueltiger Gutschein uebergeben.');
|
|
43
|
+
}
|
|
44
|
+
const kombinationsfehler = checkVoucherCombinationError(vouchers, items ?? []);
|
|
45
|
+
if (kombinationsfehler != null) {
|
|
46
|
+
throw eingabefehler(kombinationsfehler);
|
|
47
|
+
}
|
|
48
|
+
params['vouchers'] = alsNutzlast(vouchers, toVoucherPayload);
|
|
49
|
+
}
|
|
50
|
+
if (items != null && items.length > 0) {
|
|
51
|
+
params['items'] = alsNutzlast(items, toReceiptItemPayload);
|
|
52
|
+
}
|
|
53
|
+
// Vom Aufrufer kommt sie geprueft, vom Server roh — siehe die beiden Felder
|
|
54
|
+
// in [CreateReceiptOptions]. Die Typpruefung allein reicht dafuer nicht: ein
|
|
55
|
+
// Verbraucher ohne Typen faellt durch dieses Netz, und ein Tippfehler in der
|
|
56
|
+
// Zahlungsart faellt sonst erst am Server auf.
|
|
57
|
+
const zahlungsart = options.paymentMethod != null ? gepruefteZahlungsart(options.paymentMethod) : options.paymentMethodFromServer;
|
|
58
|
+
if (zahlungsart != null) {
|
|
59
|
+
params['paymentMethod'] = zahlungsart;
|
|
60
|
+
const anbieter = options.creditCardProvider ?? CreditCardProvider.custom;
|
|
61
|
+
if (zahlungsart === KeckPaymentMethod.creditCard.value) {
|
|
62
|
+
if (options.cardPaymentId != null) {
|
|
63
|
+
params['cardPaymentId'] = options.cardPaymentId;
|
|
64
|
+
// Der Kartenanbieter kommt dagegen vom Aufrufer, nicht vom Server —
|
|
65
|
+
// hier bleibt der Schreibpfad streng.
|
|
66
|
+
params['creditCardProvider'] = kartenanbieter(anbieter);
|
|
67
|
+
// Bewusst auch als `null`, wenn der Anbieter keine Rohdaten liefert:
|
|
68
|
+
// das Feld gehoert zur Kartenzahlung und das Backend nimmt null an.
|
|
69
|
+
params['cardPaymentData'] = options.cardPaymentData ?? null;
|
|
70
|
+
}
|
|
71
|
+
else if (anbieter !== CreditCardProvider.custom) {
|
|
72
|
+
throw eingabefehler(`cardPaymentId ist Pflicht bei creditCardProvider "${anbieter}".`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (options.customProjectId != null) {
|
|
77
|
+
params['customProjectId'] = options.customProjectId;
|
|
78
|
+
}
|
|
79
|
+
if (options.customerDetails != null) {
|
|
80
|
+
params['customerDetails'] = options.customerDetails.join('\n');
|
|
81
|
+
}
|
|
82
|
+
if (options.legalMessage != null) {
|
|
83
|
+
params['legalMessage'] = options.legalMessage.join('\n');
|
|
84
|
+
}
|
|
85
|
+
return params;
|
|
86
|
+
}
|
|
87
|
+
/** Normalbeleg (Verkauf) nach RKSV. */
|
|
88
|
+
export function sellReceipt(rufen, options) {
|
|
89
|
+
return createReceipt(rufen, { ...options, receiptType: ReceiptType.standard });
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Normalbeleg wie [sellReceipt], liefert zusaetzlich die Firmen-/Druckdaten
|
|
93
|
+
* aus derselben Antwort — alles, was ein Belegdruck braucht, in einem Aufruf.
|
|
94
|
+
*/
|
|
95
|
+
export function sellReceiptWithCompany(rufen, options) {
|
|
96
|
+
return createReceiptWithCompany(rufen, { ...options, receiptType: ReceiptType.standard });
|
|
97
|
+
}
|
|
98
|
+
const NOTE_MAX = 200;
|
|
99
|
+
/**
|
|
100
|
+
* Storno-Beleg zu einem bestehenden Beleg — voll oder in Teilen. Prueft die
|
|
101
|
+
* Eingabe, bevor etwas hinausgeht; der Server haelt die Restmengen und die
|
|
102
|
+
* Reichweite des Rechts (eigene/alle) und antwortet mit dem fertigen,
|
|
103
|
+
* signierten Storno-Beleg. Gutscheine des Originals wandern nicht mit.
|
|
104
|
+
*/
|
|
105
|
+
export async function cancelReceipt(rufen, options) {
|
|
106
|
+
const cashregisterId = options.receipt?.cashregisterId ?? options.cashregisterId;
|
|
107
|
+
const originalReceiptId = options.receipt?.receiptId ?? options.originalReceiptId;
|
|
108
|
+
if (typeof cashregisterId !== 'string' || cashregisterId.trim() === '') {
|
|
109
|
+
throw new KasseneckValidationError('cancelReceipt', 'cashregisterId fehlt', 'request');
|
|
110
|
+
}
|
|
111
|
+
if (typeof originalReceiptId !== 'string' || originalReceiptId.trim() === '') {
|
|
112
|
+
throw new KasseneckValidationError('cancelReceipt', 'originalReceiptId fehlt', 'request');
|
|
113
|
+
}
|
|
114
|
+
if (!isCancellationReason(options.reason)) {
|
|
115
|
+
throw new KasseneckValidationError('cancelReceipt', 'Storno-Grund fehlt oder ist unbekannt', 'request');
|
|
116
|
+
}
|
|
117
|
+
if (options.items !== undefined) {
|
|
118
|
+
if (!Array.isArray(options.items) || options.items.length === 0) {
|
|
119
|
+
throw new KasseneckValidationError('cancelReceipt', 'items muss eine nicht leere Liste sein', 'request');
|
|
120
|
+
}
|
|
121
|
+
for (const pos of options.items) {
|
|
122
|
+
if (!Number.isInteger(pos.index) || pos.index < 0 || !Number.isInteger(pos.quantity) || pos.quantity < 1) {
|
|
123
|
+
throw new KasseneckValidationError('cancelReceipt', 'Storno-Menge muss eine ganze Zahl >= 1 sein', 'request');
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (options.note !== undefined && options.note.length > NOTE_MAX) {
|
|
128
|
+
throw new KasseneckValidationError('cancelReceipt', `Anmerkung ist zu lang (hoechstens ${NOTE_MAX} Zeichen)`, 'request');
|
|
129
|
+
}
|
|
130
|
+
const params = { cashregisterId, originalReceiptId, reason: options.reason };
|
|
131
|
+
if (options.items !== undefined)
|
|
132
|
+
params.items = options.items.map((p) => ({ index: p.index, quantity: p.quantity }));
|
|
133
|
+
if (options.note !== undefined && options.note !== '')
|
|
134
|
+
params.note = options.note;
|
|
135
|
+
if (options.paymentMethod != null)
|
|
136
|
+
params.paymentMethod = gepruefteZahlungsart(options.paymentMethod);
|
|
137
|
+
const daten = await rufen('cancelReceipt', params);
|
|
138
|
+
const receipt = belegAusHuelle(daten, 'cancelReceipt');
|
|
139
|
+
const huelle = daten;
|
|
140
|
+
const bezug = huelle.cancellationOf;
|
|
141
|
+
if (bezug == null || typeof bezug.receiptId !== 'string') {
|
|
142
|
+
throw antwortfehler('cancelReceipt', 'Antwort enthaelt keinen Bezug (data.cancellationOf fehlt)');
|
|
143
|
+
}
|
|
144
|
+
if (!Array.isArray(huelle.remaining) || !huelle.remaining.every((n) => Number.isInteger(n))) {
|
|
145
|
+
throw antwortfehler('cancelReceipt', 'Antwort enthaelt keine Restmengen (data.remaining fehlt)');
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
receipt,
|
|
149
|
+
cancellationOf: { receiptId: bezug.receiptId, fullReceiptId: typeof bezug.fullReceiptId === 'string' ? bezug.fullReceiptId : null },
|
|
150
|
+
remaining: huelle.remaining,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Stornobeleg aus frei uebergebenen Positionen — fuer den Fall, dass der
|
|
155
|
+
* Originalbeleg nicht als Objekt vorliegt. Die Positionen gehen **unveraendert**
|
|
156
|
+
* hinaus; das Vorzeichen setzt der Aufrufer.
|
|
157
|
+
*/
|
|
158
|
+
export function createCancelReceipt(rufen, options) {
|
|
159
|
+
return createReceipt(rufen, { ...options, receiptType: ReceiptType.cancellation });
|
|
160
|
+
}
|
|
161
|
+
/** Nullbeleg (RKSV-Pruefbeleg) — ohne Positionen und ohne Zahlungsart. */
|
|
162
|
+
export function zeroReceipt(rufen) {
|
|
163
|
+
return createReceipt(rufen, { receiptType: ReceiptType.zero });
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Belege einer Kasse auflisten — die Grundlage jeder Belegliste in der
|
|
167
|
+
* Browser-Kasse (Nachdruck, Storno, Tagesuebersicht).
|
|
168
|
+
*
|
|
169
|
+
* Die Eintraege sind **Zusammenfassungen**, keine vollstaendigen Belege (siehe
|
|
170
|
+
* [ReceiptSummary]); fuer Nachdruck oder Storno gehoert der Beleg ueber
|
|
171
|
+
* [getReceipt] bzw. [getReceiptWithCompany] einzeln geholt.
|
|
172
|
+
*
|
|
173
|
+
* **Anmeldeweg:** Der Endpunkt laeuft im Backend unter
|
|
174
|
+
* `checkRequest(req, 'customer', …, {allowRegisterUser: true})`, der Bearer
|
|
175
|
+
* muss also ein Firebase-ID-Token sein — mit `apiKeyAuth` ist er nicht
|
|
176
|
+
* erreichbar. Ausserdem prueft das Backend die Kassenzuweisung hier im Rumpf
|
|
177
|
+
* (der Endpunkt laeuft mit `checkCashRegister: false`), ein Kassen-Benutzer
|
|
178
|
+
* bekommt also nur die ihm zugewiesenen Kassen.
|
|
179
|
+
*/
|
|
180
|
+
export async function listMyReceipts(rufen, options) {
|
|
181
|
+
if (typeof options.cashregisterId !== 'string' || options.cashregisterId.trim() === '') {
|
|
182
|
+
throw new KasseneckValidationError('listMyReceipts', 'cashregisterId fehlt', 'request');
|
|
183
|
+
}
|
|
184
|
+
if (options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit < 1)) {
|
|
185
|
+
throw new KasseneckValidationError('listMyReceipts', `limit muss eine ganze Zahl ab 1 sein, war "${options.limit}"`, 'request');
|
|
186
|
+
}
|
|
187
|
+
for (const feld of ['from', 'to']) {
|
|
188
|
+
const wert = options[feld];
|
|
189
|
+
if (wert !== undefined && !/^\d{4}-\d{2}-\d{2}/.test(wert)) {
|
|
190
|
+
throw new KasseneckValidationError('listMyReceipts', `${feld} muss mit YYYY-MM-DD beginnen, war "${wert}"`, 'request');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const daten = await rufen('listMyReceipts', {
|
|
194
|
+
cashregisterid: options.cashregisterId,
|
|
195
|
+
limit: options.limit,
|
|
196
|
+
...(options.from !== undefined ? { from: options.from } : {}),
|
|
197
|
+
...(options.to !== undefined ? { to: options.to } : {}),
|
|
198
|
+
});
|
|
199
|
+
const liste = daten?.receipts;
|
|
200
|
+
if (!Array.isArray(liste)) {
|
|
201
|
+
throw antwortfehler('listMyReceipts', 'Antwort enthaelt keine Belegliste (data.receipts fehlt)');
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
receipts: liste.map((eintrag) => fromReceiptSummaryPayload((typeof eintrag === 'object' && eintrag !== null ? eintrag : {}))),
|
|
205
|
+
stats: kennzahlen(daten?.stats),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Kennzahlen aus der Antwort. Sie duerfen Luecken haben, ohne die Belegliste
|
|
210
|
+
* unbrauchbar zu machen — eine fehlende Wochenreihe ist kein Grund, dem
|
|
211
|
+
* Kassier die Belege vorzuenthalten.
|
|
212
|
+
*/
|
|
213
|
+
function kennzahlen(roh) {
|
|
214
|
+
const quelle = (typeof roh === 'object' && roh !== null ? roh : {});
|
|
215
|
+
const tage = Array.isArray(quelle.days) ? quelle.days : [];
|
|
216
|
+
return {
|
|
217
|
+
today: {
|
|
218
|
+
revenueCents: euroToCents(quelle.today?.umsatz),
|
|
219
|
+
count: typeof quelle.today?.count === 'number' ? quelle.today.count : 0,
|
|
220
|
+
},
|
|
221
|
+
trendPercent: typeof quelle.trendPct === 'number' ? quelle.trendPct : null,
|
|
222
|
+
days: tage.map((tag) => {
|
|
223
|
+
const eintrag = (typeof tag === 'object' && tag !== null ? tag : {});
|
|
224
|
+
return {
|
|
225
|
+
date: typeof eintrag.date === 'string' ? eintrag.date : '',
|
|
226
|
+
revenueCents: euroToCents(eintrag.umsatz),
|
|
227
|
+
};
|
|
228
|
+
}),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
/** Einzelnen Beleg der angemeldeten Kasse holen. */
|
|
232
|
+
export async function getReceipt(rufen, receiptId) {
|
|
233
|
+
return belegAusHuelle(await rufen('getReceipt', { receiptId }), 'getReceipt');
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Wie [getReceipt], liefert zusaetzlich die Firmen-/Druckdaten aus derselben
|
|
237
|
+
* Antwort (Firma, Anschrift, Steuernummer, UID, Fusszeilen, Logo-Adresse,
|
|
238
|
+
* Kleinunternehmer-Kennzeichen) — die Angaben, die ein Beleg im Kopf und Fuss
|
|
239
|
+
* traegt (siehe models/receipt-company.ts).
|
|
240
|
+
*/
|
|
241
|
+
export async function getReceiptWithCompany(rufen, receiptId) {
|
|
242
|
+
return belegMitFirmaAusHuelle(await rufen('getReceipt', { receiptId }), 'getReceipt');
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Verschluesselte Volltext-Belegnummer erzeugen — der Bezeichner, unter dem der
|
|
246
|
+
* Beleg oeffentlich abrufbar ist (Beleg-Download, Pruefportal).
|
|
247
|
+
*/
|
|
248
|
+
export async function generateFullReceiptId(rufen, receiptId) {
|
|
249
|
+
const daten = await rufen('generateFullReceiptId', { receiptId });
|
|
250
|
+
const id = daten?.fullReceiptId;
|
|
251
|
+
if (typeof id !== 'string') {
|
|
252
|
+
throw antwortfehler('generateFullReceiptId', 'Antwort enthaelt keine fullReceiptId');
|
|
253
|
+
}
|
|
254
|
+
return id;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Berichtsmonat des allerersten Belegs dieser Kasse — die untere Grenze aller
|
|
258
|
+
* Monatsberichte.
|
|
259
|
+
*
|
|
260
|
+
* **Nicht fuer den Kassen-Benutzer-Weg (`registerUserAuth`):** dieser Endpunkt
|
|
261
|
+
* fuehrt kein `allowRegisterUser`, das Backend weist die Browser-Kasse hier ab
|
|
262
|
+
* (siehe Modulkommentar oben). Mit `apiKeyAuth` ist er offen.
|
|
263
|
+
*/
|
|
264
|
+
export async function getFirstReceiptDate(rufen) {
|
|
265
|
+
const roh = await rufen('getFirstReceiptDate');
|
|
266
|
+
if (typeof roh !== 'string') {
|
|
267
|
+
throw antwortfehler('getFirstReceiptDate', 'Antwort enthaelt keinen Zeitstempel');
|
|
268
|
+
}
|
|
269
|
+
// Ueber die Wiener Wanduhrzeit statt ueber getMonth(): der erste Beleg eines
|
|
270
|
+
// Monats liegt gern kurz nach Mitternacht, und der eingebaute Monat waere der
|
|
271
|
+
// des ausfuehrenden Rechners (siehe vienna-time.ts).
|
|
272
|
+
//
|
|
273
|
+
// Die Deutung wirft ein gewoehnliches Error, wenn der Zeitstempel unlesbar
|
|
274
|
+
// ist. Das ist hier ein Antwortproblem und gehoert in die Fehler-Union, die
|
|
275
|
+
// dieser Endpunkt zusagt — sonst faellt der Aufrufer aus allen Waechtern.
|
|
276
|
+
try {
|
|
277
|
+
const wanduhr = toViennaWallClock(parseServerTimeStamp(roh));
|
|
278
|
+
return { month: wanduhr.month, year: wanduhr.year };
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
// Der Zeitstempel selbst wandert NICHT in die Meldung: er kommt aus einer
|
|
282
|
+
// fremden Antwort, und was dort steht, ist nicht unsere Zusage.
|
|
283
|
+
throw antwortfehler('getFirstReceiptDate', 'Antwort enthaelt keinen lesbaren Zeitstempel');
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Prueft die Gutschein-Kombination eines Belegs und liefert den ersten
|
|
288
|
+
* Regelverstoss als Text (oder `null`) — Zwilling von
|
|
289
|
+
* `checkVoucherCombinationError`. Bewusst als Rueckgabewert statt als Fehler:
|
|
290
|
+
* eine Kassenoberflaeche will das pruefen, **bevor** sie den Beleg abschickt.
|
|
291
|
+
*/
|
|
292
|
+
export function checkVoucherCombinationError(vouchers, items) {
|
|
293
|
+
let einloesenWert = 0;
|
|
294
|
+
let verkaufenWert = 0;
|
|
295
|
+
let einloesenPromo = 0;
|
|
296
|
+
let verkaufenPromo = 0;
|
|
297
|
+
for (const voucher of vouchers) {
|
|
298
|
+
if (voucher.type === VoucherType.value && voucher.action === VoucherAction.redeem) {
|
|
299
|
+
einloesenWert++;
|
|
300
|
+
}
|
|
301
|
+
else if (voucher.type === VoucherType.value && voucher.action === VoucherAction.sell) {
|
|
302
|
+
verkaufenWert++;
|
|
303
|
+
}
|
|
304
|
+
else if (voucher.type === VoucherType.promo && voucher.action === VoucherAction.redeem) {
|
|
305
|
+
einloesenPromo++;
|
|
306
|
+
}
|
|
307
|
+
else if (voucher.type === VoucherType.promo && voucher.action === VoucherAction.sell) {
|
|
308
|
+
verkaufenPromo++;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const einloesenGesamt = einloesenWert + einloesenPromo;
|
|
312
|
+
const verkaufenGesamt = verkaufenWert + verkaufenPromo;
|
|
313
|
+
if (verkaufenPromo > 0) {
|
|
314
|
+
return 'Ungueltige Daten: Gutscheine mit type promo duerfen nicht verkauft werden';
|
|
315
|
+
}
|
|
316
|
+
if (einloesenPromo > 1) {
|
|
317
|
+
return 'Ungueltige Daten: Es darf nur ein Gutschein mit type promo eingeloest werden';
|
|
318
|
+
}
|
|
319
|
+
if (einloesenPromo > 0 && einloesenGesamt > 1) {
|
|
320
|
+
return 'Ungueltige Daten: Ein Gutschein mit type promo darf nicht mit anderen Gutscheinen kombiniert werden';
|
|
321
|
+
}
|
|
322
|
+
if (einloesenPromo > 0 && verkaufenGesamt > 0) {
|
|
323
|
+
return 'Ungueltige Daten: Mit einem Gutschein mit type promo duerfen nicht andere Gutscheine verkauft werden';
|
|
324
|
+
}
|
|
325
|
+
if (einloesenGesamt > 0 && items.length === 0) {
|
|
326
|
+
return 'Ungueltige Daten: Gutscheine mit action redeem benoetigen mindestens ein item';
|
|
327
|
+
}
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Belegtyp aufloesen. Anders als bei der Zahlungsart bleibt es hier streng:
|
|
332
|
+
* den Belegtyp setzt kein Aufrufer aus Serverdaten, er kommt aus einem der
|
|
333
|
+
* benannten Aufrufe — ein unbekannter Wert waere ein Programmierfehler.
|
|
334
|
+
*/
|
|
335
|
+
function belegtyp(wert) {
|
|
336
|
+
if (typeof wert === 'object') {
|
|
337
|
+
return wert;
|
|
338
|
+
}
|
|
339
|
+
if (!Object.prototype.hasOwnProperty.call(ReceiptType, wert)) {
|
|
340
|
+
throw eingabefehler(`Belegtyp: unbekannter Schluessel "${wert}"`);
|
|
341
|
+
}
|
|
342
|
+
return ReceiptType[wert];
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Wandelt Positionen/Gutscheine in ihre Nutzlast und faengt dabei die strengen
|
|
346
|
+
* Schreibpfad-Pruefungen der Modelle ab (unbekannter Steuersatz, unbekannte
|
|
347
|
+
* Gutschein-Aktion). Die Modelle werfen dort ein nacktes `Error`; hier soll
|
|
348
|
+
* nur die Fehler-Union des Pakets herauskommen, damit ein Verbraucher, der
|
|
349
|
+
* nach den Waechtern verzweigt, nicht im "unbekannt"-Zweig landet.
|
|
350
|
+
*/
|
|
351
|
+
function alsNutzlast(werte, wandeln) {
|
|
352
|
+
try {
|
|
353
|
+
return werte.map(wandeln);
|
|
354
|
+
}
|
|
355
|
+
catch (ursache) {
|
|
356
|
+
// Die Meldungen der Modelle sind vom Paket formuliert und geheimnisfrei
|
|
357
|
+
// (sie nennen den unbekannten Steuersatz bzw. Schluessel, sonst nichts).
|
|
358
|
+
throw eingabefehler(ursache instanceof Error ? ursache.message : 'Ungueltige Nutzlast uebergeben.');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/** Zahlungsart des Aufrufers pruefen — unbekannt wirft, bevor etwas rausgeht. */
|
|
362
|
+
function gepruefteZahlungsart(wert) {
|
|
363
|
+
if (typeof wert === 'object') {
|
|
364
|
+
return wert.value;
|
|
365
|
+
}
|
|
366
|
+
if (!Object.prototype.hasOwnProperty.call(KeckPaymentMethod, wert)) {
|
|
367
|
+
throw eingabefehler(`Zahlungsart: unbekannter Schluessel "${wert}"`);
|
|
368
|
+
}
|
|
369
|
+
return KeckPaymentMethod[wert].value;
|
|
370
|
+
}
|
|
371
|
+
/** Kartenanbieter pruefen — er stammt vom Aufrufer, nicht aus Serverdaten. */
|
|
372
|
+
function kartenanbieter(wert) {
|
|
373
|
+
if (!Object.prototype.hasOwnProperty.call(CreditCardProvider, wert)) {
|
|
374
|
+
throw eingabefehler(`Kartenanbieter: unbekannter Schluessel "${wert}"`);
|
|
375
|
+
}
|
|
376
|
+
return wert;
|
|
377
|
+
}
|
|
378
|
+
/** Fehler in der Eingabe des Aufrufers — es geht keine Anfrage raus. */
|
|
379
|
+
function eingabefehler(grund) {
|
|
380
|
+
return new KasseneckValidationError('createReceipt', grund, 'request');
|
|
381
|
+
}
|
|
382
|
+
/** Die Antwort meldete Erfolg, trug aber nicht, was der Aufruf zusagt. */
|
|
383
|
+
function antwortfehler(functionName, grund) {
|
|
384
|
+
return new KasseneckValidationError(functionName, grund, 'response');
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* `createReceipt` und `getReceipt` liefern den Beleg unter `data.receipt`,
|
|
388
|
+
* daneben die Firmen-/Druckdaten (Firma, Anschrift, Fusszeilen). Diese Lesart
|
|
389
|
+
* nimmt nur den Beleg: die Druckdaten gehoeren nicht zum RKSV-Kernbeleg (siehe
|
|
390
|
+
* models/receipt.ts), und die bestehenden Aufrufe sollen ihre Zusage behalten.
|
|
391
|
+
* Wer beides braucht, nimmt [belegMitFirmaAusHuelle] ueber die
|
|
392
|
+
* `…WithCompany`-Varianten.
|
|
393
|
+
*/
|
|
394
|
+
function belegAusHuelle(daten, functionName) {
|
|
395
|
+
const huelle = daten;
|
|
396
|
+
if (huelle == null || typeof huelle !== 'object' || huelle.receipt == null) {
|
|
397
|
+
throw antwortfehler(functionName, 'Antwort enthaelt keinen Beleg (data.receipt fehlt)');
|
|
398
|
+
}
|
|
399
|
+
const beleg = huelle.receipt;
|
|
400
|
+
if (typeof beleg !== 'object' || Array.isArray(beleg)) {
|
|
401
|
+
throw antwortfehler(functionName, 'Antwort enthaelt keinen Beleg (data.receipt ist kein Objekt)');
|
|
402
|
+
}
|
|
403
|
+
// Positionen und Gutscheine werden gleich mit `.map` gelesen. Eine Antwort,
|
|
404
|
+
// die dort etwas anderes als eine Liste fuehrt, ergab bis hierher einen
|
|
405
|
+
// nackten TypeError ("… .map is not a function") und fiel damit aus der
|
|
406
|
+
// Fehler-Union. Fehlend und `null` bleiben erlaubt: Nullbelege haben keine
|
|
407
|
+
// Positionen.
|
|
408
|
+
const roh = beleg;
|
|
409
|
+
for (const feld of ['items', 'vouchers']) {
|
|
410
|
+
const wert = roh[feld];
|
|
411
|
+
if (wert != null && !Array.isArray(wert)) {
|
|
412
|
+
throw antwortfehler(functionName, `Antwort enthaelt einen Beleg mit unbrauchbarem Feld "${feld}"`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return fromReceiptPayload(beleg);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Beleg **und** Firmendaten aus derselben Huelle. Der Beleg entscheidet:
|
|
419
|
+
* fehlt er, ist die Antwort unbrauchbar. Die Firmendaten duerfen dagegen
|
|
420
|
+
* luecken haben — ein Kundendokument ohne gepflegte Fusszeile ist kein Grund,
|
|
421
|
+
* einen ausgestellten Beleg nicht anzuzeigen (siehe models/receipt-company.ts).
|
|
422
|
+
*/
|
|
423
|
+
function belegMitFirmaAusHuelle(daten, functionName) {
|
|
424
|
+
const receipt = belegAusHuelle(daten, functionName);
|
|
425
|
+
const d = (daten ?? {});
|
|
426
|
+
const layout = d.layout && typeof d.layout === 'object' && Array.isArray(d.layout.lines) ? d.layout : null;
|
|
427
|
+
const pa = d.pruefangaben && typeof d.pruefangaben === 'object' ? d.pruefangaben : null;
|
|
428
|
+
const text = (v) => (typeof v === 'string' && v ? v : null);
|
|
429
|
+
return {
|
|
430
|
+
receipt,
|
|
431
|
+
company: fromReceiptCompanyPayload(daten),
|
|
432
|
+
testKasse: d.testKasse === true,
|
|
433
|
+
testSignatur: d.testSignatur === true,
|
|
434
|
+
kopfId: typeof d.kopfId === 'string' ? d.kopfId : null,
|
|
435
|
+
layout,
|
|
436
|
+
pruefangaben: pa ? { karteRegistriertAm: text(pa.karteRegistriertAm), kasseRegistriertAm: text(pa.kasseRegistriertAm) } : null,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ReportMonth } from '../models/index.js';
|
|
2
|
+
import type { KasseneckBinaryTransport } from './transport.js';
|
|
3
|
+
/**
|
|
4
|
+
* Bericht-Downloads — Zwilling von `downloadDailyReport`/`downloadMonthlyReport`
|
|
5
|
+
* in kasseneck_api/lib/kasseneck_api.dart (Zeilen 113-128).
|
|
6
|
+
*
|
|
7
|
+
* **Beide liefern ein PDF, also Binaerdaten.** Das Flutter-Vorbild liest die
|
|
8
|
+
* Antwort als Zeichenkette und nimmt davon `codeUnits`; in JavaScript waere das
|
|
9
|
+
* schlicht falsch — `response.text()` deutet die Bytes als UTF-8 und ersetzt
|
|
10
|
+
* jedes Byte ueber 0x7F durch U+FFFD. Ein so "heruntergeladener" Tagesbericht
|
|
11
|
+
* liesse sich in keinem Betrachter oeffnen. Dieses Paket weicht daher bewusst
|
|
12
|
+
* ab: es liest `arrayBuffer()` und liefert `Uint8Array` (siehe
|
|
13
|
+
* [createBinaryTransport] in transport.ts).
|
|
14
|
+
*
|
|
15
|
+
* **Kassen-Benutzer-Weg (`registerUserAuth`, Browser-Kasse):** Keiner der
|
|
16
|
+
* beiden Download-Endpunkte setzt `allowRegisterUser`; sie funktionieren nur
|
|
17
|
+
* mit `apiKeyAuth`. Dieses Paket bildet das **nicht** nach; wer darf,
|
|
18
|
+
* entscheidet allein das Backend. Der Hinweis steht hier, damit ein Leser
|
|
19
|
+
* nicht raten muss.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Tagesbericht als PDF.
|
|
23
|
+
*
|
|
24
|
+
* Der Kalendertag wird nach **Wiener** Wanduhrzeit bestimmt, nicht nach der
|
|
25
|
+
* Zeitzone des ausfuehrenden Rechners: der Geschaeftstag ist ein
|
|
26
|
+
* oesterreichischer Kalenderwert. Ein Zeitpunkt vom 1. Maerz 00:30 Wiener Zeit
|
|
27
|
+
* gehoert in den Tagesbericht vom 1. Maerz — die eingebauten
|
|
28
|
+
* `getDate()`/`getMonth()` wuerden auf einer UTC-Maschine den Bericht des
|
|
29
|
+
* Vortags holen (das Dart-Vorbild nimmt hier die Geraetezeit, was auf einer
|
|
30
|
+
* oesterreichischen Kasse zufaellig stimmt).
|
|
31
|
+
*/
|
|
32
|
+
export declare function downloadDailyReport(rufen: KasseneckBinaryTransport, date: Date): Promise<Uint8Array>;
|
|
33
|
+
/**
|
|
34
|
+
* Monatsbericht als PDF.
|
|
35
|
+
*
|
|
36
|
+
* **Der Endpunkt heisst `downloadReport`**, nicht `downloadMonthlyReport` —
|
|
37
|
+
* Methodenname und Endpunktname fallen hier auseinander (so im Dart-Vorbild und
|
|
38
|
+
* so im Backend, `report-endpoints.js`).
|
|
39
|
+
*
|
|
40
|
+
* `month` geht als **Zahl 1-12** raus (im Flutter-Paket `reportMonth.month.id`,
|
|
41
|
+
* wo `KeckMonth.january.id === 1` ist) — kein Name, kein nullbasierter Index.
|
|
42
|
+
*/
|
|
43
|
+
export declare function downloadMonthlyReport(rufen: KasseneckBinaryTransport, reportMonth: ReportMonth): Promise<Uint8Array>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { toViennaWallClock } from '../vienna-time.js';
|
|
2
|
+
import { KasseneckValidationError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Bericht-Downloads — Zwilling von `downloadDailyReport`/`downloadMonthlyReport`
|
|
5
|
+
* in kasseneck_api/lib/kasseneck_api.dart (Zeilen 113-128).
|
|
6
|
+
*
|
|
7
|
+
* **Beide liefern ein PDF, also Binaerdaten.** Das Flutter-Vorbild liest die
|
|
8
|
+
* Antwort als Zeichenkette und nimmt davon `codeUnits`; in JavaScript waere das
|
|
9
|
+
* schlicht falsch — `response.text()` deutet die Bytes als UTF-8 und ersetzt
|
|
10
|
+
* jedes Byte ueber 0x7F durch U+FFFD. Ein so "heruntergeladener" Tagesbericht
|
|
11
|
+
* liesse sich in keinem Betrachter oeffnen. Dieses Paket weicht daher bewusst
|
|
12
|
+
* ab: es liest `arrayBuffer()` und liefert `Uint8Array` (siehe
|
|
13
|
+
* [createBinaryTransport] in transport.ts).
|
|
14
|
+
*
|
|
15
|
+
* **Kassen-Benutzer-Weg (`registerUserAuth`, Browser-Kasse):** Keiner der
|
|
16
|
+
* beiden Download-Endpunkte setzt `allowRegisterUser`; sie funktionieren nur
|
|
17
|
+
* mit `apiKeyAuth`. Dieses Paket bildet das **nicht** nach; wer darf,
|
|
18
|
+
* entscheidet allein das Backend. Der Hinweis steht hier, damit ein Leser
|
|
19
|
+
* nicht raten muss.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Tagesbericht als PDF.
|
|
23
|
+
*
|
|
24
|
+
* Der Kalendertag wird nach **Wiener** Wanduhrzeit bestimmt, nicht nach der
|
|
25
|
+
* Zeitzone des ausfuehrenden Rechners: der Geschaeftstag ist ein
|
|
26
|
+
* oesterreichischer Kalenderwert. Ein Zeitpunkt vom 1. Maerz 00:30 Wiener Zeit
|
|
27
|
+
* gehoert in den Tagesbericht vom 1. Maerz — die eingebauten
|
|
28
|
+
* `getDate()`/`getMonth()` wuerden auf einer UTC-Maschine den Bericht des
|
|
29
|
+
* Vortags holen (das Dart-Vorbild nimmt hier die Geraetezeit, was auf einer
|
|
30
|
+
* oesterreichischen Kasse zufaellig stimmt).
|
|
31
|
+
*/
|
|
32
|
+
// `async`, damit auch die Eingabepruefung als abgelehntes Versprechen ankommt
|
|
33
|
+
// und nicht als synchroner Wurf am `await` des Aufrufers vorbei.
|
|
34
|
+
export async function downloadDailyReport(rufen, date) {
|
|
35
|
+
let wanduhr;
|
|
36
|
+
try {
|
|
37
|
+
wanduhr = toViennaWallClock(date);
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Ein Invalid Date ergaebe sonst `{year: null, month: null, day: null}` im
|
|
41
|
+
// Rumpf (JSON.stringify macht aus NaN null) und einen ratlosen Serverfehler.
|
|
42
|
+
throw eingabefehler('downloadDailyReport', 'Uebergebener Zeitpunkt ist unbrauchbar (Invalid Date)');
|
|
43
|
+
}
|
|
44
|
+
return rufen('downloadDailyReport', { year: wanduhr.year, month: wanduhr.month, day: wanduhr.day });
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Monatsbericht als PDF.
|
|
48
|
+
*
|
|
49
|
+
* **Der Endpunkt heisst `downloadReport`**, nicht `downloadMonthlyReport` —
|
|
50
|
+
* Methodenname und Endpunktname fallen hier auseinander (so im Dart-Vorbild und
|
|
51
|
+
* so im Backend, `report-endpoints.js`).
|
|
52
|
+
*
|
|
53
|
+
* `month` geht als **Zahl 1-12** raus (im Flutter-Paket `reportMonth.month.id`,
|
|
54
|
+
* wo `KeckMonth.january.id === 1` ist) — kein Name, kein nullbasierter Index.
|
|
55
|
+
*/
|
|
56
|
+
export async function downloadMonthlyReport(rufen, reportMonth) {
|
|
57
|
+
const { month, year } = reportMonth;
|
|
58
|
+
if (!Number.isInteger(month) || month < 1 || month > 12) {
|
|
59
|
+
throw eingabefehler('downloadReport', `Berichtsmonat: month muss 1-12 sein, war "${month}"`);
|
|
60
|
+
}
|
|
61
|
+
if (!Number.isInteger(year)) {
|
|
62
|
+
throw eingabefehler('downloadReport', `Berichtsmonat: year muss eine ganze Zahl sein, war "${year}"`);
|
|
63
|
+
}
|
|
64
|
+
return rufen('downloadReport', { month, year });
|
|
65
|
+
}
|
|
66
|
+
/** Fehler in der Eingabe des Aufrufers — es geht keine Anfrage raus. */
|
|
67
|
+
function eingabefehler(functionName, grund) {
|
|
68
|
+
return new KasseneckValidationError(functionName, grund, 'request');
|
|
69
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { KasseneckTransport } from './transport.js';
|
|
2
|
+
/**
|
|
3
|
+
* Betriebsstatus von Kasse und Signatureinheit bei FinanzOnline — Zwilling von
|
|
4
|
+
* `getCashboxStatus`/`getSignatureStatus` in
|
|
5
|
+
* kasseneck_api/lib/kasseneck_api.dart (Zeilen 410-440).
|
|
6
|
+
*
|
|
7
|
+
* Beide laufen ueber den **einen** Backend-Endpunkt `financeWebService`, der
|
|
8
|
+
* seine Vorgangsart als `method` **neben** `params` im Rumpf erwartet (so das
|
|
9
|
+
* Dart-Vorbild `_financeWebServicePostRequest`, so die Auswertung `b.method` im
|
|
10
|
+
* Backend). Die Antwort ist die uebliche Erfolgshuelle; darin liegt die
|
|
11
|
+
* FinanzOnline-Meldung unter `data.rkdbMessage`.
|
|
12
|
+
*
|
|
13
|
+
* **Kassen-Benutzer-Weg (`registerUserAuth`, Browser-Kasse):**
|
|
14
|
+
* `financeWebService` setzt kein `allowRegisterUser`; beide Abfragen
|
|
15
|
+
* funktionieren nur mit `apiKeyAuth`. Dieses Paket bildet das **nicht** nach —
|
|
16
|
+
* wer darf, entscheidet allein das Backend.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Betriebsstatus, den FinanzOnline meldet. Die vier bekannten Werte sind
|
|
20
|
+
* benannt; ein noch unbekannter Wert kommt **unveraendert** durch (`string`),
|
|
21
|
+
* statt wie im Dart-Vorbild still zu `null` zu werden. Lesen ist in diesem
|
|
22
|
+
* Paket tolerant: "unbekannter Status" und "kein Status" duerfen fuer den
|
|
23
|
+
* Aufrufer nicht dasselbe sein, sonst meldet eine Kasse mit einem neuen
|
|
24
|
+
* FON-Status genauso wie eine ohne Antwort.
|
|
25
|
+
*/
|
|
26
|
+
export type CashboxStatus = 'AKTIVIERT' | 'REGISTRIERT' | 'IN_BETRIEB' | 'AUSFALL' | (string & {});
|
|
27
|
+
/**
|
|
28
|
+
* Status der Signatureinheit. Wie [CashboxStatus], zusaetzlich der
|
|
29
|
+
* **abgeleitete** Wert `NOT_REGISTERED`: er ist kein FON-Status, sondern die
|
|
30
|
+
* Uebersetzung des Returncodes `B33` ("Signatureinheit nicht registriert").
|
|
31
|
+
*/
|
|
32
|
+
export type SignatureStatus = CashboxStatus | 'NOT_REGISTERED';
|
|
33
|
+
/** Betriebsstatus der Kasse bei FinanzOnline. */
|
|
34
|
+
export declare function getCashboxStatus(rufen: KasseneckTransport): Promise<CashboxStatus>;
|
|
35
|
+
/**
|
|
36
|
+
* Status der Signatureinheit mit der Zertifikatsseriennummer [zertifikatNrHex]
|
|
37
|
+
* (hexadezimal, z. B. `6F0404F0`).
|
|
38
|
+
*
|
|
39
|
+
* **Der Returncode wird vor dem Status gelesen** (wie im Dart-Vorbild): `B33`
|
|
40
|
+
* heisst "nicht registriert" und schlaegt jedes Statusfeld, das daneben stehen
|
|
41
|
+
* mag. Andersherum meldete eine nie registrierte Karte am Ende "in Betrieb".
|
|
42
|
+
*/
|
|
43
|
+
export declare function getSignatureStatus(rufen: KasseneckTransport, zertifikatNrHex: string): Promise<SignatureStatus>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { KasseneckValidationError } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Vorgangsnamen, unter denen Fehler dieser beiden Abfragen erscheinen. Beide
|
|
4
|
+
* laufen ueber denselben Endpunkt `financeWebService`; ein Fehler, der nur den
|
|
5
|
+
* Endpunkt nennt, verschweigt dem Aufrufer, ob der Kassen- oder der
|
|
6
|
+
* Signaturstatus scheiterte. Der Transport bildet denselben Namen aus
|
|
7
|
+
* `<funktion>/<method>` — hier stehen die Konstanten fuer die Fehler, die schon
|
|
8
|
+
* vor dem Senden entstehen.
|
|
9
|
+
*/
|
|
10
|
+
const VORGANG_KASSE = 'financeWebService/status_cashbox';
|
|
11
|
+
const VORGANG_SIGNATUR = 'financeWebService/status_signature';
|
|
12
|
+
/** Betriebsstatus der Kasse bei FinanzOnline. */
|
|
13
|
+
export async function getCashboxStatus(rufen) {
|
|
14
|
+
const daten = await rufen('financeWebService', {}, { method: 'status_cashbox' });
|
|
15
|
+
return statusAusMeldung(daten, VORGANG_KASSE);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Status der Signatureinheit mit der Zertifikatsseriennummer [zertifikatNrHex]
|
|
19
|
+
* (hexadezimal, z. B. `6F0404F0`).
|
|
20
|
+
*
|
|
21
|
+
* **Der Returncode wird vor dem Status gelesen** (wie im Dart-Vorbild): `B33`
|
|
22
|
+
* heisst "nicht registriert" und schlaegt jedes Statusfeld, das daneben stehen
|
|
23
|
+
* mag. Andersherum meldete eine nie registrierte Karte am Ende "in Betrieb".
|
|
24
|
+
*/
|
|
25
|
+
export async function getSignatureStatus(rufen, zertifikatNrHex) {
|
|
26
|
+
if (typeof zertifikatNrHex !== 'string' || !zertifikatNrHex.trim()) {
|
|
27
|
+
throw new KasseneckValidationError(VORGANG_SIGNATUR, 'zertifikatNrHex fehlt', 'request');
|
|
28
|
+
}
|
|
29
|
+
const daten = await rufen('financeWebService', { zertifikatnr_hex: zertifikatNrHex }, { method: 'status_signature' });
|
|
30
|
+
if (daten?.rkdbMessage?.rc === 'B33') {
|
|
31
|
+
return 'NOT_REGISTERED';
|
|
32
|
+
}
|
|
33
|
+
return statusAusMeldung(daten, VORGANG_SIGNATUR);
|
|
34
|
+
}
|
|
35
|
+
function statusAusMeldung(daten, functionName) {
|
|
36
|
+
const status = daten?.rkdbMessage?.status;
|
|
37
|
+
if (typeof status !== 'string' || !status) {
|
|
38
|
+
// Der Inhalt der Antwort wandert NICHT in die Meldung — er kommt von einer
|
|
39
|
+
// fremden Stelle, und was dort steht, ist nicht unsere Zusage.
|
|
40
|
+
throw new KasseneckValidationError(functionName, 'Antwort enthaelt keinen Status (data.rkdbMessage.status)', 'response');
|
|
41
|
+
}
|
|
42
|
+
return status;
|
|
43
|
+
}
|