@kreiseck/kasseneck-api 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/CHANGELOG.md +245 -0
  2. package/dist/cjs/client/aufrufe.d.ts +5 -5
  3. package/dist/cjs/client/aufrufe.js +22 -2
  4. package/dist/cjs/client/errors.js +2 -0
  5. package/dist/cjs/client/receipts.d.ts +34 -1
  6. package/dist/cjs/client/receipts.js +34 -1
  7. package/dist/cjs/client/reports.d.ts +4 -4
  8. package/dist/cjs/client/reports.js +4 -4
  9. package/dist/cjs/index.d.ts +1 -1
  10. package/dist/cjs/index.js +9 -2
  11. package/dist/cjs/inventory/vertrag.d.ts +1 -1
  12. package/dist/cjs/inventory/vertrag.js +2 -0
  13. package/dist/cjs/invoice/vertrag.d.ts +1 -1
  14. package/dist/cjs/invoice/vertrag.js +2 -0
  15. package/dist/cjs/models/cancellation.d.ts +49 -1
  16. package/dist/cjs/models/cancellation.js +139 -1
  17. package/dist/cjs/models/index.d.ts +1 -1
  18. package/dist/cjs/models/index.js +9 -2
  19. package/dist/cjs/models/payment-errors.d.ts +1 -1
  20. package/dist/cjs/models/payment-errors.js +2 -0
  21. package/dist/cjs/models/receipt-email.d.ts +1 -1
  22. package/dist/cjs/models/receipt-email.js +2 -0
  23. package/dist/cjs/models/receipt-errors.d.ts +1 -1
  24. package/dist/cjs/models/receipt-errors.js +2 -0
  25. package/dist/cjs/models/receipt-item.d.ts +10 -0
  26. package/dist/cjs/models/receipt-item.js +3 -0
  27. package/dist/cjs/pos/client.js +10 -0
  28. package/dist/cjs/pos/errors.d.ts +4 -3
  29. package/dist/cjs/pos/errors.js +21 -2
  30. package/dist/cjs/pos/index.d.ts +6 -2
  31. package/dist/cjs/pos/index.js +33 -3
  32. package/dist/cjs/pos/schicht-lesen.d.ts +39 -0
  33. package/dist/cjs/pos/schicht-lesen.js +405 -0
  34. package/dist/cjs/pos/schicht-typen.d.ts +380 -0
  35. package/dist/cjs/pos/schicht-typen.js +59 -0
  36. package/dist/cjs/pos/schicht.d.ts +208 -0
  37. package/dist/cjs/pos/schicht.js +235 -0
  38. package/dist/cjs/pos/settings.d.ts +37 -1
  39. package/dist/cjs/pos/settings.js +43 -3
  40. package/dist/cjs/pos/texte.d.ts +627 -0
  41. package/dist/cjs/pos/texte.js +236 -0
  42. package/dist/cjs/react/index.js +1 -1
  43. package/dist/cjs/receipt/grid.js +2 -2
  44. package/dist/cjs/receipt/layout.d.ts +5 -0
  45. package/dist/cjs/receipt/shift-sheet.d.ts +33 -0
  46. package/dist/cjs/receipt/shift-sheet.js +207 -0
  47. package/dist/cjs/register/errors.d.ts +1 -1
  48. package/dist/cjs/register/errors.js +2 -0
  49. package/dist/cjs/stored/draht.d.ts +1 -1
  50. package/dist/cjs/stored/draht.js +4 -3
  51. package/dist/cjs/stored/einstellungen.js +26 -3
  52. package/dist/cjs/stored/vokabular.d.ts +1 -1
  53. package/dist/cjs/stored/vokabular.js +118 -12
  54. package/dist/cjs/version.d.ts +1 -1
  55. package/dist/cjs/version.js +1 -1
  56. package/dist/esm/client/aufrufe.d.ts +5 -5
  57. package/dist/esm/client/aufrufe.js +22 -2
  58. package/dist/esm/client/errors.js +2 -0
  59. package/dist/esm/client/receipts.d.ts +34 -1
  60. package/dist/esm/client/receipts.js +35 -2
  61. package/dist/esm/client/reports.d.ts +4 -4
  62. package/dist/esm/client/reports.js +4 -4
  63. package/dist/esm/index.d.ts +1 -1
  64. package/dist/esm/index.js +1 -1
  65. package/dist/esm/inventory/vertrag.d.ts +1 -1
  66. package/dist/esm/inventory/vertrag.js +2 -0
  67. package/dist/esm/invoice/vertrag.d.ts +1 -1
  68. package/dist/esm/invoice/vertrag.js +2 -0
  69. package/dist/esm/models/cancellation.d.ts +49 -1
  70. package/dist/esm/models/cancellation.js +132 -0
  71. package/dist/esm/models/index.d.ts +1 -1
  72. package/dist/esm/models/index.js +1 -1
  73. package/dist/esm/models/payment-errors.d.ts +1 -1
  74. package/dist/esm/models/payment-errors.js +2 -0
  75. package/dist/esm/models/receipt-email.d.ts +1 -1
  76. package/dist/esm/models/receipt-email.js +2 -0
  77. package/dist/esm/models/receipt-errors.d.ts +1 -1
  78. package/dist/esm/models/receipt-errors.js +2 -0
  79. package/dist/esm/models/receipt-item.d.ts +10 -0
  80. package/dist/esm/models/receipt-item.js +3 -0
  81. package/dist/esm/pos/client.js +11 -1
  82. package/dist/esm/pos/errors.d.ts +4 -3
  83. package/dist/esm/pos/errors.js +21 -2
  84. package/dist/esm/pos/index.d.ts +6 -2
  85. package/dist/esm/pos/index.js +7 -2
  86. package/dist/esm/pos/schicht-lesen.d.ts +39 -0
  87. package/dist/esm/pos/schicht-lesen.js +392 -0
  88. package/dist/esm/pos/schicht-typen.d.ts +380 -0
  89. package/dist/esm/pos/schicht-typen.js +56 -0
  90. package/dist/esm/pos/schicht.d.ts +208 -0
  91. package/dist/esm/pos/schicht.js +222 -0
  92. package/dist/esm/pos/settings.d.ts +37 -1
  93. package/dist/esm/pos/settings.js +40 -2
  94. package/dist/esm/pos/texte.d.ts +627 -0
  95. package/dist/esm/pos/texte.js +236 -0
  96. package/dist/esm/react/index.js +1 -1
  97. package/dist/esm/receipt/grid.js +2 -2
  98. package/dist/esm/receipt/layout.d.ts +5 -0
  99. package/dist/esm/receipt/shift-sheet.d.ts +33 -0
  100. package/dist/esm/receipt/shift-sheet.js +203 -0
  101. package/dist/esm/register/errors.d.ts +1 -1
  102. package/dist/esm/register/errors.js +2 -0
  103. package/dist/esm/stored/draht.d.ts +1 -1
  104. package/dist/esm/stored/draht.js +4 -3
  105. package/dist/esm/stored/einstellungen.js +27 -4
  106. package/dist/esm/stored/vokabular.d.ts +1 -1
  107. package/dist/esm/stored/vokabular.js +118 -12
  108. package/dist/esm/version.d.ts +1 -1
  109. package/dist/esm/version.js +1 -1
  110. package/fixtures/cancellation-tip-cases.json +679 -0
  111. package/fixtures/expected/cash-movement.grid32.txt +15 -0
  112. package/fixtures/expected/cash-movement.grid48.txt +15 -0
  113. package/fixtures/expected/cash-movement.lines.json +171 -0
  114. package/fixtures/expected/shift-close-blind.grid32.txt +28 -0
  115. package/fixtures/expected/shift-close-blind.grid48.txt +27 -0
  116. package/fixtures/expected/shift-close-blind.lines.json +275 -0
  117. package/fixtures/expected/shift-close-put-in.grid32.txt +38 -0
  118. package/fixtures/expected/shift-close-put-in.grid48.txt +35 -0
  119. package/fixtures/expected/shift-close-put-in.lines.json +403 -0
  120. package/fixtures/expected/shift-close-take-out.grid32.txt +38 -0
  121. package/fixtures/expected/shift-close-take-out.grid48.txt +35 -0
  122. package/fixtures/expected/shift-close-take-out.lines.json +403 -0
  123. package/fixtures/expected/shift-close.grid32.txt +37 -0
  124. package/fixtures/expected/shift-close.grid48.txt +35 -0
  125. package/fixtures/expected/shift-close.lines.json +413 -0
  126. package/fixtures/expected/shift-interim.grid32.txt +28 -0
  127. package/fixtures/expected/shift-interim.grid48.txt +27 -0
  128. package/fixtures/expected/shift-interim.lines.json +293 -0
  129. package/fixtures/hobex-hps-codes.json +1 -1
  130. package/fixtures/invoice-api.schema.json +1 -1
  131. package/fixtures/invoice-texts.json +1 -1
  132. package/fixtures/pos-message-cases.json +45 -0
  133. package/fixtures/pos-texts.json +723 -1
  134. package/fixtures/receipt-due-generated.json +1 -1
  135. package/fixtures/shift-cases.json +87 -0
  136. package/fixtures/shift-receipts.json +41 -0
  137. package/fixtures/surface.json +119 -3
  138. package/fixtures/v3/antworten/belege.json +1 -1
  139. package/fixtures/v3/antworten/belegmail.json +1 -1
  140. package/fixtures/v3/antworten/kasse-belege.json +1 -1
  141. package/fixtures/v3/antworten/kasse.json +10874 -4865
  142. package/fixtures/v3/antworten/lager.json +1 -1
  143. package/fixtures/v3/antworten/rechnungen.json +1 -1
  144. package/fixtures/v3/antworten/storno.json +1 -1
  145. package/fixtures/v3/stored/belege.json +18 -6
  146. package/fixtures/v3/stored/kasse.json +4667 -183
  147. package/fixtures/v3/stored/rechnungen.json +1 -1
  148. package/fixtures/v3/v3-vokabular.json +739 -32
  149. package/fixtures/v3/zahlbetrag-faelle.json +1 -1
  150. package/package.json +1 -1
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getMyDrawerState = getMyDrawerState;
4
+ exports.openMyShift = openMyShift;
5
+ exports.recordMyCashMovement = recordMyCashMovement;
6
+ exports.logMyDrawerOpenings = logMyDrawerOpenings;
7
+ exports.getMyShiftReport = getMyShiftReport;
8
+ exports.closeMyShift = closeMyShift;
9
+ exports.sumDenominations = sumDenominations;
10
+ exports.parseCentsAllowZero = parseCentsAllowZero;
11
+ exports.newIdempotencyKey = newIdempotencyKey;
12
+ exports.shiftTipLines = shiftTipLines;
13
+ exports.durationText = durationText;
14
+ const errors_js_1 = require("../client/errors.js");
15
+ const schreiben_js_1 = require("../inventory/schreiben.js");
16
+ const schicht_lesen_js_1 = require("./schicht-lesen.js");
17
+ const texte_js_1 = require("./texte.js");
18
+ const schicht_typen_js_1 = require("./schicht-typen.js");
19
+ const antwort = (name) => ({ name, pfad: '', scope: 'response' });
20
+ function anfragefehler(name, grund) {
21
+ return new errors_js_1.KasseneckValidationError(name, grund, 'request');
22
+ }
23
+ /** Eine Kennung, die gesendet werden muss. */
24
+ function kennung(name, p, feld) {
25
+ const w = p[feld];
26
+ if (typeof w !== 'string' || w.trim() === '')
27
+ throw anfragefehler(name, `${feld} fehlt`);
28
+ }
29
+ /** Ein Betrag in Cent: ganze Zahl, sonst wirft der Aufruf vor dem Senden. */
30
+ function cent(name, feld, w) {
31
+ if (typeof w !== 'number' || !Number.isSafeInteger(w))
32
+ throw anfragefehler(name, `${feld} muss eine ganze Zahl (Cent) sein`);
33
+ }
34
+ function stueckelungPruefen(name, p) {
35
+ const w = p.denominations;
36
+ if (w === undefined || w === null)
37
+ return;
38
+ if (typeof w !== 'object' || Array.isArray(w))
39
+ throw anfragefehler(name, 'denominations muss eine Karte Wert -> Anzahl sein');
40
+ for (const [k, anzahl] of Object.entries(w))
41
+ cent(name, `denominations.${k}`, anzahl);
42
+ }
43
+ async function rufe(transport, name, p, lese) {
44
+ return lese(antwort(name), await transport(name, p));
45
+ }
46
+ /** Stand der Lade: Rechte, offene Schicht, Trinkgeld-Ansprueche und (mit `cashView`) Soll und letzte Oeffnungen. */
47
+ async function getMyDrawerState(transport, options = {}) {
48
+ const name = 'getMyDrawerState';
49
+ const p = (0, schreiben_js_1.anfrage)(name, options);
50
+ if (p.drawerId === '')
51
+ delete p.drawerId;
52
+ return rufe(transport, name, p, schicht_lesen_js_1.ladeStand);
53
+ }
54
+ /** Schicht oeffnen: gezaehltes Startgeld oder den Bestand uebernehmen (Recht `shift`). */
55
+ async function openMyShift(transport, options) {
56
+ const name = 'openMyShift';
57
+ const p = (0, schreiben_js_1.anfrage)(name, options);
58
+ kennung(name, p, 'drawerId');
59
+ (0, schreiben_js_1.schluessel)(name, p, { pflicht: true });
60
+ if (p.openingFloatCents !== undefined)
61
+ cent(name, 'openingFloatCents', p.openingFloatCents);
62
+ stueckelungPruefen(name, p);
63
+ return rufe(transport, name, p, schicht_lesen_js_1.geoeffnet);
64
+ }
65
+ /** Ein- oder Auszahlung buchen (Recht `cashMove`); Antwort mit den Bon-Daten „Kassenbewegung“. */
66
+ async function recordMyCashMovement(transport, options) {
67
+ const name = 'recordMyCashMovement';
68
+ const p = (0, schreiben_js_1.anfrage)(name, options);
69
+ kennung(name, p, 'drawerId');
70
+ (0, schreiben_js_1.schluessel)(name, p, { pflicht: true });
71
+ cent(name, 'amountCents', p.amountCents);
72
+ // Nur pruefen, wenn gesetzt: Bank/Tresor fragt die Kasse nicht mehr ab.
73
+ if (p.location !== undefined && !schicht_typen_js_1.CASH_LOCATIONS.includes(p.location)) {
74
+ throw anfragefehler(name, `location muss ${schicht_typen_js_1.CASH_LOCATIONS.join(' oder ')} sein`);
75
+ }
76
+ return rufe(transport, name, p, schicht_lesen_js_1.gebucht);
77
+ }
78
+ /** Oeffnungen der Lade melden (auch nachgesendet); doppelt gemeldete zaehlen einmal. */
79
+ async function logMyDrawerOpenings(transport, options) {
80
+ const name = 'logMyDrawerOpenings';
81
+ const p = (0, schreiben_js_1.anfrage)(name, options);
82
+ kennung(name, p, 'drawerId');
83
+ const eintraege = p.entries;
84
+ if (!Array.isArray(eintraege) || eintraege.length === 0)
85
+ throw anfragefehler(name, 'entries muss 1–50 Eintraege haben');
86
+ eintraege.forEach((e, i) => {
87
+ const k = e !== null && typeof e === 'object' ? e.idempotencyKey : undefined;
88
+ if (typeof k !== 'string' || k.trim() === '')
89
+ throw anfragefehler(name, `entries[${i}].idempotencyKey fehlt`);
90
+ });
91
+ return rufe(transport, name, p, schicht_lesen_js_1.gemeldet);
92
+ }
93
+ /** Bericht der Schicht: Schicht, Bewegungen und Bon-Daten (X-Bon, bei geschlossener Schicht der Z-Bon). */
94
+ async function getMyShiftReport(transport, options) {
95
+ const name = 'getMyShiftReport';
96
+ const p = (0, schreiben_js_1.anfrage)(name, options);
97
+ kennung(name, p, 'shiftId');
98
+ return rufe(transport, name, p, schicht_lesen_js_1.bericht);
99
+ }
100
+ /**
101
+ * Schicht schliessen (Recht `shift`). `status: 'recount'` heisst: bitte noch
102
+ * einmal zaehlen und erneut senden (kein Fehler); nach dem zweiten Zaehlen
103
+ * schliesst der Server, bei grosser Abweichung nur mit `comment`. Mit
104
+ * `checkOnly` nur pruefen: `status: 'ready'` statt Schliessen.
105
+ */
106
+ async function closeMyShift(transport, options) {
107
+ const name = 'closeMyShift';
108
+ const p = (0, schreiben_js_1.anfrage)(name, options);
109
+ kennung(name, p, 'shiftId');
110
+ (0, schreiben_js_1.schluessel)(name, p, { pflicht: true });
111
+ cent(name, 'countedCents', p.countedCents);
112
+ stueckelungPruefen(name, p);
113
+ if (p.tipPayouts !== undefined && p.tipPayouts !== null) {
114
+ if (!Array.isArray(p.tipPayouts))
115
+ throw anfragefehler(name, 'tipPayouts muss eine Liste sein');
116
+ p.tipPayouts.forEach((a, i) => cent(name, `tipPayouts[${i}].cents`, a !== null && typeof a === 'object' ? a.cents : undefined));
117
+ }
118
+ if (p.checkOnly !== undefined && typeof p.checkOnly !== 'boolean')
119
+ throw anfragefehler(name, 'checkOnly muss true oder false sein');
120
+ // Nur die Form: die Art (Katalog) und die Grenzen prueft der Server.
121
+ if (p.afterCount !== undefined && p.afterCount !== null) {
122
+ if (typeof p.afterCount !== 'object' || Array.isArray(p.afterCount))
123
+ throw anfragefehler(name, 'afterCount muss ein Objekt { type, amountCents } sein');
124
+ cent(name, 'afterCount.amountCents', p.afterCount.amountCents);
125
+ }
126
+ return rufe(transport, name, p, schicht_lesen_js_1.abschluss);
127
+ }
128
+ // ---- Helfer der Kasse ---------------------------------------------------------
129
+ const ANZAHL_MAX = 100000;
130
+ const WERTE = new Set(schicht_typen_js_1.EUR_DENOMINATIONS.map(String));
131
+ /**
132
+ * Summe einer Stueckelung in Cent, oder `null`, wenn sie keine ist: ein
133
+ * Schluessel, der kein Euro-Wert ist ([EUR_DENOMINATIONS], genau so
134
+ * geschrieben, `"500"`, nicht `"0500"`), eine Anzahl, die keine ganze Zahl
135
+ * von 0 bis 100 000 ist. Dieselbe Regel wie am Server
136
+ * (`invalid_denominations`). Gemeinsame Faelle mit dem Dart-Zwilling:
137
+ * `fixtures/shift-cases.json`.
138
+ */
139
+ function sumDenominations(denominations) {
140
+ if (denominations === null || typeof denominations !== 'object' || Array.isArray(denominations))
141
+ return null;
142
+ let summe = 0;
143
+ for (const [wert, anzahl] of Object.entries(denominations)) {
144
+ if (!WERTE.has(wert))
145
+ return null;
146
+ if (typeof anzahl !== 'number' || !Number.isSafeInteger(anzahl) || anzahl < 0 || anzahl > ANZAHL_MAX)
147
+ return null;
148
+ summe += Number(wert) * anzahl;
149
+ }
150
+ return summe;
151
+ }
152
+ /**
153
+ * Ein eingetippter Betrag in Cent, **ohne Gleitkomma**: `'12,5'` → `1250`,
154
+ * `'0'` → `0` (anders als beim Kassieren ist 0 hier gueltig: eine leere Lade,
155
+ * kein Startgeld). Dezimaltrenner Komma oder Punkt mit einer oder zwei
156
+ * Nachkommastellen; `null` fuer leer, Vorzeichen, Tausendertrenner
157
+ * (`'1.000'`, `'1 000'`), drei Nachkommastellen, Euro-Zeichen und alles
158
+ * ueber einer sicheren Ganzzahl. Gemeinsame Faelle mit dem Dart-Zwilling:
159
+ * `fixtures/shift-cases.json`.
160
+ */
161
+ function parseCentsAllowZero(text) {
162
+ if (typeof text !== 'string')
163
+ return null;
164
+ const m = /^(\d+)(?:[.,](\d{1,2}))?$/.exec(text.trim());
165
+ if (!m)
166
+ return null;
167
+ const cents = BigInt(m[1]) * 100n + BigInt((m[2] ?? '').padEnd(2, '0'));
168
+ return cents <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(cents) : null;
169
+ }
170
+ /**
171
+ * Ein frischer Idempotenzschluessel fuer einen neuen Vorgang: `kasse-` und
172
+ * 32 Hex-Zeichen aus dem sicheren Zufall (`crypto.getRandomValues`), dieselbe
173
+ * Form wie beim Zaehlen der Inventur in der Kassen-App. Fuer „Erneut senden“
174
+ * nie einen neuen ziehen, sondern den alten behalten.
175
+ */
176
+ function newIdempotencyKey() {
177
+ const zufall = globalThis.crypto;
178
+ if (!zufall || typeof zufall.getRandomValues !== 'function') {
179
+ throw new errors_js_1.KasseneckValidationError('newIdempotencyKey', 'kein sicherer Zufall vorhanden (crypto.getRandomValues)', 'request');
180
+ }
181
+ const bytes = zufall.getRandomValues(new Uint8Array(16));
182
+ return `kasse-${Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('')}`;
183
+ }
184
+ /**
185
+ * Was vom Trinkgeld eines Mitarbeiters dasteht – auf dem Bon, im Kassenstand
186
+ * und beim Abschluss gleich (Rulings R50/R51): „verdient“ (sofort + über
187
+ * Lohn) immer, „über Lohn“, „schon genommen“, „selbst zurückgegeben“ (R62)
188
+ * und „offen“ nur, wenn nicht 0. Davor „aus früheren Schichten“ (R64, nur ≠ 0):
189
+ * offen = aus früheren Schichten + verdient − über Lohn − schon genommen +
190
+ * selbst zurückgegeben.
191
+ * Ist alles 0, gibt es keine Zeile, und der Mitarbeiter entfaellt ganz. Ohne
192
+ * `openCents` (Laufstand) zaehlt offen als 0. Gemeinsame Faelle mit dem
193
+ * Dart-Zwilling: `fixtures/shift-cases.json`.
194
+ */
195
+ function shiftTipLines(tip) {
196
+ const verdient = tip.receivedImmediatelyCents + tip.retainedCents;
197
+ const vortrag = tip.carriedOverCents ?? 0;
198
+ const zeilen = [
199
+ ['shift_receipt.tip_retained', tip.retainedCents],
200
+ ['shift_receipt.tip_paid_out', tip.paidOutCents],
201
+ ['shift_receipt.tip_refunded_by_employee', tip.refundedByEmployeeCents ?? 0],
202
+ ['shift_receipt.tip_open', tip.openCents ?? 0],
203
+ ];
204
+ const rest = zeilen.filter(([, cents]) => cents !== 0);
205
+ if (verdient === 0 && rest.length === 0 && vortrag === 0)
206
+ return [];
207
+ const davor = vortrag !== 0 ? [['shift_receipt.tip_carried_over', vortrag]] : [];
208
+ return [...davor, ['shift_receipt.tip_earned', verdient], ...rest].map(([key, cents]) => ({ key, text: (0, texte_js_1.labelText)(key), cents }));
209
+ }
210
+ /**
211
+ * Dauer für Menschen aus ganzen Minuten: unter 1 Std. „45 Min.“, unter 1 Tag
212
+ * „8 Std. 32 Min.“ (bei 0 Minuten nur „8 Std.“), ab 1 Tag „1 Tag 3 Std.“ bzw.
213
+ * „2 Tage“ (bei 0 Stunden ohne Stunden). Negatives und Unsinn zählt als 0;
214
+ * Bruchteile werden abgeschnitten. Gemeinsame Fälle mit dem Dart-Zwilling:
215
+ * `fixtures/shift-cases.json` (`durationText`).
216
+ */
217
+ function durationText(minutes) {
218
+ const gesamt = Number.isFinite(minutes) && minutes > 0 ? Math.floor(minutes) : 0;
219
+ const tage = Math.floor(gesamt / 1440);
220
+ const stunden = Math.floor((gesamt % 1440) / 60);
221
+ const rest = gesamt % 60;
222
+ const teile = [];
223
+ if (tage > 0) {
224
+ teile.push((0, texte_js_1.labelText)(tage === 1 ? 'duration.days_one' : 'duration.days_other', { days: tage }));
225
+ if (stunden > 0)
226
+ teile.push((0, texte_js_1.labelText)('duration.hours', { hours: stunden }));
227
+ }
228
+ else {
229
+ if (stunden > 0)
230
+ teile.push((0, texte_js_1.labelText)('duration.hours', { hours: stunden }));
231
+ if (rest > 0 || stunden === 0)
232
+ teile.push((0, texte_js_1.labelText)('duration.minutes', { minutes: rest }));
233
+ }
234
+ return teile.join(' ');
235
+ }
@@ -189,6 +189,13 @@ export interface PosBusinessSettings {
189
189
  glass: boolean;
190
190
  /** Hilfetexte in den Chef-Einstellungen anzeigen. */
191
191
  hints: boolean;
192
+ /**
193
+ * Bar-Trinkgeld behaelt der Mitarbeiter gleich: Vorbelegung des Schalters
194
+ * „gleich behalten“ beim Kassieren (je Verkauf aenderbar). Optional: der
195
+ * Server sendet das Feld nur, wenn es gespeichert ist; fehlt es, gilt aus.
196
+ * Lesen ueber [posTipKeptByDefault]. Siehe [POS_BUSINESS_OPTIONAL_FIELDS].
197
+ */
198
+ tipKeptByDefault?: boolean;
192
199
  }
