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