@kreiseck/kasseneck-api 0.6.46 → 0.6.50

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 (121) hide show
  1. package/README.md +100 -15
  2. package/dist/cjs/client/aufrufe.d.ts +1 -1
  3. package/dist/cjs/client/aufrufe.js +1 -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/models/cancellation.d.ts +28 -0
  12. package/dist/cjs/models/cancellation.js +31 -1
  13. package/dist/cjs/models/hobex-receipt.js +9 -2
  14. package/dist/cjs/models/index.d.ts +1 -1
  15. package/dist/cjs/models/index.js +3 -1
  16. package/dist/cjs/models/receipt-summary.d.ts +2 -0
  17. package/dist/cjs/models/receipt-summary.js +4 -1
  18. package/dist/cjs/models/receipt.js +8 -1
  19. package/dist/cjs/models/voucher.js +18 -2
  20. package/dist/cjs/payments/hobex-hps/connect-client.d.ts +104 -0
  21. package/dist/cjs/payments/hobex-hps/connect-client.js +159 -0
  22. package/dist/cjs/payments/hobex-hps/errors.d.ts +122 -0
  23. package/dist/cjs/payments/hobex-hps/errors.js +158 -0
  24. package/dist/cjs/payments/hobex-hps/events.d.ts +22 -0
  25. package/dist/cjs/payments/hobex-hps/events.js +2 -0
  26. package/dist/cjs/payments/hobex-hps/index.d.ts +27 -0
  27. package/dist/cjs/payments/hobex-hps/index.js +66 -0
  28. package/dist/cjs/payments/hobex-hps/outcome.d.ts +31 -0
  29. package/dist/cjs/payments/hobex-hps/outcome.js +7 -0
  30. package/dist/cjs/payments/hobex-hps/payments.d.ts +149 -0
  31. package/dist/cjs/payments/hobex-hps/payments.js +550 -0
  32. package/dist/cjs/payments/hobex-hps/receipt.d.ts +28 -0
  33. package/dist/cjs/payments/hobex-hps/receipt.js +60 -0
  34. package/dist/cjs/payments/hobex-hps/transaction-id.d.ts +65 -0
  35. package/dist/cjs/payments/hobex-hps/transaction-id.js +91 -0
  36. package/dist/cjs/payments/hobex-hps/transaction-response.d.ts +163 -0
  37. package/dist/cjs/payments/hobex-hps/transaction-response.js +245 -0
  38. package/dist/cjs/payments/hobex.js +9 -6
  39. package/dist/cjs/payments/index.d.ts +26 -14
  40. package/dist/cjs/payments/index.js +59 -15
  41. package/dist/cjs/printing/index.d.ts +1 -1
  42. package/dist/cjs/printing/index.js +2 -1
  43. package/dist/cjs/printing/webusb.d.ts +37 -7
  44. package/dist/cjs/printing/webusb.js +76 -18
  45. package/dist/cjs/receipt/epos.d.ts +18 -0
  46. package/dist/cjs/receipt/epos.js +78 -6
  47. package/dist/cjs/receipt/index.d.ts +1 -1
  48. package/dist/cjs/receipt/index.js +2 -1
  49. package/dist/cjs/receipt/layout.js +19 -0
  50. package/dist/cjs/register/index.d.ts +1 -1
  51. package/dist/cjs/register/index.js +2 -1
  52. package/dist/cjs/register/pairing.d.ts +59 -0
  53. package/dist/cjs/register/pairing.js +51 -2
  54. package/dist/esm/client/aufrufe.d.ts +1 -1
  55. package/dist/esm/client/aufrufe.js +1 -0
  56. package/dist/esm/client/errors.d.ts +7 -1
  57. package/dist/esm/client/errors.js +8 -1
  58. package/dist/esm/client/receipts.d.ts +16 -0
  59. package/dist/esm/client/receipts.js +10 -0
  60. package/dist/esm/client/transport.js +5 -4
  61. package/dist/esm/index.d.ts +1 -1
  62. package/dist/esm/index.js +1 -1
  63. package/dist/esm/models/cancellation.d.ts +28 -0
  64. package/dist/esm/models/cancellation.js +29 -0
  65. package/dist/esm/models/hobex-receipt.js +10 -3
  66. package/dist/esm/models/index.d.ts +1 -1
  67. package/dist/esm/models/index.js +1 -1
  68. package/dist/esm/models/receipt-summary.d.ts +2 -0
  69. package/dist/esm/models/receipt-summary.js +4 -1
  70. package/dist/esm/models/receipt.js +8 -1
  71. package/dist/esm/models/voucher.js +18 -2
  72. package/dist/esm/payments/hobex-hps/connect-client.d.ts +104 -0
  73. package/dist/esm/payments/hobex-hps/connect-client.js +156 -0
  74. package/dist/esm/payments/hobex-hps/errors.d.ts +122 -0
  75. package/dist/esm/payments/hobex-hps/errors.js +149 -0
  76. package/dist/esm/payments/hobex-hps/events.d.ts +22 -0
  77. package/dist/esm/payments/hobex-hps/events.js +1 -0
  78. package/dist/esm/payments/hobex-hps/index.d.ts +27 -0
  79. package/dist/esm/payments/hobex-hps/index.js +26 -0
  80. package/dist/esm/payments/hobex-hps/outcome.d.ts +31 -0
  81. package/dist/esm/payments/hobex-hps/outcome.js +4 -0
  82. package/dist/esm/payments/hobex-hps/payments.d.ts +149 -0
  83. package/dist/esm/payments/hobex-hps/payments.js +547 -0
  84. package/dist/esm/payments/hobex-hps/receipt.d.ts +28 -0
  85. package/dist/esm/payments/hobex-hps/receipt.js +57 -0
  86. package/dist/esm/payments/hobex-hps/transaction-id.d.ts +65 -0
  87. package/dist/esm/payments/hobex-hps/transaction-id.js +86 -0
  88. package/dist/esm/payments/hobex-hps/transaction-response.d.ts +163 -0
  89. package/dist/esm/payments/hobex-hps/transaction-response.js +233 -0
  90. package/dist/esm/payments/hobex.js +9 -6
  91. package/dist/esm/payments/index.d.ts +26 -14
  92. package/dist/esm/payments/index.js +26 -14
  93. package/dist/esm/printing/index.d.ts +1 -1
  94. package/dist/esm/printing/index.js +1 -1
  95. package/dist/esm/printing/webusb.d.ts +37 -7
  96. package/dist/esm/printing/webusb.js +74 -17
  97. package/dist/esm/receipt/epos.d.ts +18 -0
  98. package/dist/esm/receipt/epos.js +76 -6
  99. package/dist/esm/receipt/index.d.ts +1 -1
  100. package/dist/esm/receipt/index.js +1 -1
  101. package/dist/esm/receipt/layout.js +19 -0
  102. package/dist/esm/register/index.d.ts +1 -1
  103. package/dist/esm/register/index.js +1 -1
  104. package/dist/esm/register/pairing.d.ts +59 -0
  105. package/dist/esm/register/pairing.js +50 -2
  106. package/fixtures/belege/storno-rabatt.json +3 -2
  107. package/fixtures/belege/storno-teil.json +2 -1
  108. package/fixtures/belege/storno-voll.json +2 -1
  109. package/fixtures/erwartet/storno-rabatt.grid32.txt +1 -0
  110. package/fixtures/erwartet/storno-rabatt.grid48.txt +1 -0
  111. package/fixtures/erwartet/storno-rabatt.lines.json +6 -0
  112. package/fixtures/erwartet/storno-teil.grid32.txt +1 -0
  113. package/fixtures/erwartet/storno-teil.grid48.txt +1 -0
  114. package/fixtures/erwartet/storno-teil.lines.json +6 -0
  115. package/fixtures/erwartet/storno-voll.grid32.txt +1 -0
  116. package/fixtures/erwartet/storno-voll.grid48.txt +1 -0
  117. package/fixtures/erwartet/storno-voll.lines.json +6 -0
  118. package/fixtures/hobex-hps-codes.json +67 -0
  119. package/fixtures/manifest.json +12 -12
  120. package/fixtures/oberflaeche.json +2 -1
  121. package/package.json +4 -2