193
200
  /** Aktionen der Kasse, die eine Taste bekommen koennen (Schluessel von `device.shortcuts`). */
194
201
  export declare const POS_SHORTCUT_ACTIONS: readonly ["checkout", "complete", "cancel", "customAmount", "cash", "card", "exactAmount", "receipts", "undoLast", "settings", "logout", "tip", "fullscreen", "clearTendered", "clearCart", "splitPayment"];
@@ -241,6 +248,13 @@ export interface PosDeviceSettings {
241
248
  qrMode: PosOpen<PosQrMode>;
242
249
  drawerEnabled: boolean;
243
250
  drawerAutoOpen: PosOpen<PosDrawerAutoOpen>;
251
+ /**
252
+ * Lade dieses Geraets (Schichtbetrieb). Optional: der Server sendet das
253
+ * Feld nur, wenn eine Lade zugeordnet ist; fehlt es, gilt die Hauptlade der
254
+ * Kasse. Beim Schreiben entfernt `null` die Zuordnung. Siehe
255
+ * [POS_DEVICE_OPTIONAL_FIELDS].
256
+ */
257
+ drawerId?: string | null;
244
258
  terminalIp: string;
245
259
  terminalPort: number;
246
260
  terminalVia: PosOpen<PosTerminalVia>;
@@ -257,6 +271,25 @@ export interface PosSettings {
257
271
  }
258
272
  export declare const POS_BUSINESS_DEFAULTS: Readonly<PosBusinessSettings>;
259
273
  export declare const POS_DEVICE_DEFAULTS: Readonly<PosDeviceSettings>;
274
+ /**
275
+ * Optionale Geraete-Einstellungen: ohne Standardwert, der Server sendet sie
276
+ * nur, wenn sie gesetzt sind (Backend `GERAET_OPTIONAL`). Sie fehlen darum in
277
+ * [POS_DEVICE_DEFAULTS]; [mergePosSettings] uebernimmt sie, wenn sie als
278
+ * Kennung kommen, und der Schreibweg nimmt sie als Kennung oder `null`
279
+ * (Zuordnung entfernen).
280
+ */
281
+ export declare const POS_DEVICE_OPTIONAL_FIELDS: readonly ["drawerId"];
282
+ /** Gilt dieser Wert fuer ein optionales Feld? Lesen: nur eine Kennung; Schreiben: auch `null`. */
283
+ export declare function _optionalerWert(wert: unknown, schreiben: boolean): boolean;
284
+ /**
285
+ * Optionale Betriebs-Einstellungen (Schalter): ohne Standardwert, der Server
286
+ * sendet sie nur, wenn sie gespeichert sind (Backend `BETRIEB_OPTIONAL`).
287
+ * [mergePosSettings] uebernimmt sie nur als Wahrheitswert; der Schreibweg
288
+ * nimmt nur `true`/`false`.
289
+ */
290
+ export declare const POS_BUSINESS_OPTIONAL_FIELDS: readonly ["tipKeptByDefault"];
291
+ /** Bar-Trinkgeld „gleich behalten“ vorbelegt? Fehlt die Einstellung, gilt aus. */
292
+ export declare function posTipKeptByDefault(business: Readonly<Partial<PosBusinessSettings>> | null | undefined): boolean;
260
293
  /**
261
294
  * Die Wertemenge je Feld, soweit das Feld eine hat: dagegen prueft der
262
295
  * Schreibweg vor dem Senden (ein deutscher Wert wie `'nacht'` geht so gar
@@ -275,7 +308,10 @@ export declare const _ALTFORM_0X: Readonly<{
275
308
  /**
276
309
  * Standard + stored. Landkarten (`vatRates`, `tipSteps`, `shortcuts`)
277
310
  * werden je Schluessel gemischt, damit neue Saetze beim Altbestand ankommen;
278
- * Schluessel, die der Standard nicht fuehrt, bleiben draussen.
311
+ * Schluessel, die der Standard nicht fuehrt, bleiben draussen, ausser einer
312
+ * optionalen Geraete-Einstellung ([POS_DEVICE_OPTIONAL_FIELDS]) mit gueltiger
313
+ * Kennung und einem optionalen Betriebs-Schalter
314
+ * ([POS_BUSINESS_OPTIONAL_FIELDS]) als Wahrheitswert.
279
315
  *
280
316
  * **Immer** (unabhaengig davon, welches Standard-Objekt uebergeben wird, auch
281
317
  * eine Kopie oder das einer zweiten Paketkopie) faellt ein Wert der inneren
@@ -13,8 +13,10 @@
13
13
  * bis die Buchung geklaert ist (Spec kachel-kasse § 9).
14
14
  */
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
- exports._ALTFORM_0X = exports.POS_DEVICE_VALUES = exports.POS_BUSINESS_VALUES = exports.POS_DEVICE_DEFAULTS = exports.POS_BUSINESS_DEFAULTS = exports.POS_SHORTCUT_SHARED_PAIRS = exports.POS_SHORTCUT_DEFAULTS = exports.POS_SHORTCUT_ACTIONS = exports.DONE_SCREEN_SECONDS = exports.WATERMARK_SCALE = exports.LOGO_SCALE = exports.WATERMARK_STRENGTH = exports.WATERMARK_SIDE = exports.DISCOUNT = exports.AUTO_LOGOUT_MINUTES = exports.TILE_STYLE = exports.SETTINGS_FONT_SIZE = exports.LOGO_SIZE = exports.QR_MODE = exports.DRAWER_AUTO_OPEN = exports.CUT = exports.CODE_PAGE = exports.PAPER_SIZE = exports.TERMINAL_TYPE = exports.TERMINAL_VIA = exports.PRINTER_TYPE = exports.TILE_HEIGHT = exports.CATEGORY_POSITION = exports.LAYOUT = exports.RECEIPT_OUTPUT = exports.CARD_PROVIDER = exports.CHECKOUT_MODE = exports.TIP_MODE = exports.QUANTITY = exports.WATERMARK = exports.FONT_SIZE = exports.THEME = void 0;
16
+ exports._ALTFORM_0X = exports.POS_DEVICE_VALUES = exports.POS_BUSINESS_VALUES = exports.POS_BUSINESS_OPTIONAL_FIELDS = exports.POS_DEVICE_OPTIONAL_FIELDS = exports.POS_DEVICE_DEFAULTS = exports.POS_BUSINESS_DEFAULTS = exports.POS_SHORTCUT_SHARED_PAIRS = exports.POS_SHORTCUT_DEFAULTS = exports.POS_SHORTCUT_ACTIONS = exports.DONE_SCREEN_SECONDS = exports.WATERMARK_SCALE = exports.LOGO_SCALE = exports.WATERMARK_STRENGTH = exports.WATERMARK_SIDE = exports.DISCOUNT = exports.AUTO_LOGOUT_MINUTES = exports.TILE_STYLE = exports.SETTINGS_FONT_SIZE = exports.LOGO_SIZE = exports.QR_MODE = exports.DRAWER_AUTO_OPEN = exports.CUT = exports.CODE_PAGE = exports.PAPER_SIZE = exports.TERMINAL_TYPE = exports.TERMINAL_VIA = exports.PRINTER_TYPE = exports.TILE_HEIGHT = exports.CATEGORY_POSITION = exports.LAYOUT = exports.RECEIPT_OUTPUT = exports.CARD_PROVIDER = exports.CHECKOUT_MODE = exports.TIP_MODE = exports.QUANTITY = exports.WATERMARK = exports.FONT_SIZE = exports.THEME = void 0;
17
17
  exports.posShortcutConflict = posShortcutConflict;
18
+ exports._optionalerWert = _optionalerWert;
19
+ exports.posTipKeptByDefault = posTipKeptByDefault;
18
20
  exports._istAltwert0x = _istAltwert0x;
19
21
  exports.mergePosSettings = mergePosSettings;
20
22
  exports.sanitizePosSettings = sanitizePosSettings;
@@ -182,6 +184,33 @@ exports.POS_DEVICE_DEFAULTS = Object.freeze({
182
184
  terminalIp: '', terminalPort: 8080, terminalTid: '', terminalVia: 'direct',
183
185
  terminalType: 'none', shortcutHints: true,
184
186
  });
187
+ /**
188
+ * Optionale Geraete-Einstellungen: ohne Standardwert, der Server sendet sie
189
+ * nur, wenn sie gesetzt sind (Backend `GERAET_OPTIONAL`). Sie fehlen darum in
190
+ * [POS_DEVICE_DEFAULTS]; [mergePosSettings] uebernimmt sie, wenn sie als
191
+ * Kennung kommen, und der Schreibweg nimmt sie als Kennung oder `null`
192
+ * (Zuordnung entfernen).
193
+ */
194
+ exports.POS_DEVICE_OPTIONAL_FIELDS = Object.freeze(['drawerId']);
195
+ /** Form einer optionalen Kennung (Backend `ladeKennung`). */
196
+ const OPTIONALE_KENNUNG = /^[A-Za-z0-9_-]{1,64}$/;
197
+ /** Gilt dieser Wert fuer ein optionales Feld? Lesen: nur eine Kennung; Schreiben: auch `null`. */
198
+ function _optionalerWert(wert, schreiben) {
199
+ return (schreiben && wert === null) || (typeof wert === 'string' && OPTIONALE_KENNUNG.test(wert));
200
+ }
201
+ const OPTIONALE = new Set(exports.POS_DEVICE_OPTIONAL_FIELDS);
202
+ /**
203
+ * Optionale Betriebs-Einstellungen (Schalter): ohne Standardwert, der Server
204
+ * sendet sie nur, wenn sie gespeichert sind (Backend `BETRIEB_OPTIONAL`).
205
+ * [mergePosSettings] uebernimmt sie nur als Wahrheitswert; der Schreibweg
206
+ * nimmt nur `true`/`false`.
207
+ */
208
+ exports.POS_BUSINESS_OPTIONAL_FIELDS = Object.freeze(['tipKeptByDefault']);
209
+ const OPTIONALE_SCHALTER = new Set(exports.POS_BUSINESS_OPTIONAL_FIELDS);
210
+ /** Bar-Trinkgeld „gleich behalten“ vorbelegt? Fehlt die Einstellung, gilt aus. */
211
+ function posTipKeptByDefault(business) {
212
+ return !!business && business.tipKeptByDefault === true;
213
+ }
185
214
  /**
186
215
  * Die Wertemenge je Feld, soweit das Feld eine hat: dagegen prueft der
187
216
  * Schreibweg vor dem Senden (ein deutscher Wert wie `'nacht'` geht so gar
@@ -243,7 +272,10 @@ exports._ALTFORM_0X = Object.freeze({ werte: ALTWERTE_0X, aktionen: [...ALTAKTIO
243
272
  /**
244
273
  * Standard + stored. Landkarten (`vatRates`, `tipSteps`, `shortcuts`)
245
274
  * werden je Schluessel gemischt, damit neue Saetze beim Altbestand ankommen;
246
- * Schluessel, die der Standard nicht fuehrt, bleiben draussen.
275
+ * Schluessel, die der Standard nicht fuehrt, bleiben draussen, ausser einer
276
+ * optionalen Geraete-Einstellung ([POS_DEVICE_OPTIONAL_FIELDS]) mit gueltiger
277
+ * Kennung und einem optionalen Betriebs-Schalter
278
+ * ([POS_BUSINESS_OPTIONAL_FIELDS]) als Wahrheitswert.
247
279
  *
248
280
  * **Immer** (unabhaengig davon, welches Standard-Objekt uebergeben wird, auch
249
281
  * eine Kopie oder das einer zweiten Paketkopie) faellt ein Wert der inneren
@@ -256,8 +288,16 @@ function mergePosSettings(standard, stored) {
256
288
  const out = JSON.parse(JSON.stringify(standard));
257
289
  if (stored && typeof stored === 'object') {
258
290
  for (const key of Object.keys(stored)) {
259
- if (!eigen(out, key))
291
+ if (!eigen(out, key)) {
292
+ // Optionale Geraete-Einstellung (ohne Standardwert): nur als Kennung;
293
+ // optionaler Betriebs-Schalter: nur als Wahrheitswert.
294
+ const wert = stored[key];
295
+ if (OPTIONALE.has(key) && _optionalerWert(wert, false))
296
+ out[key] = wert;
297
+ if (OPTIONALE_SCHALTER.has(key) && typeof wert === 'boolean')
298
+ out[key] = wert;
260
299
  continue;
300
+ }
261
301
  const wert = stored[key];
262
302
  const alt = out[key];
263
303
  if (alt && typeof alt === 'object' && !Array.isArray(alt) && wert && typeof wert === 'object' && !Array.isArray(wert)) {