@kreiseck/kasseneck-api 0.6.45 → 0.6.49

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.
Files changed (110) hide show
  1. package/README.md +95 -15
  2. package/dist/cjs/client/aufrufe.d.ts +1 -1
  3. package/dist/cjs/client/aufrufe.js +2 -0
  4. package/dist/cjs/client/errors.d.ts +7 -1
  5. package/dist/cjs/client/errors.js +8 -1
  6. package/dist/cjs/client/receipts.d.ts +16 -0
  7. package/dist/cjs/client/receipts.js +10 -0
  8. package/dist/cjs/client/transport.js +5 -4
  9. package/dist/cjs/index.d.ts +1 -1
  10. package/dist/cjs/index.js +4 -2
  11. package/dist/cjs/kasse/index.d.ts +1 -0
  12. package/dist/cjs/kasse/index.js +3 -0
  13. package/dist/cjs/kasse/trinkgeld.d.ts +10 -0
  14. package/dist/cjs/kasse/trinkgeld.js +27 -0
  15. package/dist/cjs/models/cancellation.d.ts +22 -0
  16. package/dist/cjs/models/cancellation.js +31 -1
  17. package/dist/cjs/models/hobex-receipt.js +9 -2
  18. package/dist/cjs/models/index.d.ts +1 -1
  19. package/dist/cjs/models/index.js +3 -1
  20. package/dist/cjs/models/receipt.js +3 -0
  21. package/dist/cjs/models/voucher.js +18 -2
  22. package/dist/cjs/payments/hobex-hps/connect-client.d.ts +104 -0
  23. package/dist/cjs/payments/hobex-hps/connect-client.js +159 -0
  24. package/dist/cjs/payments/hobex-hps/errors.d.ts +122 -0
  25. package/dist/cjs/payments/hobex-hps/errors.js +158 -0
  26. package/dist/cjs/payments/hobex-hps/events.d.ts +22 -0
  27. package/dist/cjs/payments/hobex-hps/events.js +2 -0
  28. package/dist/cjs/payments/hobex-hps/index.d.ts +27 -0
  29. package/dist/cjs/payments/hobex-hps/index.js +66 -0
  30. package/dist/cjs/payments/hobex-hps/outcome.d.ts +31 -0
  31. package/dist/cjs/payments/hobex-hps/outcome.js +7 -0
  32. package/dist/cjs/payments/hobex-hps/payments.d.ts +149 -0
  33. package/dist/cjs/payments/hobex-hps/payments.js +550 -0
  34. package/dist/cjs/payments/hobex-hps/receipt.d.ts +28 -0
  35. package/dist/cjs/payments/hobex-hps/receipt.js +60 -0
  36. package/dist/cjs/payments/hobex-hps/transaction-id.d.ts +65 -0
  37. package/dist/cjs/payments/hobex-hps/transaction-id.js +91 -0
  38. package/dist/cjs/payments/hobex-hps/transaction-response.d.ts +163 -0
  39. package/dist/cjs/payments/hobex-hps/transaction-response.js +245 -0
  40. package/dist/cjs/payments/hobex.js +9 -6
  41. package/dist/cjs/payments/index.d.ts +26 -14
  42. package/dist/cjs/payments/index.js +59 -15
  43. package/dist/cjs/printing/index.d.ts +1 -1
  44. package/dist/cjs/printing/index.js +2 -1
  45. package/dist/cjs/printing/webusb.d.ts +37 -7
  46. package/dist/cjs/printing/webusb.js +76 -18
  47. package/dist/cjs/receipt/epos.d.ts +18 -0
  48. package/dist/cjs/receipt/epos.js +78 -6
  49. package/dist/cjs/receipt/index.d.ts +1 -1
  50. package/dist/cjs/receipt/index.js +2 -1
  51. package/dist/cjs/register/index.d.ts +1 -1
  52. package/dist/cjs/register/index.js +2 -1
  53. package/dist/cjs/register/pairing.d.ts +67 -1
  54. package/dist/cjs/register/pairing.js +59 -3
  55. package/dist/esm/client/aufrufe.d.ts +1 -1
  56. package/dist/esm/client/aufrufe.js +2 -0
  57. package/dist/esm/client/errors.d.ts +7 -1
  58. package/dist/esm/client/errors.js +8 -1
  59. package/dist/esm/client/receipts.d.ts +16 -0
  60. package/dist/esm/client/receipts.js +10 -0
  61. package/dist/esm/client/transport.js +5 -4
  62. package/dist/esm/index.d.ts +1 -1
  63. package/dist/esm/index.js +1 -1
  64. package/dist/esm/kasse/index.d.ts +1 -0
  65. package/dist/esm/kasse/index.js +1 -0
  66. package/dist/esm/kasse/trinkgeld.d.ts +10 -0
  67. package/dist/esm/kasse/trinkgeld.js +24 -0
  68. package/dist/esm/models/cancellation.d.ts +22 -0
  69. package/dist/esm/models/cancellation.js +29 -0
  70. package/dist/esm/models/hobex-receipt.js +10 -3
  71. package/dist/esm/models/index.d.ts +1 -1
  72. package/dist/esm/models/index.js +1 -1
  73. package/dist/esm/models/receipt.js +3 -0
  74. package/dist/esm/models/voucher.js +18 -2
  75. package/dist/esm/payments/hobex-hps/connect-client.d.ts +104 -0
  76. package/dist/esm/payments/hobex-hps/connect-client.js +156 -0
  77. package/dist/esm/payments/hobex-hps/errors.d.ts +122 -0
  78. package/dist/esm/payments/hobex-hps/errors.js +149 -0
  79. package/dist/esm/payments/hobex-hps/events.d.ts +22 -0
  80. package/dist/esm/payments/hobex-hps/events.js +1 -0
  81. package/dist/esm/payments/hobex-hps/index.d.ts +27 -0
  82. package/dist/esm/payments/hobex-hps/index.js +26 -0
  83. package/dist/esm/payments/hobex-hps/outcome.d.ts +31 -0
  84. package/dist/esm/payments/hobex-hps/outcome.js +4 -0
  85. package/dist/esm/payments/hobex-hps/payments.d.ts +149 -0
  86. package/dist/esm/payments/hobex-hps/payments.js +547 -0
  87. package/dist/esm/payments/hobex-hps/receipt.d.ts +28 -0
  88. package/dist/esm/payments/hobex-hps/receipt.js +57 -0
  89. package/dist/esm/payments/hobex-hps/transaction-id.d.ts +65 -0
  90. package/dist/esm/payments/hobex-hps/transaction-id.js +86 -0
  91. package/dist/esm/payments/hobex-hps/transaction-response.d.ts +163 -0
  92. package/dist/esm/payments/hobex-hps/transaction-response.js +233 -0
  93. package/dist/esm/payments/hobex.js +9 -6
  94. package/dist/esm/payments/index.d.ts +26 -14
  95. package/dist/esm/payments/index.js +26 -14
  96. package/dist/esm/printing/index.d.ts +1 -1
  97. package/dist/esm/printing/index.js +1 -1
  98. package/dist/esm/printing/webusb.d.ts +37 -7
  99. package/dist/esm/printing/webusb.js +74 -17
  100. package/dist/esm/receipt/epos.d.ts +18 -0
  101. package/dist/esm/receipt/epos.js +76 -6
  102. package/dist/esm/receipt/index.d.ts +1 -1
  103. package/dist/esm/receipt/index.js +1 -1
  104. package/dist/esm/register/index.d.ts +1 -1
  105. package/dist/esm/register/index.js +1 -1
  106. package/dist/esm/register/pairing.d.ts +67 -1
  107. package/dist/esm/register/pairing.js +58 -3
  108. package/fixtures/hobex-hps-codes.json +67 -0
  109. package/fixtures/oberflaeche.json +3 -1
  110. package/package.json +4 -2
package/README.md CHANGED
@@ -167,6 +167,38 @@ sich nicht stornieren, ein voll stornierter Beleg nicht noch einmal. Am
167
167
  gelesenen Original liefert `remainingQuantities(receipt)` die Reste vorab (für
168
168
  den Storno-Dialog); die Wahrheit hat der Server.
169
169
 