package/README.md CHANGED
@@ -167,6 +167,43 @@ 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
+
202
+ **Bon.** Der Kopfblock des Storno-Bons nennt Bezug, Datum des Originals und
203
+ Grund: „STORNOBELEG / Stornobuchung zu Beleg KASSE1-ID-42 / vom 11.08.2026,
204
+ 09:02 Uhr / Grund: Fehleingabe". Das Datum kommt aus `cancellationOf.timeStamp`
205
+ (Backend seit 2026-09-04); Altbelege ohne bleiben ohne die Zeile.
206
+
170
207
  ## Unterpfade
171
208
 
172
209
  | Unterpfad | Inhalt |
@@ -174,7 +211,7 @@ den Storno-Dialog); die Wahrheit hat der Server.
174
211
  | `@kreiseck/kasseneck-api` | Endpunkte, Anmeldung, Transport, Modelle, Enums, Fehler — alles, was mit dem Backend spricht. |
175
212
  | `…/receipt` | Beleg-Layout als Datenmodell (framework-frei) und die Brücke zu ESC/POS. |
176
213
  | `…/printing` | ESC/POS-Erzeugung: Bytefolgen für Bondrucker, ohne jeden Transport. |
177
- | `…/payments` | Stripe-Zahllinks und Hobex-Cloud (beides HTTP-Endpunkte des Backends). |
214
+ | `…/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
215
  | `…/register` | Anmeldung der Browser-Kasse: Gerät koppeln und entkoppeln, Benutzer auflisten, per PIN anmelden, Sitzung erneuern und beenden. |
179
216
  | `…/kasse` | Kachel-Kasse: Kassen-Einstellungen (betriebsweit / je Gerät), Artikelgruppen und Artikel für Kacheln, Rabattverteilung je Steuersatz, Reichweiten der Kassen-Rechte |
180
217
  | `…/react` | Dünner React-Adapter, der ein Beleg-Layout zeichnet. Braucht React. |
@@ -182,16 +219,40 @@ den Storno-Dialog); die Wahrheit hat der Server.
182
219
 
183
220
  So zieht sich niemand den React-Adapter in ein Node-Programm.
184
221
 
185
- ## Was hier grundsätzlich nicht dazugehört
222
+ ## Hobex HPS über Kasseneck Connect
223
+
224
+ Ein Browser hat weiterhin keine rohen TCP-Sockets — ein **direkter**
225
+ Terminal-Kontakt wie beim Flutter-Paket `kasseneck_api` (`HpsClient`) bleibt
226
+ deshalb außerhalb der Reichweite dieses Pakets. **Kasseneck Connect** ist aber
227
+ ein lokaler Geräte-Agent mit gewöhnlicher HTTP-Schnittstelle, der für die Kasse
228
+ mit dem Terminal spricht — und darüber geht es:
229
+
230
+ ```ts
231
+ import { createHpsConnectClient, createHpsPayments } from '@kreiseck/kasseneck-api/payments';
186
232
 
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`.
233
+ const client = createHpsConnectClient({ token: kopplungsToken });
234
+ const zahlweg = createHpsPayments(client, { host: '192.168.1.50', tid: '3600335' });
191
235
 
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.
236
+ const ergebnis = await zahlweg.pay({ amountCents: 1050 });
237
+ // ergebnis.outcome: 'approved' | 'declined' | 'unresolved' nie geraten.
238
+ // ergebnis.transactionId ist IMMER gesetzt, auch bei 'unresolved'.
239
+ ```
240
+
241
+ Der Ausgang ist immer einer von drei: `approved`, `declined` (beweisbar nichts
242
+ belastet) oder `unresolved` (Ausgang unbekannt, eine Wiederholung könnte ein
243
+ zweites Mal belasten). Was das bedeutet und warum es so gebaut ist, steht in
244
+ `src/payments/hobex-hps/payments.ts` — dort ist die Dokumentation der Maßstab,
245
+ nicht dieses README.
246
+
247
+ **Nur `pay` — bewusst kein `refund`/`cancel`.** Kasseneck Connect exponiert
248
+ dafür (noch) keinen Endpunkt; eine Gutschrift oder ein Storno am HPS-Terminal
249
+ braucht weiterhin die Flutter-App. **myPOS** und **SumUp** bleiben
250
+ Android-SDKs ohne Entsprechung hier.
251
+
252
+ ## Was hier grundsätzlich nicht dazugehört
253
+
254
+ Die Druckeransteuerung selbst (dieses Paket erzeugt die Bytes, es verschickt
255
+ sie nicht), Firmenlogos und Rasterbilder, und die PDF-Erzeugung.
195
256
 
