@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,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-Zeitstempel parsen: ohne Offset = Wiener Wanduhrzeit, mit
|
|
3
|
+
* Offset/`Z` = bereits ein echter Zeitpunkt. Ergebnis ist immer ein echter
|
|
4
|
+
* Zeitpunkt (JS-`Date`, intern UTC-Millisekunden seit Epoch).
|
|
5
|
+
*/
|
|
6
|
+
export declare function parseServerTimeStamp(raw: string): Date;
|
|
7
|
+
/** Wiener Wanduhrzeit: die Ziffern, die eine Uhr in Wien anzeigt. `month` ist 1-12. */
|
|
8
|
+
export interface ViennaWallClock {
|
|
9
|
+
year: number;
|
|
10
|
+
month: number;
|
|
11
|
+
day: number;
|
|
12
|
+
hour: number;
|
|
13
|
+
minute: number;
|
|
14
|
+
second: number;
|
|
15
|
+
millisecond: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Echter Zeitpunkt -> Wiener Wanduhrzeit (Gegenstueck zu
|
|
19
|
+
* [parseServerTimeStamp]). Der Versatz kommt aus derselben Sommerzeitregel wie
|
|
20
|
+
* die Hinrichtung; hier ist er direkt anwendbar, weil der Zeitpunkt schon
|
|
21
|
+
* feststeht.
|
|
22
|
+
*/
|
|
23
|
+
export declare function toViennaWallClock(zeitpunkt: Date): ViennaWallClock;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseServerTimeStamp = parseServerTimeStamp;
|
|
4
|
+
exports.toViennaWallClock = toViennaWallClock;
|
|
5
|
+
/**
|
|
6
|
+
* Deutung von Server-Zeitstempeln in der Geschaeftszeitzone Europe/Vienna —
|
|
7
|
+
* Zwilling von `ViennaTime.parseServerTimeStamp` in
|
|
8
|
+
* kasseneck_api/lib/services/vienna_time.dart.
|
|
9
|
+
*
|
|
10
|
+
* Zwei Richtungen: `parseServerTimeStamp` deutet die Nutzlast als Zeitpunkt,
|
|
11
|
+
* `toViennaWallClock` liest zu einem Zeitpunkt die Wiener Wanduhrzeit zurueck.
|
|
12
|
+
* Die Rueckrichtung braucht jeder, der aus einem Zeitpunkt einen fachlichen
|
|
13
|
+
* Kalenderwert ableitet (Berichtsmonat, Tagesabgrenzung): die eingebauten
|
|
14
|
+
* `getMonth()`/`getFullYear()` liefern die Zeitzone des ausfuehrenden
|
|
15
|
+
* Rechners, und ein Beleg vom 1. Maerz 00:30 Wiener Zeit faellt damit auf
|
|
16
|
+
* einer UTC-Maschine in den Februar. Weitere Anzeigehilfen des Dart-Vorbilds
|
|
17
|
+
* (`dayKey`, `now()`) sind nicht Teil dieses Pakets.
|
|
18
|
+
*
|
|
19
|
+
* Der Kasseneck-Server liefert Zeitstempel uneinheitlich: neue Belege als
|
|
20
|
+
* Wiener Wanduhrzeit ohne Offset (`getLokalAustriaTimeFinanzamtFormat()` im
|
|
21
|
+
* Backend, Format `YYYY-MM-DDTHH:mm:ss`), manche aeltere/andere Felder als
|
|
22
|
+
* echten Zeitpunkt mit `Z`/Offset. `new Date(raw)` allein ist dafuer
|
|
23
|
+
* ungeeignet: JS deutet einen offsetlosen ISO-String als **lokale** Zeit des
|
|
24
|
+
* ausfuehrenden Rechners — auf einer Wiener Maschine zufaellig richtig, in
|
|
25
|
+
* jeder anderen Zeitzone (z. B. einer Browser-Kasse im Ausland) falsch.
|
|
26
|
+
*/
|
|
27
|
+
const STUNDE_MS = 60 * 60 * 1000;
|
|
28
|
+
const NAIVER_ZEITSTEMPEL = /^(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?$/;
|
|
29
|
+
const HAT_ZEITZONE = /Z$|[+-]\d{2}:?\d{2}$/;
|
|
30
|
+
/**
|
|
31
|
+
* Server-Zeitstempel parsen: ohne Offset = Wiener Wanduhrzeit, mit
|
|
32
|
+
* Offset/`Z` = bereits ein echter Zeitpunkt. Ergebnis ist immer ein echter
|
|
33
|
+
* Zeitpunkt (JS-`Date`, intern UTC-Millisekunden seit Epoch).
|
|
34
|
+
*/
|
|
35
|
+
function parseServerTimeStamp(raw) {
|
|
36
|
+
const trimmed = raw.trim();
|
|
37
|
+
if (HAT_ZEITZONE.test(trimmed)) {
|
|
38
|
+
const zeitpunkt = new Date(trimmed);
|
|
39
|
+
// `new Date` macht aus '2026-99-99T00:00:00Z' klaglos ein Invalid Date:
|
|
40
|
+
// eine Zeitzone allein macht noch kein Datum. Ohne diese Pruefung wandert
|
|
41
|
+
// der Unsinn still weiter und wird erst in einer Ableitung zu NaN — dort,
|
|
42
|
+
// wo niemand mehr auf den Zeitstempel schaut.
|
|
43
|
+
if (Number.isNaN(zeitpunkt.getTime())) {
|
|
44
|
+
throw new Error(`Zeitstempel: unlesbares Format "${raw}"`);
|
|
45
|
+
}
|
|
46
|
+
return zeitpunkt;
|
|
47
|
+
}
|
|
48
|
+
const match = NAIVER_ZEITSTEMPEL.exec(trimmed);
|
|
49
|
+
if (!match) {
|
|
50
|
+
throw new Error(`Zeitstempel: unlesbares Format "${raw}"`);
|
|
51
|
+
}
|
|
52
|
+
const [, jahr, monat, tag, stunde, minute, sekunde, bruchteil] = match;
|
|
53
|
+
return fromViennaWallClock({
|
|
54
|
+
year: Number(jahr),
|
|
55
|
+
month: Number(monat),
|
|
56
|
+
day: Number(tag),
|
|
57
|
+
hour: Number(stunde),
|
|
58
|
+
minute: Number(minute),
|
|
59
|
+
second: Number(sekunde),
|
|
60
|
+
millisecond: bruchteil ? Number(bruchteil.padEnd(3, '0').slice(0, 3)) : 0,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Echter Zeitpunkt -> Wiener Wanduhrzeit (Gegenstueck zu
|
|
65
|
+
* [parseServerTimeStamp]). Der Versatz kommt aus derselben Sommerzeitregel wie
|
|
66
|
+
* die Hinrichtung; hier ist er direkt anwendbar, weil der Zeitpunkt schon
|
|
67
|
+
* feststeht.
|
|
68
|
+
*/
|
|
69
|
+
function toViennaWallClock(zeitpunkt) {
|
|
70
|
+
const utcMs = zeitpunkt.getTime();
|
|
71
|
+
// Ein Invalid Date wuerde hier lautlos zu NaN in jedem Feld — und damit zu
|
|
72
|
+
// einem NaN-Berichtsmonat beim Aufrufer.
|
|
73
|
+
if (Number.isNaN(utcMs)) {
|
|
74
|
+
throw new Error('Zeitpunkt: unbrauchbarer Wert (Invalid Date)');
|
|
75
|
+
}
|
|
76
|
+
const versatzMs = (istSommerzeit(utcMs) ? 2 : 1) * STUNDE_MS;
|
|
77
|
+
const verschoben = new Date(utcMs + versatzMs);
|
|
78
|
+
return {
|
|
79
|
+
year: verschoben.getUTCFullYear(),
|
|
80
|
+
month: verschoben.getUTCMonth() + 1,
|
|
81
|
+
day: verschoben.getUTCDate(),
|
|
82
|
+
hour: verschoben.getUTCHours(),
|
|
83
|
+
minute: verschoben.getUTCMinutes(),
|
|
84
|
+
second: verschoben.getUTCSeconds(),
|
|
85
|
+
millisecond: verschoben.getUTCMilliseconds(),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Wiener Wanduhrzeit (Ziffern wie angezeigt) -> echter Zeitpunkt (UTC). */
|
|
89
|
+
function fromViennaWallClock(wand) {
|
|
90
|
+
const basisUtcMs = Date.UTC(wand.year, wand.month - 1, wand.day, wand.hour, wand.minute, wand.second, wand.millisecond);
|
|
91
|
+
// Erst Sommerzeit annehmen; stimmt die Annahme fuer den resultierenden
|
|
92
|
+
// Zeitpunkt, war sie richtig — sonst gilt Winterzeit (wie im Dart-Vorbild).
|
|
93
|
+
const sommerVariante = basisUtcMs - 2 * STUNDE_MS;
|
|
94
|
+
if (istSommerzeit(sommerVariante)) {
|
|
95
|
+
return new Date(sommerVariante);
|
|
96
|
+
}
|
|
97
|
+
return new Date(basisUtcMs - 1 * STUNDE_MS);
|
|
98
|
+
}
|
|
99
|
+
/** EU-Sommerzeit: letzter Sonntag im Maerz 01:00 UTC bis letzter Sonntag im Oktober 01:00 UTC. */
|
|
100
|
+
function istSommerzeit(utcMs) {
|
|
101
|
+
const jahr = new Date(utcMs).getUTCFullYear();
|
|
102
|
+
const start = letzterSonntagUtcMs(jahr, 3);
|
|
103
|
+
const ende = letzterSonntagUtcMs(jahr, 10);
|
|
104
|
+
return utcMs >= start && utcMs < ende;
|
|
105
|
+
}
|
|
106
|
+
/** Letzter Sonntag des Monats, 01:00 UTC (EU-Umstellungszeitpunkt). */
|
|
107
|
+
function letzterSonntagUtcMs(jahr, monat) {
|
|
108
|
+
const letzterTag = new Date(Date.UTC(jahr, monat, 0)).getUTCDate();
|
|
109
|
+
// JS-Wochentag (0=Sonntag..6=Samstag) ist bereits die Anzahl Tage seit dem
|
|
110
|
+
// letzten Sonntag — keine Umrechnung noetig (anders als Darts weekday, das
|
|
111
|
+
// Montag=1..Sonntag=7 zaehlt und dafuer `% 7` braucht).
|
|
112
|
+
const wochentag = new Date(Date.UTC(jahr, monat - 1, letzterTag)).getUTCDay();
|
|
113
|
+
return Date.UTC(jahr, monat - 1, letzterTag - wochentag, 1, 0, 0, 0);
|
|
114
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Cashregister, HobexReceipt, Receipt, ReportMonth, StripeUrlSession } from '../models/index.js';
|
|
2
|
+
import { type CreateStripeLinkOptions, type StripeCaptureResult } from '../payments/stripe.js';
|
|
3
|
+
import { type HobexPayOptions, type HobexRefundOptions } from '../payments/hobex.js';
|
|
4
|
+
import { type TransportOptions } from './transport.js';
|
|
5
|
+
import { type CashboxStatus, type SignatureStatus } from './status.js';
|
|
6
|
+
import { type ListMyReceiptsOptions, type ReceiptList, type ReceiptWithCompany, type SellReceiptOptions, type CancelReceiptOptions, type CancelReceiptResult, type CreateCancelReceiptOptions } from './receipts.js';
|
|
7
|
+
/**
|
|
8
|
+
* Schlichte Factory ueber den Endpunkt-Funktionen: bindet einen Transport
|
|
9
|
+
* einmal und liefert die Aufrufe ohne den wiederkehrenden ersten Parameter.
|
|
10
|
+
* Bewusst **keine** Klasse und keine Vererbung — die Aufrufe sind freie
|
|
11
|
+
* Funktionen (siehe receipts.ts) und bleiben einzeln importierbar; wer sie
|
|
12
|
+
* lieber einzeln nimmt, verliert nichts.
|
|
13
|
+
*
|
|
14
|
+
* Hier stehen **alle** Endpunkt-Aufrufe des Pakets: Belege, Berichte, Status,
|
|
15
|
+
* Zahlungen und die beiden Sitzungs-Aufrufe der Browser-Kasse. Was kein
|
|
16
|
+
* Endpunkt-Aufruf ist (Druck, Beleg-Darstellung), gehoert nicht hierher. Der
|
|
17
|
+
* Grund fuer die Vollstaendigkeit ist der Zweck der Fassade: ein **einziger**
|
|
18
|
+
* Konstruktionsweg je Anmeldung. Fehlte eine Endpunkt-Familie, muesste ein
|
|
19
|
+
* Aufrufer fuer sie zusaetzlich [createTransport] mit denselben Optionen bauen
|
|
20
|
+
* und haette zwei Wege fuer dieselbe Anmeldung nebeneinander.
|
|
21
|
+
*
|
|
22
|
+
* **Eine Ausnahme, und sie folgt aus genau diesem Grund:** Kopplung,
|
|
23
|
+
* Benutzerliste des Geraets und PIN-Anmeldung (`@kreiseck/kasseneck-api/register`)
|
|
24
|
+
* stehen **nicht** hier. Diese Fassade wird mit einer Anmeldung gebaut, jene
|
|
25
|
+
* drei laufen aber, **bevor** es eine gibt. Stuenden sie darin, sagte die
|
|
26
|
+
* Fassade eine Anmeldung zu, die fuer sie nicht gilt: ein Aufrufer riefe
|
|
27
|
+
* `pairRegisterDevice` an einer mit `apiKeyAuth` gebauten Fassade und glaubte,
|
|
28
|
+
* der Schluessel haette dabei etwas zu sagen. Sie bringen ihre
|
|
29
|
+
* Verbindungsangaben stattdessen selbst mit.
|
|
30
|
+
*/
|
|
31
|
+
export interface KasseneckApi {
|
|
32
|
+
/** Normalbeleg (Verkauf). */
|
|
33
|
+
sellReceipt(options: SellReceiptOptions): Promise<Receipt>;
|
|
34
|
+
/** Normalbeleg samt Firmen-/Druckdaten fuer den Belegdruck. */
|
|
35
|
+
sellReceiptWithCompany(options: SellReceiptOptions): Promise<ReceiptWithCompany>;
|
|
36
|
+
/** Storno eines bestehenden Belegs — voll oder in Teilen, ueber den Storno-Endpunkt. */
|
|
37
|
+
cancelReceipt(options: CancelReceiptOptions): Promise<CancelReceiptResult>;
|
|
38
|
+
/** Storno aus frei uebergebenen Positionen. */
|
|
39
|
+
createCancelReceipt(options: CreateCancelReceiptOptions): Promise<Receipt>;
|
|
40
|
+
/** Nullbeleg (RKSV-Pruefbeleg). */
|
|
41
|
+
zeroReceipt(): Promise<Receipt>;
|
|
42
|
+
/** Einzelnen Beleg holen. */
|
|
43
|
+
getReceipt(receiptId: string): Promise<Receipt>;
|
|
44
|
+
/** Einzelnen Beleg samt Firmen-/Druckdaten holen. */
|
|
45
|
+
getReceiptWithCompany(receiptId: string): Promise<ReceiptWithCompany>;
|
|
46
|
+
/** Verschluesselte Volltext-Belegnummer erzeugen. */
|
|
47
|
+
generateFullReceiptId(receiptId: string): Promise<string>;
|
|
48
|
+
/** Berichtsmonat des ersten Belegs (nicht fuer den Kassen-Benutzer-Weg). */
|
|
49
|
+
getFirstReceiptDate(): Promise<ReportMonth>;
|
|
50
|
+
/** Belegliste einer Kasse samt Kennzahlen (nur mit ID-Token, siehe receipts.ts). */
|
|
51
|
+
listMyReceipts(options: ListMyReceiptsOptions): Promise<ReceiptList>;
|
|
52
|
+
/** Kassen des angemeldeten Benutzers (nur mit ID-Token, siehe cashregisters.ts). */
|
|
53
|
+
listMyCashregisters(): Promise<Cashregister[]>;
|
|
54
|
+
/** Tagesbericht als PDF (Kalendertag nach Wiener Zeit). */
|
|
55
|
+
downloadDailyReport(date: Date): Promise<Uint8Array>;
|
|
56
|
+
/** Monatsbericht als PDF (Endpunkt `downloadReport`). */
|
|
57
|
+
downloadMonthlyReport(reportMonth: ReportMonth): Promise<Uint8Array>;
|
|
58
|
+
/** Betriebsstatus der Kasse bei FinanzOnline. */
|
|
59
|
+
getCashboxStatus(): Promise<CashboxStatus>;
|
|
60
|
+
/** Status der Signatureinheit bei FinanzOnline. */
|
|
61
|
+
getSignatureStatus(zertifikatNrHex: string): Promise<SignatureStatus>;
|
|
62
|
+
/** Stripe-Zahlungslink erzeugen (Endpunkt `createPaymentLinkStripe`). */
|
|
63
|
+
createStripeLink(options: CreateStripeLinkOptions): Promise<StripeUrlSession>;
|
|
64
|
+
/** Reservierte Stripe-Zahlung einziehen. */
|
|
65
|
+
stripeCaptureIntent(stripeSessionId: string): Promise<StripeCaptureResult>;
|
|
66
|
+
/** Karte ueber die Hobex-Cloud belasten. */
|
|
67
|
+
hobexPay(options: HobexPayOptions): Promise<HobexReceipt>;
|
|
68
|
+
/** Hobex-Cloud-Zahlung erstatten (ohne Rueckgabewert, siehe payments/hobex.ts). */
|
|
69
|
+
hobexRefund(options: HobexRefundOptions): Promise<void>;
|
|
70
|
+
/** Kassen-Sitzung verlaengern; liefert den neuen Ablauf (nur Kassen-Benutzer). */
|
|
71
|
+
renewRegisterSession(): Promise<number>;
|
|
72
|
+
/** Kassen-Sitzung beenden und den Lizenzplatz freigeben (nur Kassen-Benutzer). */
|
|
73
|
+
endRegisterSession(): Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
export declare function createKasseneckApi(options: TransportOptions): KasseneckApi;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createStripeLink, stripeCaptureIntent, } from '../payments/stripe.js';
|
|
2
|
+
import { hobexPay, hobexRefund } from '../payments/hobex.js';
|
|
3
|
+
import { createTransport, createBinaryTransport } from './transport.js';
|
|
4
|
+
import { downloadDailyReport, downloadMonthlyReport } from './reports.js';
|
|
5
|
+
import { getCashboxStatus, getSignatureStatus } from './status.js';
|
|
6
|
+
import { sellReceipt, sellReceiptWithCompany, cancelReceipt, createCancelReceipt, zeroReceipt, getReceipt, getReceiptWithCompany, generateFullReceiptId, getFirstReceiptDate, listMyReceipts, } from './receipts.js';
|
|
7
|
+
import { listMyCashregisters } from './cashregisters.js';
|
|
8
|
+
import { renewRegisterSession, endRegisterSession } from '../register/session.js';
|
|
9
|
+
export function createKasseneckApi(options) {
|
|
10
|
+
const rufen = createTransport(options);
|
|
11
|
+
// Zweiter Einstiegspunkt fuer die PDF-Downloads; dieselben Optionen,
|
|
12
|
+
// dieselbe Anmeldung — nur die Antwort wird als Bytes gelesen.
|
|
13
|
+
const rufenBinaer = createBinaryTransport(options);
|
|
14
|
+
return {
|
|
15
|
+
sellReceipt: (o) => sellReceipt(rufen, o),
|
|
16
|
+
sellReceiptWithCompany: (o) => sellReceiptWithCompany(rufen, o),
|
|
17
|
+
cancelReceipt: (o) => cancelReceipt(rufen, o),
|
|
18
|
+
createCancelReceipt: (o) => createCancelReceipt(rufen, o),
|
|
19
|
+
zeroReceipt: () => zeroReceipt(rufen),
|
|
20
|
+
getReceipt: (receiptId) => getReceipt(rufen, receiptId),
|
|
21
|
+
getReceiptWithCompany: (receiptId) => getReceiptWithCompany(rufen, receiptId),
|
|
22
|
+
generateFullReceiptId: (receiptId) => generateFullReceiptId(rufen, receiptId),
|
|
23
|
+
getFirstReceiptDate: () => getFirstReceiptDate(rufen),
|
|
24
|
+
listMyReceipts: (o) => listMyReceipts(rufen, o),
|
|
25
|
+
listMyCashregisters: () => listMyCashregisters(rufen),
|
|
26
|
+
downloadDailyReport: (date) => downloadDailyReport(rufenBinaer, date),
|
|
27
|
+
downloadMonthlyReport: (reportMonth) => downloadMonthlyReport(rufenBinaer, reportMonth),
|
|
28
|
+
getCashboxStatus: () => getCashboxStatus(rufen),
|
|
29
|
+
getSignatureStatus: (zertifikatNrHex) => getSignatureStatus(rufen, zertifikatNrHex),
|
|
30
|
+
createStripeLink: (o) => createStripeLink(rufen, o),
|
|
31
|
+
stripeCaptureIntent: (stripeSessionId) => stripeCaptureIntent(rufen, stripeSessionId),
|
|
32
|
+
hobexPay: (o) => hobexPay(rufen, o),
|
|
33
|
+
hobexRefund: (o) => hobexRefund(rufen, o),
|
|
34
|
+
renewRegisterSession: () => renewRegisterSession(rufen),
|
|
35
|
+
endRegisterSession: () => endRegisterSession(rufen),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Die zwei Anmeldewege ans Kasseneck-Backend — gleichrangig, austauschbar,
|
|
3
|
+
* keiner ist der bevorzugte:
|
|
4
|
+
*
|
|
5
|
+
* - `apiKeyAuth` — der Weg der Geraete, POS-Apps und Dritten: der `api_key` als
|
|
6
|
+
* Bearer plus die Kopfzeile `cashregister-token` (wie im Flutter-Zwilling
|
|
7
|
+
* `kasseneck_api`).
|
|
8
|
+
* - `registerUserAuth` — der Weg der Browser-Kasse: ein Kassen-Benutzer, per
|
|
9
|
+
* PIN an einem gekoppelten Geraet angemeldet. Firebase-ID-Token als Bearer,
|
|
10
|
+
* die laufende Sitzung als Kopfzeile `register-session`, die Kasse als
|
|
11
|
+
* Parameter `cashregisterId` in der Nutzlast.
|
|
12
|
+
*
|
|
13
|
+
* Eine Anmeldung liefert **nur** Kopfzeilen und ggf. Zusatzparameter. Sie
|
|
14
|
+
* fuehrt keine eigenen HTTP-Aufrufe, kennt den Transport nicht und haelt keinen
|
|
15
|
+
* Zustand — genau deshalb ist sie austauschbar und einzeln testbar.
|
|
16
|
+
*
|
|
17
|
+
* Das Paket weiss **nichts** von Firebase: `registerUserAuth` bekommt eine
|
|
18
|
+
* Funktion, die ein gueltiges ID-Token liefert (darf `async` sein). Sie wird
|
|
19
|
+
* bei **jedem** Aufruf befragt — Firebase-ID-Tokens laufen nach einer Stunde
|
|
20
|
+
* ab, die Sitzung der Browser-Kasse lebt sogar nur 90 Sekunden und wird alle
|
|
21
|
+
* 30 Sekunden erneuert; ein einmal gemerkter Wert waere also bald tot.
|
|
22
|
+
*/
|
|
23
|
+
/** Was eine Anmeldung zu einer Anfrage beisteuert. */
|
|
24
|
+
export interface AuthCredentials {
|
|
25
|
+
/** Kopfzeilen der Anfrage (mindestens `Authorization`). */
|
|
26
|
+
headers: Record<string, string>;
|
|
27
|
+
/** Zusatzparameter, die in die Nutzlast wandern (z. B. `cashregisterId`). */
|
|
28
|
+
params: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Eine Anmeldung ist eine Funktion, die pro Anfrage frische Zugangsdaten
|
|
32
|
+
* liefert (synchron oder asynchron).
|
|
33
|
+
*/
|
|
34
|
+
export type KasseneckAuth = () => AuthCredentials | Promise<AuthCredentials>;
|
|
35
|
+
export interface ApiKeyAuthOptions {
|
|
36
|
+
/** API-Schluessel im Format `kr_<env>_<...>` (opak). */
|
|
37
|
+
apiKey: string;
|
|
38
|
+
/** Kassen-Token der Kasse, an der gearbeitet wird (`cb_<env>_<...>`). */
|
|
39
|
+
cashregisterToken: string;
|
|
40
|
+
}
|
|
41
|
+
export interface RegisterUserAuthOptions {
|
|
42
|
+
/** Liefert ein gueltiges Firebase-ID-Token; wird pro Anfrage befragt. */
|
|
43
|
+
getIdToken: () => string | Promise<string>;
|
|
44
|
+
/** Liefert die laufende Kassen-Sitzung; wird pro Anfrage befragt. */
|
|
45
|
+
getSessionId: () => string | Promise<string>;
|
|
46
|
+
/** Kasse, an der der Kassen-Benutzer angemeldet ist. */
|
|
47
|
+
cashregisterId: string;
|
|
48
|
+
}
|
|
49
|
+
/** Anmeldung per API-Schluessel und Kassen-Token (Geraete, POS-Apps, Dritte). */
|
|
50
|
+
export declare function apiKeyAuth(options: ApiKeyAuthOptions): KasseneckAuth;
|
|
51
|
+
/** Anmeldung eines Kassen-Benutzers der Browser-Kasse (ID-Token + Sitzung). */
|
|
52
|
+
export declare function registerUserAuth(options: RegisterUserAuthOptions): KasseneckAuth;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Die zwei Anmeldewege ans Kasseneck-Backend — gleichrangig, austauschbar,
|
|
3
|
+
* keiner ist der bevorzugte:
|
|
4
|
+
*
|
|
5
|
+
* - `apiKeyAuth` — der Weg der Geraete, POS-Apps und Dritten: der `api_key` als
|
|
6
|
+
* Bearer plus die Kopfzeile `cashregister-token` (wie im Flutter-Zwilling
|
|
7
|
+
* `kasseneck_api`).
|
|
8
|
+
* - `registerUserAuth` — der Weg der Browser-Kasse: ein Kassen-Benutzer, per
|
|
9
|
+
* PIN an einem gekoppelten Geraet angemeldet. Firebase-ID-Token als Bearer,
|
|
10
|
+
* die laufende Sitzung als Kopfzeile `register-session`, die Kasse als
|
|
11
|
+
* Parameter `cashregisterId` in der Nutzlast.
|
|
12
|
+
*
|
|
13
|
+
* Eine Anmeldung liefert **nur** Kopfzeilen und ggf. Zusatzparameter. Sie
|
|
14
|
+
* fuehrt keine eigenen HTTP-Aufrufe, kennt den Transport nicht und haelt keinen
|
|
15
|
+
* Zustand — genau deshalb ist sie austauschbar und einzeln testbar.
|
|
16
|
+
*
|
|
17
|
+
* Das Paket weiss **nichts** von Firebase: `registerUserAuth` bekommt eine
|
|
18
|
+
* Funktion, die ein gueltiges ID-Token liefert (darf `async` sein). Sie wird
|
|
19
|
+
* bei **jedem** Aufruf befragt — Firebase-ID-Tokens laufen nach einer Stunde
|
|
20
|
+
* ab, die Sitzung der Browser-Kasse lebt sogar nur 90 Sekunden und wird alle
|
|
21
|
+
* 30 Sekunden erneuert; ein einmal gemerkter Wert waere also bald tot.
|
|
22
|
+
*/
|
|
23
|
+
import { KasseneckAuthError } from './errors.js';
|
|
24
|
+
/** Anmeldung per API-Schluessel und Kassen-Token (Geraete, POS-Apps, Dritte). */
|
|
25
|
+
export function apiKeyAuth(options) {
|
|
26
|
+
// Fehlende Zugangsdaten fallen hier auf und nicht erst als 401 vom Backend.
|
|
27
|
+
// Die Meldung nennt nur das leere Feld, nie einen Wert.
|
|
28
|
+
if (!options.apiKey) {
|
|
29
|
+
throw new KasseneckAuthError('apiKeyAuth: apiKey fehlt');
|
|
30
|
+
}
|
|
31
|
+
if (!options.cashregisterToken) {
|
|
32
|
+
throw new KasseneckAuthError('apiKeyAuth: cashregisterToken fehlt');
|
|
33
|
+
}
|
|
34
|
+
const { apiKey, cashregisterToken } = options;
|
|
35
|
+
// Pro Aufruf ein frisches Objekt: der Transport (oder ein Aufrufer) darf
|
|
36
|
+
// daran herumschreiben, ohne die naechste Anfrage zu vergiften.
|
|
37
|
+
return () => ({
|
|
38
|
+
headers: {
|
|
39
|
+
Authorization: `Bearer ${apiKey}`,
|
|
40
|
+
'cashregister-token': cashregisterToken,
|
|
41
|
+
},
|
|
42
|
+
params: {},
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** Anmeldung eines Kassen-Benutzers der Browser-Kasse (ID-Token + Sitzung). */
|
|
46
|
+
export function registerUserAuth(options) {
|
|
47
|
+
if (!options.cashregisterId) {
|
|
48
|
+
throw new KasseneckAuthError('registerUserAuth: cashregisterId fehlt');
|
|
49
|
+
}
|
|
50
|
+
const { getIdToken, getSessionId, cashregisterId } = options;
|
|
51
|
+
return async () => {
|
|
52
|
+
// Beides bei JEDEM Aufruf frisch — siehe Modulkommentar (Ablaufzeiten).
|
|
53
|
+
const [idToken, sessionId] = await Promise.all([getIdToken(), getSessionId()]);
|
|
54
|
+
if (!idToken) {
|
|
55
|
+
throw new KasseneckAuthError('registerUserAuth: getIdToken lieferte kein Token');
|
|
56
|
+
}
|
|
57
|
+
if (!sessionId) {
|
|
58
|
+
throw new KasseneckAuthError('registerUserAuth: getSessionId lieferte keine Sitzung');
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
headers: {
|
|
62
|
+
Authorization: `Bearer ${idToken}`,
|
|
63
|
+
'register-session': sessionId,
|
|
64
|
+
},
|
|
65
|
+
params: { cashregisterId },
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type Cashregister } from '../models/index.js';
|
|
2
|
+
import type { KasseneckTransport } from './transport.js';
|
|
3
|
+
/**
|
|
4
|
+
* Kassen-Endpunkte.
|
|
5
|
+
*
|
|
6
|
+
* **Anmeldeweg:** `listMyCashregisters` laeuft im Backend unter
|
|
7
|
+
* `checkRequest(req, 'customer', …, {allowRegisterUser: true})` — der Bearer
|
|
8
|
+
* muss also ein Firebase-ID-Token sein. Mit `apiKeyAuth` (api_key als Bearer)
|
|
9
|
+
* ist dieser Endpunkt **nicht** erreichbar; fuer die Browser-Kasse ist
|
|
10
|
+
* `registerUserAuth` der Weg. Dieses Paket bildet das nicht nach — wer darf,
|
|
11
|
+
* entscheidet allein das Backend. Der Hinweis steht hier, damit ein Leser
|
|
12
|
+
* nicht raten muss.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Die Kassen, die dem angemeldeten Benutzer offenstehen — fuer einen
|
|
16
|
+
* Kassen-Benutzer genau die ihm zugewiesenen (das Backend filtert, siehe
|
|
17
|
+
* `registerAuth.darfKasse`).
|
|
18
|
+
*
|
|
19
|
+
* Ohne diesen Aufruf kann eine Browser-Kasse ihre Kasse nicht auswaehlen.
|
|
20
|
+
*/
|
|
21
|
+
export declare function listMyCashregisters(rufen: KasseneckTransport): Promise<Cashregister[]>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { fromCashregisterPayload } from '../models/index.js';
|
|
2
|
+
import { KasseneckValidationError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Kassen-Endpunkte.
|
|
5
|
+
*
|
|
6
|
+
* **Anmeldeweg:** `listMyCashregisters` laeuft im Backend unter
|
|
7
|
+
* `checkRequest(req, 'customer', …, {allowRegisterUser: true})` — der Bearer
|
|
8
|
+
* muss also ein Firebase-ID-Token sein. Mit `apiKeyAuth` (api_key als Bearer)
|
|
9
|
+
* ist dieser Endpunkt **nicht** erreichbar; fuer die Browser-Kasse ist
|
|
10
|
+
* `registerUserAuth` der Weg. Dieses Paket bildet das nicht nach — wer darf,
|
|
11
|
+
* entscheidet allein das Backend. Der Hinweis steht hier, damit ein Leser
|
|
12
|
+
* nicht raten muss.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Die Kassen, die dem angemeldeten Benutzer offenstehen — fuer einen
|
|
16
|
+
* Kassen-Benutzer genau die ihm zugewiesenen (das Backend filtert, siehe
|
|
17
|
+
* `registerAuth.darfKasse`).
|
|
18
|
+
*
|
|
19
|
+
* Ohne diesen Aufruf kann eine Browser-Kasse ihre Kasse nicht auswaehlen.
|
|
20
|
+
*/
|
|
21
|
+
export async function listMyCashregisters(rufen) {
|
|
22
|
+
const daten = await rufen('listMyCashregisters');
|
|
23
|
+
const liste = daten?.cashregisters;
|
|
24
|
+
if (!Array.isArray(liste)) {
|
|
25
|
+
throw new KasseneckValidationError('listMyCashregisters', 'Antwort enthaelt keine Kassenliste (data.cashregisters fehlt)', 'response');
|
|
26
|
+
}
|
|
27
|
+
return liste.map((eintrag, i) => {
|
|
28
|
+
const nutzlast = (typeof eintrag === 'object' && eintrag !== null ? eintrag : {});
|
|
29
|
+
// Die Dokument-ID steht in dieser Antwort im Eintrag selbst; der zweite
|
|
30
|
+
// Parameter ist nur der Rueckfall (siehe fromCashregisterPayload).
|
|
31
|
+
return fromCashregisterPayload(nutzlast, nutzlast.id ?? String(i));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Die fuenf Fehlerarten des Kasseneck-Clients — bewusst getrennt, weil ein
|
|
3
|
+
* Aufrufer auf jede anders reagieren muss:
|
|
4
|
+
*
|
|
5
|
+
* - `KasseneckValidationError` — die **Form** stimmt nicht: entweder ging
|
|
6
|
+
* mangels brauchbarer Eingabe gar keine Anfrage raus, oder die Antwort war
|
|
7
|
+
* nicht verwertbar. `scope` trennt die beiden Richtungen. `'request'`: die
|
|
8
|
+
* Eingabe des Aufrufers verletzt eine Regel, die dieses Paket schon vor dem
|
|
9
|
+
* Senden kennt (Beleg ohne Positionen, unmoegliche Gutscheinkombination) —
|
|
10
|
+
* das ist ein Programmierfehler im Aufrufer, kein Betriebszustand.
|
|
11
|
+
* `'response'`: das Backend meldete Erfolg, die Nutzlast enthielt aber
|
|
12
|
+
* nicht, was der Aufruf zusagt.
|
|
13
|
+
*
|
|
14
|
+
* - `KasseneckApiError` — **fachlicher** Fehler. Das Backend antwortet immer
|
|
15
|
+
* mit HTTP 200 und legt Erfolg/Misserfolg in den Rumpf
|
|
16
|
+
* (`{status:'success'|'error', message, data}`, siehe `successResponse`/
|
|
17
|
+
* `errorResponse` im Backend). Ein `status:'error'` heisst: die Anfrage kam
|
|
18
|
+
* an und wurde abgelehnt (gesperrte Kasse, fehlendes Modul, ungueltiger
|
|
19
|
+
* Parameter). Wiederholen hilft nicht.
|
|
20
|
+
* - `KasseneckHttpError` — die Antwort war **keine** verwertbare Huelle:
|
|
21
|
+
* HTTP 500/404, leerer Rumpf, oder HTTP 200 mit HTML statt JSON (typisch,
|
|
22
|
+
* wenn ein Aufruf mangels Rewrite auf der Single-Page-App landet). Beim
|
|
23
|
+
* Bericht-Download gelten dieselben Gruende fuer alles, was kein PDF ist.
|
|
24
|
+
* `reason` trennt die Faelle maschinenlesbar.
|
|
25
|
+
* - `KasseneckNetworkError` — die Antwort kam gar nicht: Netz weg, DNS,
|
|
26
|
+
* abgebrochene Verbindung oder Zeitueberschreitung (`timedOut`).
|
|
27
|
+
* - `KasseneckAuthError` — es kam nicht einmal zur Anfrage, weil die Anmeldung
|
|
28
|
+
* scheiterte (fehlende Zugangsdaten, oder der Token-/Sitzungsgeber warf).
|
|
29
|
+
* In der Browser-Kasse mit ihrer 90-Sekunden-Sitzung ist das Alltag, kein
|
|
30
|
+
* Sonderfall.
|
|
31
|
+
*
|
|
32
|
+
* **Geheimnisse gehoeren in keinen dieser Fehler.** Fehlermeldungen landen in
|
|
33
|
+
* Protokollen und Fehlerdiensten; weder `api_key`, Kassen-Token, Firebase-
|
|
34
|
+
* ID-Token noch Sitzungsbezeichner duerfen dorthin. Deshalb tragen die Fehler
|
|
35
|
+
* ausschliesslich Funktionsname, HTTP-Status/Inhaltstyp, den vom Paket
|
|
36
|
+
* formulierten Grund und die vom Backend formulierte Meldung — nie Kopfzeilen,
|
|
37
|
+
* nie den gesendeten Rumpf und auch nicht den empfangenen Rumpf (der koennte
|
|
38
|
+
* bei einem fremden Proxy alles Moegliche zurueckspiegeln).
|
|
39
|
+
*
|
|
40
|
+
* Aus demselben Grund haengt **keine fremde Ursache** als `cause` an diesen
|
|
41
|
+
* Fehlern: `console.error(err)` und `util.inspect` drucken die Ursachenkette
|
|
42
|
+
* mit, und fremde HTTP-Bibliotheken haengen ihre Anfrage an ihre Fehler (axios
|
|
43
|
+
* `config.headers`, got `options.headers`) — mit dem Bearer-Schluessel darin.
|
|
44
|
+
* Statt der Ursache selbst traegt `KasseneckNetworkError` ihre **verdichtete**
|
|
45
|
+
* Form: `causeName`/`causeCode`, beide nur, wenn sie wie ein Bezeichner
|
|
46
|
+
* aussehen **und** mit keinem der gesendeten Geheimnisse ueberlappen (siehe
|
|
47
|
+
* `causeDigest`).
|
|
48
|
+
*
|
|
49
|
+
* Der Formfilter allein traegt diese Zusage naemlich nicht: der
|
|
50
|
+
* Sitzungsbezeichner der Browser-Kasse ist selbst bezeichner-foermig und kaeme
|
|
51
|
+
* durch. Verdichtet werden darf nur, wo die gesendeten Geheimnisse bekannt
|
|
52
|
+
* sind — also nach dem Bauen der Anfrage. `KasseneckAuthError` traegt darum
|
|
53
|
+
* **gar keine** Ursachen-Verdichtung: dort ist noch nichts gesendet, es gibt
|
|
54
|
+
* keine Liste zum Abgleichen, und Diagnose ist an dieser Stelle weniger wert
|
|
55
|
+
* als die Zusage.
|
|
56
|
+
*/
|
|
57
|
+
/** Verdichtete, geheimnisfreie Form einer fremden Fehlerursache. */
|
|
58
|
+
export interface CauseDigest {
|
|
59
|
+
causeName?: string | undefined;
|
|
60
|
+
causeCode?: string | undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Reduziert eine fremde Ursache auf Name und Code — und verwirft beides, wenn
|
|
64
|
+
* es kein Bezeichner ist oder mit einem der uebergebenen Geheimnisse
|
|
65
|
+
* ueberlappt (ein Sitzungsbezeichner sieht durchaus bezeichner-artig aus).
|
|
66
|
+
*
|
|
67
|
+
* `geheimnisse` hat bewusst **keinen** Vorgabewert: eine Verdichtung ohne
|
|
68
|
+
* Abgleichliste waere nur ein Formfilter und damit keine Zusage. Wo die
|
|
69
|
+
* gesendeten Werte nicht bekannt sind, wird gar nicht verdichtet.
|
|
70
|
+
*/
|
|
71
|
+
export declare function causeDigest(ursache: unknown, geheimnisse: readonly string[]): CauseDigest;
|
|
72
|
+
/** Fachlicher Fehler: HTTP 200, aber `status: 'error'` im Rumpf. */
|
|
73
|
+
export declare class KasseneckApiError extends Error {
|
|
74
|
+
readonly name = "KasseneckApiError";
|
|
75
|
+
/** Aufgerufene Backend-Funktion, z. B. `createReceipt`. */
|
|
76
|
+
readonly functionName: string;
|
|
77
|
+
/** Meldung des Backends, unveraendert (`message` aus der Huelle). */
|
|
78
|
+
readonly serverMessage: string;
|
|
79
|
+
constructor(functionName: string, serverMessage: string);
|
|
80
|
+
}
|
|
81
|
+
/** Warum die Antwort keine verwertbare Huelle war. */
|
|
82
|
+
export type HttpFailureReason = 'server-error' | 'empty-body' | 'not-json' | 'missing-status';
|
|
83
|
+
/** Antwort ohne verwertbare Huelle (HTTP-Fehler, leerer Rumpf, HTML statt JSON). */
|
|
84
|
+
export declare class KasseneckHttpError extends Error {
|
|
85
|
+
readonly name = "KasseneckHttpError";
|
|
86
|
+
readonly functionName: string;
|
|
87
|
+
/** HTTP-Statuscode der Antwort (bei HTML-statt-JSON durchaus 200). */
|
|
88
|
+
readonly statusCode: number;
|
|
89
|
+
/** Inhaltstyp der Antwort, falls die Gegenstelle einen gesetzt hat. */
|
|
90
|
+
readonly contentType: string | undefined;
|
|
91
|
+
/** Maschinenlesbarer Grund — trennt den Rewrite-Fall vom 500er ohne Textparsen. */
|
|
92
|
+
readonly reason: HttpFailureReason;
|
|
93
|
+
constructor(functionName: string, statusCode: number, contentType: string | undefined, reason: HttpFailureReason);
|
|
94
|
+
}
|
|
95
|
+
/** Es kam keine Antwort: Netzfehler oder Zeitueberschreitung. */
|
|
96
|
+
export declare class KasseneckNetworkError extends Error {
|
|
97
|
+
readonly name = "KasseneckNetworkError";
|
|
98
|
+
readonly functionName: string;
|
|
99
|
+
/** true = das Zeitlimit lief ab und die Anfrage wurde abgebrochen. */
|
|
100
|
+
readonly timedOut: boolean;
|
|
101
|
+
/** Das geltende Zeitlimit in Millisekunden. */
|
|
102
|
+
readonly timeoutMs: number;
|
|
103
|
+
/** Name der zugrunde liegenden Ursache, sofern unbedenklich (s. `causeDigest`). */
|
|
104
|
+
readonly causeName: string | undefined;
|
|
105
|
+
/** Code der zugrunde liegenden Ursache, sofern unbedenklich (z. B. `ECONNREFUSED`). */
|
|
106
|
+
readonly causeCode: string | undefined;
|
|
107
|
+
constructor(functionName: string, timedOut: boolean, timeoutMs: number, ursache?: CauseDigest);
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Die Anmeldung scheiterte — es ging keine Anfrage raus.
|
|
111
|
+
*
|
|
112
|
+
* Dieser Fehler traegt **nichts** aus der fremden Ursache: weder ihre Meldung
|
|
113
|
+
* (ein Token-Geber fuehrt gern den Token mit, den er gerade nicht erneuern
|
|
114
|
+
* konnte) noch eine Verdichtung ihres Namens/Codes. Zum Zeitpunkt des
|
|
115
|
+
* Anmeldefehlers ist noch nichts gesendet, es gibt also keine Liste der
|
|
116
|
+
* Geheimnisse, gegen die verdichtete Werte geprueft werden koennten — und ein
|
|
117
|
+
* reiner Formfilter laesst genau die Form durch, die ein Sitzungsbezeichner
|
|
118
|
+
* hat.
|
|
119
|
+
*/
|
|
120
|
+
export declare class KasseneckAuthError extends Error {
|
|
121
|
+
readonly name = "KasseneckAuthError";
|
|
122
|
+
/** Betroffene Backend-Funktion, falls der Fehler bei einem Aufruf entstand. */
|
|
123
|
+
readonly functionName: string | undefined;
|
|
124
|
+
/** Vom Paket formulierter Grund — geheimnisfrei, anders als fremde Meldungen. */
|
|
125
|
+
readonly reason: string;
|
|
126
|
+
constructor(reason: string, options?: {
|
|
127
|
+
functionName?: string;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/** Woran die Form haengt: an der Eingabe des Aufrufers oder an der Antwort. */
|
|
131
|
+
export type ValidationScope = 'request' | 'response';
|
|
132
|
+
/**
|
|
133
|
+
* Die Form stimmt nicht — bei `scope: 'request'` ging deshalb keine Anfrage
|
|
134
|
+
* raus, bei `scope: 'response'` kam eine unbrauchbare zurueck.
|
|
135
|
+
*
|
|
136
|
+
* Es gilt dieselbe Zusage wie fuer die uebrigen Fehlerarten: `reason` ist vom
|
|
137
|
+
* Paket formuliert und geheimnisfrei. Bei `'response'` wird insbesondere
|
|
138
|
+
* **nichts aus der Antwort** uebernommen — ein fremder Proxy koennte dort
|
|
139
|
+
* alles Moegliche zurueckspiegeln.
|
|
140
|
+
*/
|
|
141
|
+
export declare class KasseneckValidationError extends Error {
|
|
142
|
+
readonly name = "KasseneckValidationError";
|
|
143
|
+
/**
|
|
144
|
+
* Betroffener Vorgang — meist die Backend-Funktion, bei Pruefungen ohne
|
|
145
|
+
* Aufruf der Name der Paketfunktion (z. B. `buildReceiptLayout`).
|
|
146
|
+
*/
|
|
147
|
+
readonly functionName: string;
|
|
148
|
+
/** Vom Paket formulierter Grund. */
|
|
149
|
+
readonly reason: string;
|
|
150
|
+
readonly scope: ValidationScope;
|
|
151
|
+
constructor(functionName: string, reason: string, scope: ValidationScope);
|
|
152
|
+
}
|
|
153
|
+
/** Alle Fehler, die dieses Paket wirft. */
|
|
154
|
+
export type KasseneckError = KasseneckApiError | KasseneckHttpError | KasseneckNetworkError | KasseneckAuthError | KasseneckValidationError;
|
|
155
|
+
export declare function isKasseneckApiError(fehler: unknown): fehler is KasseneckApiError;
|
|
156
|
+
export declare function isKasseneckHttpError(fehler: unknown): fehler is KasseneckHttpError;
|
|
157
|
+
export declare function isKasseneckNetworkError(fehler: unknown): fehler is KasseneckNetworkError;
|
|
158
|
+
export declare function isKasseneckAuthError(fehler: unknown): fehler is KasseneckAuthError;
|
|
159
|
+
export declare function isKasseneckValidationError(fehler: unknown): fehler is KasseneckValidationError;
|