170
+ **Fehler entscheidet man am Code, nicht am Text.** Jeder fachliche Fehler von
171
+ `cancelReceipt` trägt `KasseneckApiError.code` aus `CANCELLATION_ERROR_CODES`
172
+ (z. B. `bereits_storniert`, `menge_ueber_rest`, `nur_eigene_belege`). Die
173
+ deutsche Meldung (`serverMessage`) ist Anzeige und darf sich ändern.
174
+
175
+ ```ts
176
+ import { isKasseneckApiError, isCancellationErrorCode } from '@kreiseck/kasseneck-api';
177
+
178
+ try {
179
+ await api.cancelReceipt({ receipt: beleg, reason: 'fehleingabe' });
180
+ } catch (fehler) {
181
+ if (isKasseneckApiError(fehler) && isCancellationErrorCode(fehler.code)) {
182
+ switch (fehler.code) {
183
+ case 'bereits_storniert': // Beleg im Dialog als „storniert" zeigen, Knopf sperren
184
+ case 'menge_ueber_rest': // Restmengen neu laden (jemand war schneller)
185
+ case 'nur_eigene_belege': // Chef holen
186
+ break;
187
+ }
188
+ }
189
+ throw fehler;
190
+ }
191
+ ```
192
+
193
+ **Gutscheine.** Ein Wertgutschein wird nur beim Vollstorno (ohne `items`)
194
+ gespiegelt — er ist unteilbar. Ein Rabattgutschein ist am Original bereits in
195
+ den Umsatz eingerechnet; **jeder** Storno nimmt ihn anteilig der stornierten
196
+ Menge zurück: bei 3 Stück à 10 € mit 6 € Rabatt sind 8 € je Stück Entgelt, der
197
+ Storno-Beleg trägt dann „−10,00" plus eine Zeile „Gutschein-Ausgleich +2,00".
198
+ Was ein Storno gewährt hat, steht am Eintrag in `receipt.cancellations[]` als
199
+ `promoAdjustmentCents` (Cent je Steuertopf) — die Kasse kann es im Dialog
200
+ zeigen, rechnen muss sie nichts.
201
+
170
202
  ## Unterpfade
171
203
 
172
204
  | Unterpfad | Inhalt |
@@ -174,7 +206,7 @@ den Storno-Dialog); die Wahrheit hat der Server.
174
206
  | `@kreiseck/kasseneck-api` | Endpunkte, Anmeldung, Transport, Modelle, Enums, Fehler — alles, was mit dem Backend spricht. |
175
207
  | `…/receipt` | Beleg-Layout als Datenmodell (framework-frei) und die Brücke zu ESC/POS. |
176
208
  | `…/printing` | ESC/POS-Erzeugung: Bytefolgen für Bondrucker, ohne jeden Transport. |
177
- | `…/payments` | Stripe-Zahllinks und Hobex-Cloud (beides HTTP-Endpunkte des Backends). |
209
+ | `…/payments` | Stripe-Zahllinks, Hobex-Cloud (beides HTTP-Endpunkte des Backends) und Hobex **HPS** über **Kasseneck Connect** (lokaler Geräte-Agent, spricht mit dem Terminal). |
178
210
  | `…/register` | Anmeldung der Browser-Kasse: Gerät koppeln und entkoppeln, Benutzer auflisten, per PIN anmelden, Sitzung erneuern und beenden. |
179
211
  | `…/kasse` | Kachel-Kasse: Kassen-Einstellungen (betriebsweit / je Gerät), Artikelgruppen und Artikel für Kacheln, Rabattverteilung je Steuersatz, Reichweiten der Kassen-Rechte |
180
212
  | `…/react` | Dünner React-Adapter, der ein Beleg-Layout zeichnet. Braucht React. |
@@ -182,16 +214,40 @@ den Storno-Dialog); die Wahrheit hat der Server.
182
214
 
183
215
  So zieht sich niemand den React-Adapter in ein Node-Programm.
184
216
 
185
- ## Was hier grundsätzlich nicht dazugehört
217
+ ## Hobex HPS über Kasseneck Connect
218
+
219
+ Ein Browser hat weiterhin keine rohen TCP-Sockets — ein **direkter**
220
+ Terminal-Kontakt wie beim Flutter-Paket `kasseneck_api` (`HpsClient`) bleibt
221
+ deshalb außerhalb der Reichweite dieses Pakets. **Kasseneck Connect** ist aber
222
+ ein lokaler Geräte-Agent mit gewöhnlicher HTTP-Schnittstelle, der für die Kasse
223
+ mit dem Terminal spricht — und darüber geht es:
186
224
 
187
- **Hobex HPS, myPOS und SumUp sind nicht Teil dieses Pakets und werden es auch
188
- nicht.** Hobex HPS spricht lokal über TCP mit dem Terminal, myPOS und SumUp
189
- sind Android-SDKs. Ein Browser kann das nicht, und kein Bündler ändert daran
190
- etwas. Wer diese Terminals braucht, nimmt das Flutter-Paket `kasseneck_api`.
225
+ ```ts
226
+ import { createHpsConnectClient, createHpsPayments } from '@kreiseck/kasseneck-api/payments';
191
227
 
192
- Ebenfalls nicht enthalten: die Druckeransteuerung selbst (dieses Paket erzeugt
193
- die Bytes, es verschickt sie nicht), Firmenlogos und Rasterbilder, und die
194
- PDF-Erzeugung.
228
+ const client = createHpsConnectClient({ token: kopplungsToken });
229
+ const zahlweg = createHpsPayments(client, { host: '192.168.1.50', tid: '3600335' });
230
+
231
+ const ergebnis = await zahlweg.pay({ amountCents: 1050 });
232
+ // ergebnis.outcome: 'approved' | 'declined' | 'unresolved' — nie geraten.
233
+ // ergebnis.transactionId ist IMMER gesetzt, auch bei 'unresolved'.
234
+ ```
235
+
236
+ Der Ausgang ist immer einer von drei: `approved`, `declined` (beweisbar nichts
237
+ belastet) oder `unresolved` (Ausgang unbekannt, eine Wiederholung könnte ein
238
+ zweites Mal belasten). Was das bedeutet und warum es so gebaut ist, steht in
239
+ `src/payments/hobex-hps/payments.ts` — dort ist die Dokumentation der Maßstab,
240
+ nicht dieses README.
241
+
242
+ **Nur `pay` — bewusst kein `refund`/`cancel`.** Kasseneck Connect exponiert
243
+ dafür (noch) keinen Endpunkt; eine Gutschrift oder ein Storno am HPS-Terminal
244
+ braucht weiterhin die Flutter-App. **myPOS** und **SumUp** bleiben
245
+ Android-SDKs ohne Entsprechung hier.
246
+
247
+ ## Was hier grundsätzlich nicht dazugehört
248
+
249
+ Die Druckeransteuerung selbst (dieses Paket erzeugt die Bytes, es verschickt
250
+ sie nicht), Firmenlogos und Rasterbilder, und die PDF-Erzeugung.
195
251
 
196
252
  ## Entwicklung
197
253
 
@@ -199,28 +255,52 @@ PDF-Erzeugung.
199
255
  npm test # Testsuite in drei Zeitzonen (Wien, UTC, Kiritimati)
200
256
  npm run build # ESM- und CJS-Bau nach dist/, inkl. Prüfung der exports
201
257
  npm run check:consumer # baut den Tarball und übersetzt zwei Verbraucher (CJS/ESM)