196
257
  ## Entwicklung
197
258
 
@@ -199,28 +260,52 @@ PDF-Erzeugung.
199
260
  npm test # Testsuite in drei Zeitzonen (Wien, UTC, Kiritimati)
200
261
  npm run build # ESM- und CJS-Bau nach dist/, inkl. Prüfung der exports
201
262
  npm run check:consumer # baut den Tarball und übersetzt zwei Verbraucher (CJS/ESM)
263
+ npm run check:erreichbar # fragt die öffentliche Adresse: antwortet dort zu jedem Aufruf eine Function?
202
264
  ```
203
265
 
204
266
  Die drei Zeitzonen sind kein Übereifer: Zeitfehler sind auf einer Wiener
205
267
  Maschine zufällig richtig. Belegzeiten werden konsequent als **Wiener
206
268
  Wanduhrzeit** gedeutet (`parseServerTimeStamp`), nie über `new Date(text)`.
207
269
 
270
+ ### `check:erreichbar` — spricht als einzige mit `api.kasseneck.at`
271
+
272
+ Testsuite und `check:consumer` laufen gegen Attrappen bzw. gegen den Tarball;
273
+ keine von beiden setzt je einen Aufruf ab. Fehlt einem Aufruf die
274
+ Hosting-Weiterleitung, liefert die veröffentlichte Adresse die
275
+ HTML-Auffangseite statt der Function — und das sieht keine Attrappe.
276
+
277
+ Die Prüfung braucht **keine Zugangsdaten**. Ein Aufruf ohne Anmeldung
278
+ antwortet, wenn dort eine Function steht, mit
279
+ `{"status":"error","message":"Ungültiger Request: Authorization key erwartet."}`.
280
+ Genau das ist der Beweis: Der Aufruf wurde angenommen und die Anmeldung
281
+ geprüft. Eine HTML-Seite oder ein 404 ist der Beweis, dass dort keine Function
282
+ steht. Deshalb prüft das Skript auf ein `status`-Feld und nicht auf Erfolg.
283
+
284
+ Bewusst außerhalb von `npm test`: Sie braucht Netz. Ist keines da, sagt sie es
285
+ und endet mit 0. Aufrufe, die unter `/v1` absichtlich keine Weiterleitung
286
+ haben — der Kassen-Weg über `kasse.kasseneck.at/api`, die Aufrufe mit
287
+ Firebase-ID-Token — stehen mit Grund in `scripts/erreichbarkeit-ausnahmen.json`.
288
+ Wird eine Ausnahme erreichbar, schlägt die Prüfung an: Sonst sänke die Zahl nie.
289
+
208
290
  ## Vertragsdateien für die Zwillinge
209
291
 
210
292
  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:
293
+ Backend-Validator `kasse-settings-core.js`. Drei Dateien in `fixtures/` reisen
294
+ im Tarball mit und sagen in Maschinenform, worauf sich beide Seiten geeinigt haben:
213
295
 
214
296
  | Datei | Inhalt |
215
297
  |---|---|
216
298
  | `kasse-settings-standard.json` | Feldnamen und Standardwerte der Kassen-Einstellungen |
217
299
  | `oberflaeche.json` | Aufrufnamen, Enum-Werte, Rechte-Schlüssel, Tasten-Aktionen |
300
+ | `hobex-hps-codes.json` | Gemessene HPS-Ergebniscodes, ihre Bedeutung und ob sie einen Ausgang festschreiben — der Vertrag hinter `.../payments/hobex-hps`s `isConclusive`. |
218
301
 
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.
302
+ Alle drei werden erzeugt (`npm run fixtures:kasse`, `npm run fixtures:oberflaeche`,
303
+ `npm run fixtures:hobex-hps-codes`) und nie von Hand geändert; die CI prüft
304
+ nach jedem Lauf, dass sie zum Code passen.
221
305
 
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.
306
+ `oberflaeche.json` und `hobex-hps-codes.json` tragen die Paketversion. **Nach
307
+ jedem `npm version` müssen deshalb alle drei Dateien neu erzeugt und
308
+ mitcommittet werden**, sonst wird die CI rot.
224
309
 
225
310
  ## Lizenz
226
311
 
@@ -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", "listMyTipRecipients", "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>;
@@ -24,6 +24,7 @@ exports.AUFRUFE = [
24
24
  'listMyReceipts',
25
25
  'listMyTipRecipients',
26
26
  'listRegisterUsersForDevice',
27
+ 'listRegisterSessionsForDevice',
27
28
  'pairRegisterDevice',
28
29
  'registerPinLogin',
29
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; } });
@@ -18,10 +18,29 @@ 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;
24
37
  fullReceiptId: string | null;
38
+ /**
39
+ * Zeitstempel des Originals (Server-Format, Wiener Wanduhr) — das Layout
40
+ * nennt ihn im Kopfblock des Storno-Bons („vom 11.08.2026, 09:02 Uhr").
41
+ * Fehlt bei Altbelegen; dann bleibt die Zeile weg.
42
+ */
43
+ timeStamp?: string;
25
44
  }
26
45
  /** Eine stornierte Position: Index im Original und Menge. */
27
46
  export interface CancellationItem {
@@ -40,6 +59,15 @@ export interface Cancellation {
40
59
  by: string | null;
41
60
  note: string | null;
42
61
  items: CancellationItem[];
62
+ /**
63
+ * Rabattgutschein-Ausgleich, den DIESER Storno gewaehrt hat — Cent je
64
+ * Steuertopf des Backends (`amountRateStandard`, `amountRateReduced1`, …).
65
+ * Ein Rabattgutschein ist am Original schon in den Umsatz eingerechnet;
66
+ * jeder Storno nimmt ihn anteilig der stornierten Menge zurueck (bei 3 Stueck
67
+ * a 10 EUR mit 6 EUR Rabatt: 2,00 je Stueck). Fehlt das Feld, hat der Eintrag
68
+ * nichts gewaehrt (Altbestand vor dieser Regel) — der naechste Storno holt nach.
69
+ */
70
+ promoAdjustmentCents?: Record<string, number>;
43
71
  }
44
72
  /** Ab wann eine liegengebliebene Reservierung nicht mehr zaehlt (wie im Backend). */
45
73
  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; } });
@@ -56,6 +56,7 @@ export interface ReceiptSummary {
56
56
  /** Nur am Storno-Beleg: das Original. */
57
57
  cancellationOf?: {
58
58
  receiptId: string | null;
59
+ timeStamp?: string;
59
60
  };
60
61
  cancellationReason?: string;
61
62
  /** offen | teil | voll -- Storno-Stand des Originals. */
@@ -84,6 +85,7 @@ export interface ReceiptSummaryPayload {
84
85
  } | null;
85
86
  cancellationOf?: {
86
87
  receiptId?: string | null;
88
+ timeStamp?: string | null;
87
89
  } | null;
88
90
  cancellationReason?: string | null;
89
91
  stornoStand?: string | null;
@@ -46,7 +46,10 @@ function fromReceiptSummaryPayload(payload) {
46
46
  signatureOk: payload.signature_ok !== false,
47
47
  items: Array.isArray(payload.items) ? payload.items.map((i) => ({ name: i?.name ?? '', quantity: typeof i?.quantity === 'number' ? i.quantity : 0 })) : [],
48
48
  ...(payload.operator ? { operator: { uid: payload.operator.uid ?? null, name: payload.operator.name ?? '' } } : {}),
49
- ...(payload.cancellationOf ? { cancellationOf: { receiptId: payload.cancellationOf.receiptId ?? null } } : {}),
49
+ ...(payload.cancellationOf ? { cancellationOf: {
50
+ receiptId: payload.cancellationOf.receiptId ?? null,
51
+ ...(typeof payload.cancellationOf.timeStamp === 'string' && payload.cancellationOf.timeStamp ? { timeStamp: payload.cancellationOf.timeStamp } : {}),
52
+ } } : {}),
50
53
  ...(payload.cancellationReason ? { cancellationReason: payload.cancellationReason } : {}),
51
54
  stornoStand: payload.stornoStand === 'teil' || payload.stornoStand === 'voll' ? payload.stornoStand : 'offen',
52
55
  ...(istZeroKind(payload.zeroKind) ? { zeroKind: payload.zeroKind } : {}),
@@ -76,7 +76,11 @@ function fromReceiptPayload(payload) {
76
76
  signatureSuccess: payload.signatureSuccess ?? undefined,
77
77
  customProjectId: payload.customProjectId ?? undefined,
78
78
  ...(typeof payload.tipCents === 'number' ? { tipCents: payload.tipCents } : {}),
79
- ...(payload.cancellationOf ? { cancellationOf: { receiptId: payload.cancellationOf.receiptId, fullReceiptId: payload.cancellationOf.fullReceiptId ?? null } } : {}),
79
+ ...(payload.cancellationOf ? { cancellationOf: {
80
+ receiptId: payload.cancellationOf.receiptId,
81
+ fullReceiptId: payload.cancellationOf.fullReceiptId ?? null,
82
+ ...(typeof payload.cancellationOf.timeStamp === 'string' && payload.cancellationOf.timeStamp ? { timeStamp: payload.cancellationOf.timeStamp } : {}),
83
+ } } : {}),
80
84
  ...(payload.cancellationReason ? { cancellationReason: payload.cancellationReason } : {}),
81
85
  ...((0, receipt_summary_js_1.istZeroKind)(payload.zeroKind) ? { zeroKind: payload.zeroKind } : {}),
82
86
  ...(payload.cancellations ? { cancellations: payload.cancellations.map(leseStorno) } : {}),
@@ -90,6 +94,9 @@ function leseStorno(eintrag) {
90
94
  by: eintrag.by ?? null,
91
95
  note: eintrag.note ?? null,
92
96
  items: (eintrag.items ?? []).map((p) => ({ index: Number(p.index), quantity: Number(p.quantity) })),
97
+ ...(eintrag.promoAdjustmentCents && typeof eintrag.promoAdjustmentCents === 'object'
98
+ ? { promoAdjustmentCents: Object.fromEntries(Object.entries(eintrag.promoAdjustmentCents).map(([k, v]) => [k, Number(v)])) }
99
+ : {}),
93
100
  };
94
101
  }
95
102
  /**
@@ -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,