258
+ npm run check:erreichbar # fragt die öffentliche Adresse: antwortet dort zu jedem Aufruf eine Function?
202
259
  ```
203
260
 
204
261
  Die drei Zeitzonen sind kein Übereifer: Zeitfehler sind auf einer Wiener
205
262
  Maschine zufällig richtig. Belegzeiten werden konsequent als **Wiener
206
263
  Wanduhrzeit** gedeutet (`parseServerTimeStamp`), nie über `new Date(text)`.
207
264
 
265
+ ### `check:erreichbar` — spricht als einzige mit `api.kasseneck.at`
266
+
267
+ Testsuite und `check:consumer` laufen gegen Attrappen bzw. gegen den Tarball;
268
+ keine von beiden setzt je einen Aufruf ab. Fehlt einem Aufruf die
269
+ Hosting-Weiterleitung, liefert die veröffentlichte Adresse die
270
+ HTML-Auffangseite statt der Function — und das sieht keine Attrappe.
271
+
272
+ Die Prüfung braucht **keine Zugangsdaten**. Ein Aufruf ohne Anmeldung
273
+ antwortet, wenn dort eine Function steht, mit
274
+ `{"status":"error","message":"Ungültiger Request: Authorization key erwartet."}`.
275
+ Genau das ist der Beweis: Der Aufruf wurde angenommen und die Anmeldung
276
+ geprüft. Eine HTML-Seite oder ein 404 ist der Beweis, dass dort keine Function
277
+ steht. Deshalb prüft das Skript auf ein `status`-Feld und nicht auf Erfolg.
278
+
279
+ Bewusst außerhalb von `npm test`: Sie braucht Netz. Ist keines da, sagt sie es
280
+ und endet mit 0. Aufrufe, die unter `/v1` absichtlich keine Weiterleitung
281
+ haben — der Kassen-Weg über `kasse.kasseneck.at/api`, die Aufrufe mit
282
+ Firebase-ID-Token — stehen mit Grund in `scripts/erreichbarkeit-ausnahmen.json`.
283
+ Wird eine Ausnahme erreichbar, schlägt die Prüfung an: Sonst sänke die Zahl nie.
284
+
208
285
  ## Vertragsdateien für die Zwillinge
209
286
 
210
287
  Dieses Paket ist die Quelle für das Dart-Paket `kasseneck_api` und den
211
- Backend-Validator `kasse-settings-core.js`. Zwei Dateien in `fixtures/` reisen
212
- im Tarball mit und sagen in Maschinenform, worauf sich alle drei geeinigt haben:
288
+ Backend-Validator `kasse-settings-core.js`. Drei Dateien in `fixtures/` reisen
289
+ im Tarball mit und sagen in Maschinenform, worauf sich beide Seiten geeinigt haben:
213
290
 
214
291
  | Datei | Inhalt |
215
292
  |---|---|
216
293
  | `kasse-settings-standard.json` | Feldnamen und Standardwerte der Kassen-Einstellungen |
217
294
  | `oberflaeche.json` | Aufrufnamen, Enum-Werte, Rechte-Schlüssel, Tasten-Aktionen |
295
+ | `hobex-hps-codes.json` | Gemessene HPS-Ergebniscodes, ihre Bedeutung und ob sie einen Ausgang festschreiben — der Vertrag hinter `.../payments/hobex-hps`s `isConclusive`. |
218
296
 
219
- Beide werden erzeugt (`npm run fixtures:kasse`, `npm run fixtures:oberflaeche`)
220
- und nie von Hand geändert; die CI prüft nach jedem Lauf, dass sie zum Code passen.
297
+ Alle drei werden erzeugt (`npm run fixtures:kasse`, `npm run fixtures:oberflaeche`,
298
+ `npm run fixtures:hobex-hps-codes`) und nie von Hand geändert; die CI prüft
299
+ nach jedem Lauf, dass sie zum Code passen.
221
300
 
222
- `oberflaeche.json` trägt die Paketversion. **Nach jedem `npm version` müssen deshalb beide
223
- Dateien neu erzeugt und mitcommittet werden**, sonst wird die CI rot.
301
+ `oberflaeche.json` und `hobex-hps-codes.json` tragen die Paketversion. **Nach
302
+ jedem `npm version` müssen deshalb alle drei Dateien neu erzeugt und
303
+ mitcommittet werden**, sonst wird die CI rot.
224
304
 
225
305
  ## Lizenz
226
306
 
@@ -13,7 +13,7 @@
13
13
  * umhuellt, muss ihn weiterhin absetzen koennen.
14
14
  */
15
15
  import type { TransportBodyFields } from './transport.js';
16
- export declare const AUFRUFE: readonly ["cancelReceipt", "createPaymentLinkStripe", "createPrintJob", "createReceipt", "downloadDailyReport", "downloadReport", "endRegisterSession", "financeWebService", "generateFullReceiptId", "getFirstReceiptDate", "getKasseSettings", "getPrintJob", "getReceipt", "hobexPayApi", "hobexRefundApi", "listMyArticleGroups", "listMyArticles", "listMyCashregisters", "listMyPrinters", "listMyReceipts", "listRegisterUsersForDevice", "pairRegisterDevice", "registerPinLogin", "registerUserLogin", "renewRegisterSession", "setMyKasseSettings", "setMyRegisterDeviceSettings", "stripeCaptureIntent", "unpairRegisterDevice"];
16
+ export declare const AUFRUFE: readonly ["cancelReceipt", "createPaymentLinkStripe", "createPrintJob", "createReceipt", "downloadDailyReport", "downloadReport", "endRegisterSession", "financeWebService", "generateFullReceiptId", "getFirstReceiptDate", "getKasseSettings", "getPrintJob", "getReceipt", "hobexPayApi", "hobexRefundApi", "listMyArticleGroups", "listMyArticles", "listMyCashregisters", "listMyPrinters", "listMyReceipts", "listMyTipRecipients", "listRegisterUsersForDevice", "listRegisterSessionsForDevice", "pairRegisterDevice", "registerPinLogin", "registerUserLogin", "renewRegisterSession", "setMyKasseSettings", "setMyRegisterDeviceSettings", "stripeCaptureIntent", "unpairRegisterDevice"];
17
17
  export type Aufruf = typeof AUFRUFE[number];
18
18
  /** Wie [KasseneckTransport], nur mit bekanntem Aufrufnamen. Nicht exportiert nach aussen. */
19
19
  export type InternerTransport = <T = unknown>(functionName: Aufruf, params?: Record<string, unknown>, extraBodyFields?: TransportBodyFields, secretParams?: readonly string[]) => Promise<T>;
@@ -22,7 +22,9 @@ exports.AUFRUFE = [
22
22
  'listMyCashregisters',
23
23
  'listMyPrinters',
24
24
  'listMyReceipts',
25
+ 'listMyTipRecipients',
25
26
  'listRegisterUsersForDevice',
27
+ 'listRegisterSessionsForDevice',
26
28
  'pairRegisterDevice',
27
29
  'registerPinLogin',
28
30
  'registerUserLogin',
@@ -76,7 +76,13 @@ export declare class KasseneckApiError extends Error {
76
76
  readonly functionName: string;
77
77
  /** Meldung des Backends, unveraendert (`message` aus der Huelle). */
78
78
  readonly serverMessage: string;
79
- constructor(functionName: string, serverMessage: string);
79
+ /**
80
+ * Stabiler Fehlercode des Backends (`code` aus der Huelle), wenn der
81
+ * Endpunkt einen legt — heute `cancelReceipt` (siehe CANCELLATION_ERROR_CODES).
82
+ * Daran entscheiden, nie an [serverMessage]: der Text darf sich aendern.
83
+ */
84
+ readonly code: string | undefined;
85
+ constructor(functionName: string, serverMessage: string, code?: string);
80
86
  }
81
87
  /** Warum die Antwort keine verwertbare Huelle war. */
82
88
  export type HttpFailureReason = 'server-error' | 'empty-body' | 'not-json' | 'missing-status';
@@ -101,10 +101,17 @@ class KasseneckApiError extends Error {
101
101
  functionName;
102
102
  /** Meldung des Backends, unveraendert (`message` aus der Huelle). */
103
103
  serverMessage;
104
- constructor(functionName, serverMessage) {
104
+ /**
105
+ * Stabiler Fehlercode des Backends (`code` aus der Huelle), wenn der
106
+ * Endpunkt einen legt — heute `cancelReceipt` (siehe CANCELLATION_ERROR_CODES).
107
+ * Daran entscheiden, nie an [serverMessage]: der Text darf sich aendern.
108
+ */
109
+ code;
110
+ constructor(functionName, serverMessage, code) {
105
111
  super(`${functionName} fehlgeschlagen: ${serverMessage}`);
106
112
  this.functionName = functionName;
107
113
  this.serverMessage = serverMessage;
114
+ this.code = code;
108
115
  }
109
116
  }
110
117
  exports.KasseneckApiError = KasseneckApiError;
@@ -54,6 +54,22 @@ export interface TipOptions {
54
54
  paymentMethod?: KeckPaymentMethod | KeckPaymentMethodKey;
55
55
  /** Empfaenger; ohne Angabe der angemeldete Kassen-Benutzer. Summe der cents = cents. */
56
56
  recipients?: TipRecipientShare[];
57
+ /**
58
+ * Hat der Empfaenger das Geld schon? `true` = ja (Bargeld mitgenommen,
59
+ * Kartentrinkgeld sofort aus der Lade ausgezahlt), `false` = der Betrieb
60
+ * behaelt es und schuldet es.
61
+ *
62
+ * Entscheidend ist NICHT die Zahlart, sondern der Besitz -- § 2j Abs 2 AVRAG
63
+ * kennt beide Faelle. Ohne Angabe gilt die Voreinstellung des Betriebs (bar:
64
+ * schon erhalten, bargeldlos: einbehalten); diese Angabe braucht es nur fuer
65
+ * die Ausnahme.
66
+ *
67
+ * NUR mit dem Recht `tipAssign` bei persoenlicher Anmeldung: das Merkmal
68
+ * entscheidet, ob der Betrieb Geld schuldet, und gewoehnliches Personal soll
69
+ * das nicht am Geraet umstellen koennen. Ueber einen Geraete-API-Schluessel
70
+ * (ohne angemeldeten Kassen-Benutzer) gilt die Einschraenkung nicht.
71
+ */
72
+ sofortErhalten?: boolean;
57
73
  }
58
74
  export interface TipRecipientShare {
59
75
  registerUserId: string;
@@ -399,6 +399,16 @@ function gepruefterTip(tip) {
399
399
  const nutzlast = { cents: tip.cents };
400
400
  if (tip.paymentMethod != null)
401
401
  nutzlast['paymentMethod'] = gepruefteZahlungsart(tip.paymentMethod);
402
+ // Nur mitschicken, wenn gesetzt: fehlt das Feld, entscheidet die
403
+ // Voreinstellung des Betriebs. Ein `false` waere dort eine Aussage, kein
404
+ // Weglassen. Der Wortlaut des Fehlers ist der des Backends
405
+ // (tip-core.normalizeTip) -- wer ihn hier sieht, sieht denselben Satz.
406
+ if (tip.sofortErhalten != null) {
407
+ if (typeof tip.sofortErhalten !== 'boolean') {
408
+ throw eingabefehler('Trinkgeld: sofortErhalten muss true oder false sein.');
409
+ }
410
+ nutzlast['sofortErhalten'] = tip.sofortErhalten;
411
+ }
402
412
  if (tip.recipients != null) {
403
413
  if (!Array.isArray(tip.recipients) || tip.recipients.length === 0) {
404
414
  throw eingabefehler('Trinkgeld: recipients darf nicht leer sein.');
@@ -181,7 +181,7 @@ function jsonAuswerten(text, functionName, statusCode, inhaltstyp) {
181
181
  }
182
182
  // Alles, was nicht ausdruecklich Erfolg ist, gilt als fachlicher Fehler —
183
183
  // ein unbekannter Statuswert darf nie stillschweigend als Erfolg durchgehen.
184
- throw fachfehler(functionName, huelle.message);
184
+ throw fachfehler(functionName, huelle.message, huelle.code);
185
185
  }
186
186
  /**
187
187
  * Auswertung des Binaerwegs. Der Kern der Zusage: **ein Aufrufer bekommt nie
@@ -227,7 +227,7 @@ function pdfAuswerten(bytes, functionName, statusCode, inhaltstyp) {
227
227
  }
228
228
  // Derselbe fachliche Fehler wie auf dem JSON-Weg — fuer den Aufrufer macht
229
229
  // es keinen Unterschied, ob er ein PDF oder eine Nutzlast erwartet hat.
230
- throw fachfehler(functionName, huelle.message);
230
+ throw fachfehler(functionName, huelle.message, huelle.code);
231
231
  }
232
232
  /** `%PDF` am Anfang — die Kennung jeder PDF-Datei. */
233
233
  function istPdf(bytes) {
@@ -240,9 +240,10 @@ function alsHuelle(wert) {
240
240
  }
241
241
  return wert;
242
242
  }
243
- function fachfehler(functionName, message) {
243
+ function fachfehler(functionName, message, code) {
244
244
  const meldung = typeof message === 'string' && message.trim() ? message : 'Unbekannter Fehler';
245
- return new errors_js_1.KasseneckApiError(functionName, meldung);
245
+ // Nur ein Text zaehlt als Code -- alles andere waere ein geratener Vertrag.
246
+ return new errors_js_1.KasseneckApiError(functionName, meldung, typeof code === 'string' && code ? code : undefined);
246
247
  }
247
248
  /**
248
249
  * Nutzlast aus Auth- und Aufruferparametern. Auth-Parameter bilden die
@@ -1,5 +1,5 @@
1
1
  export { ReceiptType, type ReceiptTypeKey, VatRate, type VatRateKey, KeckPaymentMethod, type KeckPaymentMethodKey, CreditCardProvider, type CreditCardProviderKey, VoucherType, type VoucherTypeKey, VoucherAction, type VoucherActionKey, StripeLinkMode, type StripeLinkModeKey, } from './enums/index.js';
2
- export { type ReceiptItem, type ReceiptItemPayload, type ReceiptItemPayloadRead, toReceiptItemPayload, fromReceiptItemPayload, receiptItemTotalCents, receiptItemIsValid, negateReceiptItem, type TipRecipient, isTipItem, type Voucher, type VoucherPayload, toVoucherPayload, fromVoucherPayload, voucherIsValid, type Receipt, type ReceiptPayload, type ReceiptPayloadRead, toReceiptPayload, fromReceiptPayload, receiptSubSumCents, receiptSumCents, type CancellationOf, type CancellationItem, type Cancellation, type CancellationReason, CANCELLATION_REASONS, CANCELLATION_RESERVATION_MS, isCancellationReason, remainingQuantities, type ReceiptCompany, type ReceiptCompanyPayload, fromReceiptCompanyPayload, receiptCompanyTaxInfo, type Cashregister, type CashregisterOnboarding, type CashregisterPayload, type CashregisterOnboardingPayload, fromCashregisterPayload, type ReceiptSummary, type ReceiptSummaryPayload, fromReceiptSummaryPayload, type ZeroKind, ZERO_KINDS, istZeroKind, type ReportMonth, reportMonthFromDate, previousReportMonth, nextReportMonth, reportMonthKey, reportMonthReadable, type StripeUrlSession, type StripeUrlSessionPayload, toStripeUrlSessionPayload, fromStripeUrlSessionPayload, type HobexReceipt, type HobexReceiptPayload, toHobexReceiptPayload, fromHobexReceiptPayload, hobexReceiptToCardPaymentData, hobexReceiptNeedsSignature, } from './models/index.js';
2
+ export { type ReceiptItem, type ReceiptItemPayload, type ReceiptItemPayloadRead, toReceiptItemPayload, fromReceiptItemPayload, receiptItemTotalCents, receiptItemIsValid, negateReceiptItem, type TipRecipient, isTipItem, type Voucher, type VoucherPayload, toVoucherPayload, fromVoucherPayload, voucherIsValid, type Receipt, type ReceiptPayload, type ReceiptPayloadRead, toReceiptPayload, fromReceiptPayload, receiptSubSumCents, receiptSumCents, type CancellationOf, type CancellationItem, type Cancellation, type CancellationReason, type CancellationErrorCode, CANCELLATION_REASONS, CANCELLATION_ERROR_CODES, CANCELLATION_RESERVATION_MS, isCancellationReason, isCancellationErrorCode, remainingQuantities, type ReceiptCompany, type ReceiptCompanyPayload, fromReceiptCompanyPayload, receiptCompanyTaxInfo, type Cashregister, type CashregisterOnboarding, type CashregisterPayload, type CashregisterOnboardingPayload, fromCashregisterPayload, type ReceiptSummary, type ReceiptSummaryPayload, fromReceiptSummaryPayload, type ZeroKind, ZERO_KINDS, istZeroKind, type ReportMonth, reportMonthFromDate, previousReportMonth, nextReportMonth, reportMonthKey, reportMonthReadable, type StripeUrlSession, type StripeUrlSessionPayload, toStripeUrlSessionPayload, fromStripeUrlSessionPayload, type HobexReceipt, type HobexReceiptPayload, toHobexReceiptPayload, fromHobexReceiptPayload, hobexReceiptToCardPaymentData, hobexReceiptNeedsSignature, } from './models/index.js';
3
3
  export { type AuthCredentials, type KasseneckAuth, type ApiKeyAuthOptions, type RegisterUserAuthOptions, apiKeyAuth, registerUserAuth, DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, type HttpResponseLike, type HttpRequestInit, type FetchLike, type TransportOptions, type TransportBodyFields, type KasseneckTransport, type KasseneckBinaryTransport, AUFRUFE, type Aufruf, createTransport, createBinaryTransport, KasseneckApiError, KasseneckAuthError, KasseneckHttpError, KasseneckNetworkError, KasseneckValidationError, type ValidationScope, type KasseneckError, type HttpFailureReason, type CauseDigest, isKasseneckApiError, isKasseneckAuthError, isKasseneckHttpError, isKasseneckNetworkError, isKasseneckValidationError, type ReceiptCommonOptions, type SellReceiptOptions, type TipOptions, type TipRecipientShare, type CancelReceiptOptions, type CancelReceiptResult, type CreateCancelReceiptOptions, type ReceiptWithCompany, sellReceipt, sellReceiptWithCompany, cancelReceipt, createCancelReceipt, zeroReceipt, getReceipt, getReceiptWithCompany, generateFullReceiptId, getFirstReceiptDate, listMyReceipts, type ListMyReceiptsOptions, type ReceiptList, type ReceiptListStats, listMyCashregisters, checkVoucherCombinationError, downloadDailyReport, downloadMonthlyReport, type CashboxStatus, type SignatureStatus, getCashboxStatus, getSignatureStatus, type KasseneckApi, createKasseneckApi, } from './client/index.js';
4
4
  export { parseServerTimeStamp, toViennaWallClock, type ViennaWallClock } from './vienna-time.js';
5
5
  export { type KasseSettingsBetrieb, type KasseSettingsGeraet, type KasseSettings, type ArticleGroup, type KasseArtikel, KASSE_BETRIEB_STANDARD, KASSE_GERAET_STANDARD, mergeKasseSettings, fromArticleGroupPayload, fromKasseArtikelPayload, listMyArticleGroups, listMyArticles, getKasseSettings, setMyKasseSettings, setMyRegisterDeviceSettings, verteileRabatt, cancelScopeOf, receiptsScopeOf, } from './kasse/index.js';
package/dist/cjs/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.KasseneckAuthError = exports.KasseneckApiError = exports.createBinaryTransport = exports.createTransport = exports.AUFRUFE = exports.DEFAULT_TIMEOUT_MS = exports.DEFAULT_BASE_URL = exports.registerUserAuth = exports.apiKeyAuth = exports.hobexReceiptNeedsSignature = exports.hobexReceiptToCardPaymentData = exports.fromHobexReceiptPayload = exports.toHobexReceiptPayload = exports.fromStripeUrlSessionPayload = exports.toStripeUrlSessionPayload = exports.reportMonthReadable = exports.reportMonthKey = exports.nextReportMonth = exports.previousReportMonth = exports.reportMonthFromDate = exports.istZeroKind = exports.ZERO_KINDS = exports.fromReceiptSummaryPayload = exports.fromCashregisterPayload = exports.receiptCompanyTaxInfo = exports.fromReceiptCompanyPayload = exports.remainingQuantities = exports.isCancellationReason = exports.CANCELLATION_RESERVATION_MS = exports.CANCELLATION_REASONS = exports.receiptSumCents = exports.receiptSubSumCents = exports.fromReceiptPayload = exports.toReceiptPayload = exports.voucherIsValid = exports.fromVoucherPayload = exports.toVoucherPayload = exports.isTipItem = exports.negateReceiptItem = exports.receiptItemIsValid = exports.receiptItemTotalCents = exports.fromReceiptItemPayload = exports.toReceiptItemPayload = exports.StripeLinkMode = exports.VoucherAction = exports.VoucherType = exports.CreditCardProvider = exports.KeckPaymentMethod = exports.VatRate = exports.ReceiptType = void 0;
4
- exports.receiptsScopeOf = exports.cancelScopeOf = exports.verteileRabatt = exports.setMyRegisterDeviceSettings = exports.setMyKasseSettings = exports.getKasseSettings = exports.listMyArticles = exports.listMyArticleGroups = exports.fromKasseArtikelPayload = exports.fromArticleGroupPayload = exports.mergeKasseSettings = exports.KASSE_GERAET_STANDARD = exports.KASSE_BETRIEB_STANDARD = exports.toViennaWallClock = exports.parseServerTimeStamp = exports.createKasseneckApi = exports.getSignatureStatus = exports.getCashboxStatus = exports.downloadMonthlyReport = exports.downloadDailyReport = exports.checkVoucherCombinationError = exports.listMyCashregisters = exports.listMyReceipts = exports.getFirstReceiptDate = exports.generateFullReceiptId = exports.getReceiptWithCompany = exports.getReceipt = exports.zeroReceipt = exports.createCancelReceipt = exports.cancelReceipt = exports.sellReceiptWithCompany = exports.sellReceipt = exports.isKasseneckValidationError = exports.isKasseneckNetworkError = exports.isKasseneckHttpError = exports.isKasseneckAuthError = exports.isKasseneckApiError = exports.KasseneckValidationError = exports.KasseneckNetworkError = exports.KasseneckHttpError = void 0;
3
+ exports.createBinaryTransport = exports.createTransport = exports.AUFRUFE = exports.DEFAULT_TIMEOUT_MS = exports.DEFAULT_BASE_URL = exports.registerUserAuth = exports.apiKeyAuth = exports.hobexReceiptNeedsSignature = exports.hobexReceiptToCardPaymentData = exports.fromHobexReceiptPayload = exports.toHobexReceiptPayload = exports.fromStripeUrlSessionPayload = exports.toStripeUrlSessionPayload = exports.reportMonthReadable = exports.reportMonthKey = exports.nextReportMonth = exports.previousReportMonth = exports.reportMonthFromDate = exports.istZeroKind = exports.ZERO_KINDS = exports.fromReceiptSummaryPayload = exports.fromCashregisterPayload = exports.receiptCompanyTaxInfo = exports.fromReceiptCompanyPayload = exports.remainingQuantities = exports.isCancellationErrorCode = exports.isCancellationReason = exports.CANCELLATION_RESERVATION_MS = exports.CANCELLATION_ERROR_CODES = exports.CANCELLATION_REASONS = exports.receiptSumCents = exports.receiptSubSumCents = exports.fromReceiptPayload = exports.toReceiptPayload = exports.voucherIsValid = exports.fromVoucherPayload = exports.toVoucherPayload = exports.isTipItem = exports.negateReceiptItem = exports.receiptItemIsValid = exports.receiptItemTotalCents = exports.fromReceiptItemPayload = exports.toReceiptItemPayload = exports.StripeLinkMode = exports.VoucherAction = exports.VoucherType = exports.CreditCardProvider = exports.KeckPaymentMethod = exports.VatRate = exports.ReceiptType = void 0;
4
+ exports.receiptsScopeOf = exports.cancelScopeOf = exports.verteileRabatt = exports.setMyRegisterDeviceSettings = exports.setMyKasseSettings = exports.getKasseSettings = exports.listMyArticles = exports.listMyArticleGroups = exports.fromKasseArtikelPayload = exports.fromArticleGroupPayload = exports.mergeKasseSettings = exports.KASSE_GERAET_STANDARD = exports.KASSE_BETRIEB_STANDARD = exports.toViennaWallClock = exports.parseServerTimeStamp = exports.createKasseneckApi = exports.getSignatureStatus = exports.getCashboxStatus = exports.downloadMonthlyReport = exports.downloadDailyReport = exports.checkVoucherCombinationError = exports.listMyCashregisters = exports.listMyReceipts = exports.getFirstReceiptDate = exports.generateFullReceiptId = exports.getReceiptWithCompany = exports.getReceipt = exports.zeroReceipt = exports.createCancelReceipt = exports.cancelReceipt = exports.sellReceiptWithCompany = exports.sellReceipt = exports.isKasseneckValidationError = exports.isKasseneckNetworkError = exports.isKasseneckHttpError = exports.isKasseneckAuthError = exports.isKasseneckApiError = exports.KasseneckValidationError = exports.KasseneckNetworkError = exports.KasseneckHttpError = exports.KasseneckAuthError = exports.KasseneckApiError = void 0;
5
5
  var index_js_1 = require("./enums/index.js");
6
6
  Object.defineProperty(exports, "ReceiptType", { enumerable: true, get: function () { return index_js_1.ReceiptType; } });
7
7
  Object.defineProperty(exports, "VatRate", { enumerable: true, get: function () { return index_js_1.VatRate; } });
@@ -25,8 +25,10 @@ Object.defineProperty(exports, "fromReceiptPayload", { enumerable: true, get: fu
25
25
  Object.defineProperty(exports, "receiptSubSumCents", { enumerable: true, get: function () { return index_js_2.receiptSubSumCents; } });
26
26
  Object.defineProperty(exports, "receiptSumCents", { enumerable: true, get: function () { return index_js_2.receiptSumCents; } });
27
27
  Object.defineProperty(exports, "CANCELLATION_REASONS", { enumerable: true, get: function () { return index_js_2.CANCELLATION_REASONS; } });
28
+ Object.defineProperty(exports, "CANCELLATION_ERROR_CODES", { enumerable: true, get: function () { return index_js_2.CANCELLATION_ERROR_CODES; } });
28
29
  Object.defineProperty(exports, "CANCELLATION_RESERVATION_MS", { enumerable: true, get: function () { return index_js_2.CANCELLATION_RESERVATION_MS; } });
29
30
  Object.defineProperty(exports, "isCancellationReason", { enumerable: true, get: function () { return index_js_2.isCancellationReason; } });
31
+ Object.defineProperty(exports, "isCancellationErrorCode", { enumerable: true, get: function () { return index_js_2.isCancellationErrorCode; } });
30
32
  Object.defineProperty(exports, "remainingQuantities", { enumerable: true, get: function () { return index_js_2.remainingQuantities; } });
31
33
  Object.defineProperty(exports, "fromReceiptCompanyPayload", { enumerable: true, get: function () { return index_js_2.fromReceiptCompanyPayload; } });
32
34
  Object.defineProperty(exports, "receiptCompanyTaxInfo", { enumerable: true, get: function () { return index_js_2.receiptCompanyTaxInfo; } });
@@ -9,3 +9,4 @@ export { getKasseSettings, setMyKasseSettings, setMyRegisterDeviceSettings } fro
9
9
  export { verteileRabatt } from '../receipt/discount.js';
10
10
  export { cancelScopeOf, receiptsScopeOf, type RegisterScope, type RegisterUserPerms } from '../register/pairing.js';
11
11
  export { type NetzDrucker, type DruckJob, type DruckJobStatus, type CreatePrintJobOptions, listMyPrinters, createPrintJob, getPrintJob } from './drucker.js';
12
+ export { listMyTipRecipients } from './trinkgeld.js';
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getPrintJob = exports.createPrintJob = exports.listMyPrinters = exports.receiptsScopeOf = exports.cancelScopeOf = exports.verteileRabatt = exports.setMyRegisterDeviceSettings = exports.setMyKasseSettings = exports.getKasseSettings = exports.mengeErlaubt = exports.mengenVorgabe = exports.mengenregelFuerEinheit = exports.listMyArticles = exports.listMyArticleGroups = exports.fromKasseArtikelPayload = exports.fromArticleGroupPayload = exports.FERTIG_SEKUNDEN = exports.WZ_SKALA = exports.LOGO_SKALA = exports.WZ_STAERKE = exports.WZ_SEITE = exports.RABATT = exports.AUTO_AB_MIN = exports.KACHELSTIL = exports.SCHRIFT_EINST = exports.LOGO_GROESSE = exports.TASTEN_AKTIONEN = exports.LADE_AUTO = exports.SCHNITT = exports.ZEICHENSATZ = exports.PAPIER = exports.TERMINAL_ART = exports.TERMINAL_VIA = exports.DRUCKER_ART = exports.HOEHE = exports.KATPOS = exports.LAYOUT = exports.BELEG_AUSGABE = exports.KASSIEREN_MODUS = exports.TG_MODUS = exports.MENGE = exports.WASSERZEICHEN = exports.SCHRIFT = exports.STIL = exports.mergeKasseSettings = exports.KASSE_GERAET_STANDARD = exports.KASSE_BETRIEB_STANDARD = exports.KASSE_TASTEN_STANDARD = exports.KASSE_TASTEN_AKTIONEN = exports.KARTENANBIETER = void 0;
4
+ exports.listMyTipRecipients = void 0;
4
5
  /**
5
6
  * Kachel-Kasse: Einstellungen, Artikelgruppen/Artikel fuer Kacheln,
6
7
  * Rabattverteilung. Die Aufrufe heissen 1:1 wie die Backend-Functions
@@ -65,3 +66,5 @@ var drucker_js_1 = require("./drucker.js");
65
66
  Object.defineProperty(exports, "listMyPrinters", { enumerable: true, get: function () { return drucker_js_1.listMyPrinters; } });
66
67
  Object.defineProperty(exports, "createPrintJob", { enumerable: true, get: function () { return drucker_js_1.createPrintJob; } });
67
68
  Object.defineProperty(exports, "getPrintJob", { enumerable: true, get: function () { return drucker_js_1.getPrintJob; } });
69
+ var trinkgeld_js_1 = require("./trinkgeld.js");
70
+ Object.defineProperty(exports, "listMyTipRecipients", { enumerable: true, get: function () { return trinkgeld_js_1.listMyTipRecipients; } });
@@ -0,0 +1,10 @@
1
+ import type { InternerTransport } from '../client/aufrufe.js';
2
+ import type { TipRecipient } from '../models/index.js';
3
+ /**
4
+ * Die Personen, denen dieser Aufrufer Trinkgeld zuweisen kann.
5
+ *
6
+ * Es ist **dieselbe Menge**, die `createReceipt` akzeptiert — wer hier steht,
7
+ * wird beim Verkauf nicht zurueckgewiesen. Ist ein Kassen-Benutzer ohne das
8
+ * Recht `tipAssign` angemeldet, steht nur er selbst darin.
9
+ */
10
+ export declare function listMyTipRecipients(rufen: InternerTransport): Promise<TipRecipient[]>;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listMyTipRecipients = listMyTipRecipients;
4
+ const errors_js_1 = require("../client/errors.js");
5
+ /**
6
+ * Die Personen, denen dieser Aufrufer Trinkgeld zuweisen kann.
7
+ *
8
+ * Es ist **dieselbe Menge**, die `createReceipt` akzeptiert — wer hier steht,
9
+ * wird beim Verkauf nicht zurueckgewiesen. Ist ein Kassen-Benutzer ohne das
10
+ * Recht `tipAssign` angemeldet, steht nur er selbst darin.
11
+ */
12
+ async function listMyTipRecipients(rufen) {
13
+ const daten = await rufen('listMyTipRecipients');
14
+ const roh = daten?.recipients;
15
+ if (!Array.isArray(roh)) {
16
+ // Keine Liste ist etwas anderes als eine leere Liste: „noch niemand
17
+ // angelegt" darf nicht aussehen wie „Antwort kaputt".
18
+ throw new errors_js_1.KasseneckValidationError('listMyTipRecipients', 'Antwort enthaelt keine Liste (data.recipients fehlt)', 'response');
19
+ }
20
+ return roh.map((e) => {
21
+ const d = (e ?? {});
22
+ // Dieselbe Gestalt wie der Empfaenger am Beleg-Item — `TipRecipient` aus
23
+ // src/models/receipt-item.ts. Kein neuer Typ: „wer kann Trinkgeld
24
+ // bekommen" und „wer hat es bekommen" sind dieselbe Sache.
25
+ return { registerUserId: String(d.registerUserId ?? ''), name: String(d.name ?? ''), owner: d.owner === true };
26
+ });
27
+ }
@@ -18,6 +18,19 @@ export declare const CANCELLATION_REASONS: Readonly<{
18
18
  }>;
19
19
  export type CancellationReason = keyof typeof CANCELLATION_REASONS;
20
20
  export declare function isCancellationReason(value: unknown): value is CancellationReason;
21
+ /**
22
+ * Stabile Fehlercodes von `cancelReceipt` — Zwilling von
23
+ * `functions/storno-core.js` STORNO_FEHLERCODES. Das Backend legt sie bei
24
+ * jedem fachlichen Fehler als `code` neben die Meldung; das Paket reicht sie
25
+ * als [KasseneckApiError.code] durch. **Entscheide am Code, nie am Text** —
26
+ * die deutsche Meldung darf sich aendern, der Code nicht.
27
+ *
28
+ * Nur Auth-/Parameterfehler (Sitzung abgelaufen, Pflichtfeld fehlt) kommen
29
+ * ohne Code; dort bleibt `code` undefined.
30
+ */
31
+ export declare const CANCELLATION_ERROR_CODES: readonly ["beleg_nicht_gefunden", "belegart_nicht_stornierbar", "trainingsbeleg", "bereits_storniert", "position_ungueltig", "menge_ueber_rest", "grund_unbekannt", "anmerkung_zu_lang", "items_ungueltig", "kasse_nicht_zugewiesen", "keine_berechtigung", "nur_eigene_belege", "kasse_unvollstaendig", "storno_fehlgeschlagen"];
32
+ export type CancellationErrorCode = (typeof CANCELLATION_ERROR_CODES)[number];
33
+ export declare function isCancellationErrorCode(value: unknown): value is CancellationErrorCode;
21
34
  /** Bezug eines Storno-Belegs auf sein Original. */
22
35
  export interface CancellationOf {
23
36
  receiptId: string;
@@ -40,6 +53,15 @@ export interface Cancellation {
40
53
  by: string | null;
41
54
  note: string | null;
42
55
  items: CancellationItem[];
56
+ /**
57
+ * Rabattgutschein-Ausgleich, den DIESER Storno gewaehrt hat — Cent je
58
+ * Steuertopf des Backends (`amountRateStandard`, `amountRateReduced1`, …).
59
+ * Ein Rabattgutschein ist am Original schon in den Umsatz eingerechnet;
60
+ * jeder Storno nimmt ihn anteilig der stornierten Menge zurueck (bei 3 Stueck
61
+ * a 10 EUR mit 6 EUR Rabatt: 2,00 je Stueck). Fehlt das Feld, hat der Eintrag
62
+ * nichts gewaehrt (Altbestand vor dieser Regel) — der naechste Storno holt nach.
63
+ */
64
+ promoAdjustmentCents?: Record<string, number>;
43
65
  }
44
66
  /** Ab wann eine liegengebliebene Reservierung nicht mehr zaehlt (wie im Backend). */
45
67
  export declare const CANCELLATION_RESERVATION_MS = 120000;
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.CANCELLATION_RESERVATION_MS = exports.CANCELLATION_REASONS = void 0;
3
+ exports.CANCELLATION_RESERVATION_MS = exports.CANCELLATION_ERROR_CODES = exports.CANCELLATION_REASONS = void 0;
4
4
  exports.isCancellationReason = isCancellationReason;
5
+ exports.isCancellationErrorCode = isCancellationErrorCode;
5
6
  exports.remainingQuantities = remainingQuantities;
6
7
  /**
7
8
  * Storno-Metadaten — Zwilling von `functions/storno-core.js`.
@@ -23,6 +24,35 @@ exports.CANCELLATION_REASONS = Object.freeze({
23
24
  function isCancellationReason(value) {
24
25
  return typeof value === 'string' && Object.prototype.hasOwnProperty.call(exports.CANCELLATION_REASONS, value);
25
26
  }
27
+ /**
28
+ * Stabile Fehlercodes von `cancelReceipt` — Zwilling von
29
+ * `functions/storno-core.js` STORNO_FEHLERCODES. Das Backend legt sie bei
30
+ * jedem fachlichen Fehler als `code` neben die Meldung; das Paket reicht sie
31
+ * als [KasseneckApiError.code] durch. **Entscheide am Code, nie am Text** —
32
+ * die deutsche Meldung darf sich aendern, der Code nicht.
33
+ *
34
+ * Nur Auth-/Parameterfehler (Sitzung abgelaufen, Pflichtfeld fehlt) kommen
35
+ * ohne Code; dort bleibt `code` undefined.
36
+ */
37
+ exports.CANCELLATION_ERROR_CODES = [
38
+ 'beleg_nicht_gefunden', // Original fehlt oder gehoert nicht zu dieser Kasse
39
+ 'belegart_nicht_stornierbar', // Original ist selbst Storno-, Null- oder Startbeleg
40
+ 'trainingsbeleg', // Trainingsbelege werden nicht storniert
41
+ 'bereits_storniert', // keine Restmenge mehr (auch beim positionslosen Beleg)
42
+ 'position_ungueltig', // Index unbekannt oder doppelt
43
+ 'menge_ueber_rest', // Menge nicht ganzzahlig >= 1 oder groesser als der Rest
44
+ 'grund_unbekannt', // reason fehlt oder nicht im Katalog
45
+ 'anmerkung_zu_lang', // note laenger als 200 Zeichen
46
+ 'items_ungueltig', // items ist keine Liste
47
+ 'kasse_nicht_zugewiesen', // Register-Benutzer darf diese Kasse nicht
48
+ 'keine_berechtigung', // Register-Benutzer ohne Storno-Recht
49
+ 'nur_eigene_belege', // Recht "eigene", fremder Beleg
50
+ 'kasse_unvollstaendig', // api_key/token fehlen am Konto bzw. an der Kasse
51
+ 'storno_fehlgeschlagen', // der Storno-Beleg selbst wurde abgelehnt (z. B. Signatur)
52
+ ];
53
+ function isCancellationErrorCode(value) {
54
+ return typeof value === 'string' && exports.CANCELLATION_ERROR_CODES.includes(value);
55
+ }
26
56
  /** Ab wann eine liegengebliebene Reservierung nicht mehr zaehlt (wie im Backend). */
27
57
  exports.CANCELLATION_RESERVATION_MS = 120_000;
28
58
  /**
@@ -44,8 +44,15 @@ function fromHobexReceiptPayload(payload) {
44
44
  responseCode: payload.responseCode,
45
45
  transactionType: payload.transactionType,
46
46
  currency: payload.currency,
47
- amountCents: Math.round((payload.amount ?? 0) * 100),
48
- tipCents: Math.round((payload.tip ?? 0) * 100),
47
+ // Euro->Cent ueber die eine gehaertete Stelle im Paket ([euroToCents],
48
+ // money.ts) statt einer eigenen `Math.round(x * 100)` -- die Hobex-Antwort
49
+ // ist eine fremde Antwort und wird hier nur auf transactionId/
50
+ // transactionDate geprueft (siehe payments/hobex.ts, belegAusNutzlast);
51
+ // amount/tip bleiben ungeprueft. Ein nicht-numerischer oder
52
+ // nicht-endlicher Wert ergab bis zu dieser Fassung `NaN` -- lautlos, ohne
53
+ // dass irgendwo geworfen wurde. [euroToCents] liefert dafuer `0`.
54
+ amountCents: (0, money_js_1.euroToCents)(payload.amount),
55
+ tipCents: (0, money_js_1.euroToCents)(payload.tip),
49
56
  cvm: String(payload.cvm),
50
57
  // Die Cloud-API-Nutzlast traegt keinen eigenen Provider-Schluessel — der
51
58
  // Provider ergibt sich aus dem Transportweg (Cloud vs. HPS), nicht aus
@@ -1,7 +1,7 @@
1
1
  export { type ReceiptItem, type ReceiptItemPayload, type ReceiptItemPayloadRead, toReceiptItemPayload, fromReceiptItemPayload, receiptItemTotalCents, receiptItemIsValid, negateReceiptItem, type TipRecipient, isTipItem, isDiscountItem, } from './receipt-item.js';
2
2
  export { type Voucher, type VoucherPayload, toVoucherPayload, fromVoucherPayload, voucherIsValid } from './voucher.js';
3
3
  export { type Receipt, type ReceiptPayload, type ReceiptPayloadRead, toReceiptPayload, fromReceiptPayload, receiptSubSumCents, receiptSumCents, } from './receipt.js';
4
- export { type CancellationOf, type CancellationItem, type Cancellation, type CancellationReason, CANCELLATION_REASONS, CANCELLATION_RESERVATION_MS, isCancellationReason, remainingQuantities, } from './cancellation.js';
4
+ export { type CancellationOf, type CancellationItem, type Cancellation, type CancellationReason, type CancellationErrorCode, CANCELLATION_REASONS, CANCELLATION_ERROR_CODES, CANCELLATION_RESERVATION_MS, isCancellationReason, isCancellationErrorCode, remainingQuantities, } from './cancellation.js';
5
5
  export { type ReceiptCompany, type ReceiptCompanyPayload, fromReceiptCompanyPayload, receiptCompanyTaxInfo, } from './receipt-company.js';
6
6
  export { type Cashregister, type CashregisterOnboarding, type CashregisterPayload, type CashregisterOnboardingPayload, fromCashregisterPayload, } from './cashregister.js';
7
7
  export { type ReceiptSummary, type ReceiptSummaryPayload, fromReceiptSummaryPayload, type ZeroKind, ZERO_KINDS, istZeroKind } from './receipt-summary.js';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.hobexReceiptNeedsSignature = exports.hobexReceiptToCardPaymentData = exports.fromHobexReceiptPayload = exports.toHobexReceiptPayload = exports.fromStripeUrlSessionPayload = exports.toStripeUrlSessionPayload = exports.reportMonthReadable = exports.reportMonthKey = exports.nextReportMonth = exports.previousReportMonth = exports.reportMonthFromDate = exports.istZeroKind = exports.ZERO_KINDS = exports.fromReceiptSummaryPayload = exports.fromCashregisterPayload = exports.receiptCompanyTaxInfo = exports.fromReceiptCompanyPayload = exports.remainingQuantities = exports.isCancellationReason = exports.CANCELLATION_RESERVATION_MS = exports.CANCELLATION_REASONS = exports.receiptSumCents = exports.receiptSubSumCents = exports.fromReceiptPayload = exports.toReceiptPayload = exports.voucherIsValid = exports.fromVoucherPayload = exports.toVoucherPayload = exports.isDiscountItem = exports.isTipItem = exports.negateReceiptItem = exports.receiptItemIsValid = exports.receiptItemTotalCents = exports.fromReceiptItemPayload = exports.toReceiptItemPayload = void 0;
3
+ exports.hobexReceiptNeedsSignature = exports.hobexReceiptToCardPaymentData = exports.fromHobexReceiptPayload = exports.toHobexReceiptPayload = exports.fromStripeUrlSessionPayload = exports.toStripeUrlSessionPayload = exports.reportMonthReadable = exports.reportMonthKey = exports.nextReportMonth = exports.previousReportMonth = exports.reportMonthFromDate = exports.istZeroKind = exports.ZERO_KINDS = exports.fromReceiptSummaryPayload = exports.fromCashregisterPayload = exports.receiptCompanyTaxInfo = exports.fromReceiptCompanyPayload = exports.remainingQuantities = exports.isCancellationErrorCode = exports.isCancellationReason = exports.CANCELLATION_RESERVATION_MS = exports.CANCELLATION_ERROR_CODES = exports.CANCELLATION_REASONS = exports.receiptSumCents = exports.receiptSubSumCents = exports.fromReceiptPayload = exports.toReceiptPayload = exports.voucherIsValid = exports.fromVoucherPayload = exports.toVoucherPayload = exports.isDiscountItem = exports.isTipItem = exports.negateReceiptItem = exports.receiptItemIsValid = exports.receiptItemTotalCents = exports.fromReceiptItemPayload = exports.toReceiptItemPayload = void 0;
4
4
  var receipt_item_js_1 = require("./receipt-item.js");
5
5
  Object.defineProperty(exports, "toReceiptItemPayload", { enumerable: true, get: function () { return receipt_item_js_1.toReceiptItemPayload; } });
6
6
  Object.defineProperty(exports, "fromReceiptItemPayload", { enumerable: true, get: function () { return receipt_item_js_1.fromReceiptItemPayload; } });
@@ -20,8 +20,10 @@ Object.defineProperty(exports, "receiptSubSumCents", { enumerable: true, get: fu
20
20
  Object.defineProperty(exports, "receiptSumCents", { enumerable: true, get: function () { return receipt_js_1.receiptSumCents; } });
21
21
  var cancellation_js_1 = require("./cancellation.js");
22
22
  Object.defineProperty(exports, "CANCELLATION_REASONS", { enumerable: true, get: function () { return cancellation_js_1.CANCELLATION_REASONS; } });
23
+ Object.defineProperty(exports, "CANCELLATION_ERROR_CODES", { enumerable: true, get: function () { return cancellation_js_1.CANCELLATION_ERROR_CODES; } });
23
24
  Object.defineProperty(exports, "CANCELLATION_RESERVATION_MS", { enumerable: true, get: function () { return cancellation_js_1.CANCELLATION_RESERVATION_MS; } });
24
25
  Object.defineProperty(exports, "isCancellationReason", { enumerable: true, get: function () { return cancellation_js_1.isCancellationReason; } });
26
+ Object.defineProperty(exports, "isCancellationErrorCode", { enumerable: true, get: function () { return cancellation_js_1.isCancellationErrorCode; } });
25
27
  Object.defineProperty(exports, "remainingQuantities", { enumerable: true, get: function () { return cancellation_js_1.remainingQuantities; } });
26
28
  var receipt_company_js_1 = require("./receipt-company.js");
27
29
  Object.defineProperty(exports, "fromReceiptCompanyPayload", { enumerable: true, get: function () { return receipt_company_js_1.fromReceiptCompanyPayload; } });
@@ -90,6 +90,9 @@ function leseStorno(eintrag) {
90
90
  by: eintrag.by ?? null,
91
91
  note: eintrag.note ?? null,
92
92
  items: (eintrag.items ?? []).map((p) => ({ index: Number(p.index), quantity: Number(p.quantity) })),
93
+ ...(eintrag.promoAdjustmentCents && typeof eintrag.promoAdjustmentCents === 'object'
94
+ ? { promoAdjustmentCents: Object.fromEntries(Object.entries(eintrag.promoAdjustmentCents).map(([k, v]) => [k, Number(v)])) }
95
+ : {}),
93
96
  };
94
97
  }
95
98
  /**
@@ -4,6 +4,7 @@ exports.toVoucherPayload = toVoucherPayload;
4
4
  exports.voucherIsValid = voucherIsValid;
5
5
  exports.fromVoucherPayload = fromVoucherPayload;
6
6
  const index_js_1 = require("../enums/index.js");
7
+ const money_js_1 = require("../money.js");
7
8
  const enum_payload_js_1 = require("./enum-payload.js");
8
9
  function toVoucherPayload(voucher) {
9
10
  return {
@@ -35,16 +36,31 @@ function voucherIsValid(voucher) {
35
36
  if (voucher.type === index_js_1.VoucherType.promo && voucher.valueCents == null) {
36
37
  return false;
37
38
  }
38
- if (voucher.valueCents != null && voucher.valueCents <= 0) {
39
+ // `!Number.isFinite` VOR den Vergleich, nicht dahinter: `NaN <= 0` und
40
+ // `Infinity <= 0` sind beide `false`, ein Vergleich mit einem
41
+ // nicht-endlichen Wert ist also IMMER falsch und liesse ihn hier
42
+ // durchrutschen. Gemessen (28.08.2026): ein Gutschein mit `valueCents: NaN`
43
+ // (aus einem kaputten `value` in der Nutzlast, siehe [fromVoucherPayload])
44
+ // oder `valueCents: Infinity` galt bis zu dieser Fassung als GUELTIG und
45
+ // waere still in die Belegsummen gewandert. Diese Pruefung greift
46
+ // unabhaengig davon, ob `valueCents` ueber [fromVoucherPayload] oder direkt
47
+ // gesetzt wurde -- die gehaertete Umrechnung dort allein reicht nicht.
48
+ if (voucher.valueCents != null && (!Number.isFinite(voucher.valueCents) || voucher.valueCents <= 0)) {
39
49
  return false;
40
50
  }
41
51
  return true;
42
52
  }
43
53
  function fromVoucherPayload(payload) {
54
+ // Euro->Cent ueber die eine gehaertete Stelle im Paket ([euroToCents],
55
+ // money.ts) statt einer eigenen `Math.round(payload.value * 100)` --
56
+ // [euroToCents] prueft `Number.isFinite` und liefert fuer einen
57
+ // nicht-numerischen oder nicht-endlichen Wert `0` statt eines lautlosen
58
+ // `NaN`. Siehe auch [voucherIsValid]: `0` faellt dort ueber die
59
+ // "<= 0"-Pruefung, ein `NaN` waere durchgerutscht.
44
60
  const valueCents = payload.valueCents != null
45
61
  ? payload.valueCents
46
62
  : payload.value != null
47
- ? Math.round(payload.value * 100)
63
+ ? (0, money_js_1.euroToCents)(payload.value)
48
64
  : undefined;
49
65
  return {
50
66
  name: payload.name ?? undefined,