@kreiseck/kasseneck-api 0.7.2 → 0.9.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 (55) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/cjs/kasse/index.d.ts +1 -0
  3. package/dist/cjs/kasse/index.js +7 -1
  4. package/dist/cjs/kasse/texte.d.ts +336 -0
  5. package/dist/cjs/kasse/texte.js +150 -0
  6. package/dist/cjs/receipt/layout.d.ts +11 -5
  7. package/dist/cjs/receipt/layout.js +248 -26
  8. package/dist/esm/kasse/index.d.ts +1 -0
  9. package/dist/esm/kasse/index.js +2 -0
  10. package/dist/esm/kasse/texte.d.ts +336 -0
  11. package/dist/esm/kasse/texte.js +145 -0
  12. package/dist/esm/receipt/layout.d.ts +11 -5
  13. package/dist/esm/receipt/layout.js +247 -26
  14. package/fixtures/belege/karte-eigener.json +44 -0
  15. package/fixtures/belege/karte-gptom-ios.json +59 -0
  16. package/fixtures/belege/karte-gptom.json +59 -0
  17. package/fixtures/belege/karte-hobex-cloud.json +54 -0
  18. package/fixtures/belege/karte-hobex-hps.json +54 -0
  19. package/fixtures/belege/karte-mypos.json +55 -0
  20. package/fixtures/belege/karte-stripe-eps.json +52 -0
  21. package/fixtures/belege/karte-stripe.json +57 -0
  22. package/fixtures/belege/karte-sumup.json +53 -0
  23. package/fixtures/erwartet/karte-eigener.grid32.txt +22 -0
  24. package/fixtures/erwartet/karte-eigener.grid48.txt +22 -0
  25. package/fixtures/erwartet/karte-eigener.lines.json +216 -0
  26. package/fixtures/erwartet/karte-gptom-ios.grid32.txt +34 -0
  27. package/fixtures/erwartet/karte-gptom-ios.grid48.txt +34 -0
  28. package/fixtures/erwartet/karte-gptom-ios.lines.json +286 -0
  29. package/fixtures/erwartet/karte-gptom.grid32.txt +34 -0
  30. package/fixtures/erwartet/karte-gptom.grid48.txt +34 -0
  31. package/fixtures/erwartet/karte-gptom.lines.json +286 -0
  32. package/fixtures/erwartet/karte-hobex-cloud.grid32.txt +29 -0
  33. package/fixtures/erwartet/karte-hobex-cloud.grid48.txt +29 -0
  34. package/fixtures/erwartet/karte-hobex-cloud.lines.json +256 -0
  35. package/fixtures/erwartet/karte-hobex-hps.grid32.txt +33 -0
  36. package/fixtures/erwartet/karte-hobex-hps.grid48.txt +33 -0
  37. package/fixtures/erwartet/karte-hobex-hps.lines.json +272 -0
  38. package/fixtures/erwartet/karte-mypos.grid32.txt +38 -0
  39. package/fixtures/erwartet/karte-mypos.grid48.txt +36 -0
  40. package/fixtures/erwartet/karte-mypos.lines.json +353 -0
  41. package/fixtures/erwartet/karte-stripe-eps.grid32.txt +28 -0
  42. package/fixtures/erwartet/karte-stripe-eps.grid48.txt +28 -0
  43. package/fixtures/erwartet/karte-stripe-eps.lines.json +250 -0
  44. package/fixtures/erwartet/karte-stripe.grid32.txt +31 -0
  45. package/fixtures/erwartet/karte-stripe.grid48.txt +31 -0
  46. package/fixtures/erwartet/karte-stripe.lines.json +268 -0
  47. package/fixtures/erwartet/karte-sumup.grid32.txt +32 -0
  48. package/fixtures/erwartet/karte-sumup.grid48.txt +30 -0
  49. package/fixtures/erwartet/karte-sumup.lines.json +316 -0
  50. package/fixtures/hobex-hps-codes.json +1 -1
  51. package/fixtures/kasse-meldungen-faelle.json +32 -0
  52. package/fixtures/kasse-texte.json +385 -0
  53. package/fixtures/manifest.json +54 -0
  54. package/fixtures/oberflaeche.json +1 -1
  55. package/package.json +3 -2
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SMALL_BUSINESS_NOTICE = exports.AKTUELLES_REGELWERK = void 0;
4
4
  exports.formatCents = formatCents;
5
+ exports.stripeReceiptLines = stripeReceiptLines;
5
6
  exports.receiptSignatureFailed = receiptSignatureFailed;
6
7
  exports.receiptSignatureIsTest = receiptSignatureIsTest;
7
8
  exports.receiptIsZero = receiptIsZero;
@@ -71,6 +72,36 @@ function originalDatum(timeStamp) {
71
72
  * `typeof null === 'object'` ist, ergab das beim Lesen von `.label` einen
72
73
  * nackten TypeError.
73
74
  */
75
+ /**
76
+ * Die anbieterspezifischen Kartenzahlungsbloecke.
77
+ *
78
+ * WARUM DIESE BLOECKE HIER STEHEN
79
+ *
80
+ * Bis 0.8.0 trug dieses Modell nur den Hobex-Block, mit der Begruendung, die
81
+ * uebrigen Anbieter druckten "in ihren eigenen Apps". Das Argument gilt fuer
82
+ * die Frage, wer das TERMINAL bedient -- nicht dafuer, was auf dem
83
+ * BELEGDOKUMENT steht. Aus der einen Regel wurde die andere, und damit
84
+ * verschwanden vier Bloecke: Wer denselben Beleg als PDF oeffnete, sah "nur
85
+ * Kartenzahlung", waehrend der Bon daneben Marke, letzte Ziffern, Betrag und
86
+ * Referenz auswies. Vorher (backend `generateReceiptPdf`) hatte das PDF alle
87
+ * sieben Anbieter.
88
+ *
89
+ * Gebaut werden die Zeilen deshalb hier, an genau EINER Stelle. Wer sie
90
+ * anderswo noch einmal baut, oeffnet die Drift wieder.
91
+ *
92
+ * Vorbild ist `print_paper.dart` im Dart-Paket (`_gpTom`, `_hobexHps`,
93
+ * `_sumup`, `_mypos`, `_stripe`); die Fixtures unter `fixtures/belege/karte-*`
94
+ * halten je Anbieter eine Zeile-fuer-Zeile-Probe fest.
95
+ */
96
+ /** Ein Feld aus `cardPaymentData` als Text; fehlend/null wird leer. */
97
+ function kartenFeld(daten, schluessel) {
98
+ const v = daten[schluessel];
99
+ return v == null ? '' : String(v);
100
+ }
101
+ /** Nicht-leere Teile mit Mittelpunkt verbinden. */
102
+ function verbunden(teile) {
103
+ return teile.filter(Boolean).join(' · ');
104
+ }
74
105
  /**
75
106
  * Kartenzahlungsblock fuer Hobex (HPS am Geraet wie Cloud-API): die Felder
76
107
  * kommen aus `cardPaymentData` in der Form von [hobexReceiptToCardPaymentData]
@@ -78,43 +109,234 @@ function originalDatum(timeStamp) {
78
109
  * Antwortcode. Bei CVM "1" verlangt die Karte eine Unterschrift; dafuer
79
110
  * bekommt der Bon eine Linie. Der PAN haengt beim HPS das Ablaufdatum mit
80
111
  * Unterstrich an ("…4720_2810") — gedruckt wird nur die maskierte Nummer.
81
- *
82
- * Andere Anbieter (GP Tom, SumUp, myPOS, Stripe) drucken in ihren eigenen
83
- * Apps; ihre Bloecke bleiben bewusst draussen (siehe Kopfkommentar).
84
112
  */
85
- function hobexKartenblock(receipt) {
86
- const provider = receipt.creditCardProvider;
87
- const daten = receipt.cardPaymentData;
88
- if ((provider !== 'hobexHps' && provider !== 'hobexCloudApi') || daten == null) {
89
- return [];
90
- }
91
- const feld = (k) => {
92
- const v = daten[k];
93
- return v == null ? '' : String(v);
94
- };
95
- const paar = (teile) => teile.filter(Boolean).join(' · ');
96
- const pan = feld('cardNumber').split('_')[0] ?? '';
97
- const rc = feld('responseCode');
113
+ function hobexBlock(daten) {
114
+ const feld = (k) => kartenFeld(daten, k);
98
115
  const zeilen = [textZeile('Hobex Beleg', 'center', true)];
99
116
  const inhalt = [
100
117
  feld('date'),
101
- paar([feld('tid') && `TID ${feld('tid')}`, feld('no') && `Nr. ${feld('no')}`]),
102
- paar([feld('type'), feld('cardBrand')]),
103
- pan,
104
- rc && `RC ${rc}`,
118
+ verbunden([feld('tid') && `TID ${feld('tid')}`, feld('no') && `Nr. ${feld('no')}`]),
119
+ verbunden([feld('type'), feld('cardBrand')]),
120
+ feld('cardNumber').split('_')[0] ?? '',
121
+ feld('responseCode') && `RC ${feld('responseCode')}`,
122
+ ];
123
+ for (const zeile of inhalt) {
124
+ if (zeile)
125
+ zeilen.push(textZeile(zeile, 'center'));
126
+ }
127
+ if (feld('cvm') === '1')
128
+ zeilen.push(...unterschriftsfeld());
129
+ return zeilen;
130
+ }
131
+ /** Zwei Leerzeilen, Linie, Wort -- das Unterschriftsfeld auf dem Bon. */
132
+ function unterschriftsfeld() {
133
+ return [
134
+ { kind: 'space', lines: 2 },
135
+ textZeile('------------------', 'center'),
136
+ textZeile('Unterschrift', 'center'),
137
+ ];
138
+ }
139
+ /**
140
+ * GP-Tom-`transactionType`: das Plugin-`toMap` schreibt den Schluessel mit
141
+ * Tippfehler (`transacitonType`), aeltere Daten tragen ihn gar nicht. Beide
142
+ * Schreibweisen werden gelesen -- ein Beleg von damals soll nicht anders
143
+ * aussehen als einer von heute.
144
+ */
145
+ function gpTomVorgangsart(daten) {
146
+ switch (daten.transactionType ?? daten.transacitonType) {
147
+ case 1: return 'Sale';
148
+ case 2: return 'Void';
149
+ case 3: return 'Refund';
150
+ case 4: return 'Close Batch';
151
+ default: return '';
152
+ }
153
+ }
154
+ /** GP-Tom-Betrag: ganze Zahl = Cent, sonst schon ein Eurobetrag. */
155
+ function gpTomBetrag(wert) {
156
+ if (typeof wert !== 'number')
157
+ return '-';
158
+ return Number.isInteger(wert) ? formatCents(wert) : wert.toFixed(2).replace('.', ',');
159
+ }
160
+ function gpTomBlock(daten) {
161
+ const feld = (k) => kartenFeld(daten, k);
162
+ const art = gpTomVorgangsart(daten);
163
+ const zeilen = [textZeile('GP Tom Beleg', 'center', true)];
164
+ const inhalt = [
165
+ `Batch: ${feld('batchNumber')}`,
166
+ `Receipt: ${feld('externalTransactionID')}`,
167
+ `TID: ${feld('terminalID')}`,
168
+ feld('emvAid'),
169
+ (daten.emvAppLable != null || daten.cardDataEntry != null)
170
+ ? `${feld('emvAppLable')} ${feld('cardDataEntry')}`.trim() : '',
171
+ feld('cardNumber'),
172
+ `${art ? `${art} ` : ''}Amount ${feld('currencyCode')} ${gpTomBetrag(daten.amount)}`,
173
+ daten.pinOk === true ? 'PIN OK' : 'PIN NOT OK',
174
+ `Authorization Code ${feld('approvedCode')}`,
175
+ `Sequence Number: ${feld('sequenceNumber')}`,
105
176
  ];
106
177
  for (const zeile of inhalt) {
107
178
  if (zeile)
108
179
  zeilen.push(textZeile(zeile, 'center'));
109
180
  }
110
- if (feld('cvm') === '1') {
111
- zeilen.push({ kind: 'space', lines: 2 });
112
- zeilen.push(textZeile('------------------', 'center'));
113
- zeilen.push(textZeile('Unterschrift', 'center'));
181
+ return zeilen;
182
+ }
183
+ function sumupBlock(daten) {
184
+ const feld = (k) => kartenFeld(daten, k);
185
+ const betrag = typeof daten.amount === 'number'
186
+ ? (Number.isInteger(daten.amount) ? formatCents(daten.amount) : daten.amount.toFixed(2).replace('.', ','))
187
+ : '-';
188
+ return [
189
+ textZeile('Sumup Beleg', 'center', true),
190
+ paarZeile('Kartentyp:', feld('cardType') || 'n/a'),
191
+ paarZeile('Kartennummer:', `**** **** **** ${feld('cardLastDigits')}`),
192
+ paarZeile('Zahlungstyp:', feld('paymentType') || 'n/a'),
193
+ paarZeile('Gesamtbetrag:', `${betrag} ${feld('currency')}`.trim()),
194
+ paarZeile('Transaktionscode:', feld('transactionCode') || '-'),
195
+ paarZeile('Modus:', feld('entryMode').toUpperCase()),
196
+ ];
197
+ }
198
+ /**
199
+ * myPOS liefert den Zeitpunkt als `YYMMDDhhmmss` ohne Trenner. Ein Wert, der
200
+ * nicht so aussieht, wird UNVERAENDERT durchgereicht statt zerschnitten: eine
201
+ * halb geratene Uhrzeit auf einem Beleg ist schlimmer als ein roher Wert.
202
+ */
203
+ function myposZeitpunkt(wert) {
204
+ const t = /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})$/.exec(wert);
205
+ if (!t)
206
+ return wert;
207
+ return `${t[3]}.${t[2]}.20${t[1]} ${t[4]}:${t[5]}:${t[6]}`;
208
+ }
209
+ function myposBlock(daten) {
210
+ const feld = (k) => kartenFeld(daten, k);
211
+ const zeilen = [
212
+ textZeile('MyPos Beleg', 'center', true),
213
+ paarZeile('TERMINAL ID:', feld('TID') || '-'),
214
+ paarZeile('DATUM:', myposZeitpunkt(feld('date_time'))),
215
+ ];
216
+ if (feld('application_name'))
217
+ zeilen.push(textZeile(feld('application_name'), 'center', true));
218
+ zeilen.push(paarZeile('KARTE:', feld('pan') || '-'));
219
+ if (daten.signature_required === true)
220
+ zeilen.push(...unterschriftsfeld());
221
+ zeilen.push(paarZeile('STAN:', feld('STAN') ? feld('STAN').padStart(6, '0') : '-'));
222
+ zeilen.push(paarZeile('AUTH. CODE:', feld('authorization_code') || '-'));
223
+ zeilen.push(paarZeile('RRN:', feld('reference_number') || '-'));
224
+ zeilen.push(paarZeile('AID:', feld('AID') || '-'));
225
+ return zeilen;
226
+ }
227
+ const STRIPE_KARTENART = {
228
+ debit: 'Debitkarte',
229
+ credit: 'Kreditkarte',
230
+ prepaid: 'Prepaid-Karte',
231
+ };
232
+ const STRIPE_WALLET = {
233
+ apple_pay: 'Apple Pay',
234
+ google_pay: 'Google Pay',
235
+ };
236
+ /** EPS-Bank-Slug (`bank_austria`) -> Anzeigename (`Bank Austria`). */
237
+ function stripeEpsBank(slug) {
238
+ return slug.split('_').filter(Boolean)
239
+ .map((w) => w[0].toUpperCase() + w.slice(1)).join(' ');
240
+ }
241
+ function stripeMarkenZeile(daten) {
242
+ const teile = [];
243
+ if (daten.cardBrand != null)
244
+ teile.push(String(daten.cardBrand));
245
+ const art = STRIPE_KARTENART[String(daten.cardFunding)];
246
+ if (art)
247
+ teile.push(art);
248
+ let zeile = teile.join(' ');
249
+ if (daten.wallet != null) {
250
+ const wallet = STRIPE_WALLET[String(daten.wallet)] ?? String(daten.wallet);
251
+ zeile = zeile ? `${zeile} (${wallet})` : `(${wallet})`;
252
+ }
253
+ return zeile;
254
+ }
255
+ /**
256
+ * Stripe-Zahlzeitpunkt: Sekunden seit Epoche, gedruckt in **Wiener** Zeit.
257
+ * Ueber `toViennaWallClock`, nicht ueber die lokale Zeitzone -- ein Beleg
258
+ * traegt Ortszeit des Betriebs, nicht die des Geraets, das ihn oeffnet.
259
+ */
260
+ function stripeZeitpunkt(sekunden) {
261
+ const t = (0, vienna_time_js_1.toViennaWallClock)(new Date(sekunden * 1000));
262
+ const zwei = (n) => String(n).padStart(2, '0');
263
+ return `${zwei(t.day)}.${zwei(t.month)}.${t.year} ${zwei(t.hour)}:${zwei(t.minute)}`;
264
+ }
265
+ /** Zwilling von `stripeReceiptLines` (print_paper.dart). */
266
+ function stripeReceiptLines(daten, cardPaymentId) {
267
+ const zeilen = [];
268
+ const art = daten.paymentMethodType == null ? null : String(daten.paymentMethodType);
269
+ if (art === 'card') {
270
+ const marke = stripeMarkenZeile(daten);
271
+ if (marke)
272
+ zeilen.push(marke);
273
+ if (daten.cardLastDigits != null)
274
+ zeilen.push(`**** **** **** ${daten.cardLastDigits}`);
275
+ if (daten.threeDSecure === 'authenticated')
276
+ zeilen.push('3-D Secure: ja');
277
+ }
278
+ else if (art === 'eps') {
279
+ if (daten.epsBank != null)
280
+ zeilen.push(`EPS - ${stripeEpsBank(String(daten.epsBank))}`);
281
+ }
282
+ else if (art != null) {
283
+ zeilen.push(art.toUpperCase());
284
+ }
285
+ if (typeof daten.amount === 'number' && Number.isInteger(daten.amount)) {
286
+ const waehrung = daten.currency == null ? 'EUR' : String(daten.currency).toUpperCase();
287
+ zeilen.push(`Gesamtbetrag ${formatCents(daten.amount)} ${waehrung}`);
288
+ }
289
+ if (typeof daten.paidAt === 'number' && Number.isInteger(daten.paidAt)) {
290
+ zeilen.push(`Bezahlt: ${stripeZeitpunkt(daten.paidAt)}`);
291
+ }
292
+ if (daten.statementDescriptor != null)
293
+ zeilen.push(`Abrechnung: ${daten.statementDescriptor}`);
294
+ if (cardPaymentId != null)
295
+ zeilen.push(`Referenz: ${cardPaymentId}`);
296
+ return zeilen;
297
+ }
298
+ function stripeBlock(daten, cardPaymentId) {
299
+ const zeilen = [textZeile('Online-Zahlung (Stripe)', 'center', true)];
300
+ for (const zeile of stripeReceiptLines(daten, cardPaymentId)) {
301
+ zeilen.push(textZeile(zeile, 'center'));
114
302
  }
115
- zeilen.push({ kind: 'space', lines: 1 });
116
303
  return zeilen;
117
304
  }
305
+ /**
306
+ * Der Kartenzahlungsblock des Belegs -- fuer JEDEN Anbieter, den das Backend
307
+ * kennt. Ohne `cardPaymentData` bleibt der Block weg; ein unbekannter Anbieter
308
+ * ebenfalls, still und ohne Fehler: ein Beleg muss sich immer zeigen lassen.
309
+ */
310
+ function kartenblock(receipt) {
311
+ const daten = receipt.cardPaymentData;
312
+ if (daten == null)
313
+ return [];
314
+ const zeilen = (() => {
315
+ switch (receipt.creditCardProvider) {
316
+ case 'hobexHps':
317
+ case 'hobexCloudApi':
318
+ return hobexBlock(daten);
319
+ case 'gpTomAndroid':
320
+ case 'gpTomIos':
321
+ return gpTomBlock(daten);
322
+ case 'sumup':
323
+ return sumupBlock(daten);
324
+ case 'myposPro':
325
+ return myposBlock(daten);
326
+ case 'stripe':
327
+ return stripeBlock(daten, receipt.cardPaymentId ?? null);
328
+ default:
329
+ return [];
330
+ }
331
+ })();
332
+ if (zeilen.length === 0)
333
+ return [];
334
+ // Eine Leerzeile hinter JEDEM Block, nicht nur hinter manchen. Das Vorbild
335
+ // ist hier uneinheitlich (nur `_sumup` setzt am Ende ein `addFeed()`), was
336
+ // den Abstand zur Fusszeile vom Anbieter abhaengig machte. Die Leerzeile
337
+ // gehoert aber zum Blatt, nicht zum Terminal.
338
+ return [...zeilen, { kind: 'space', lines: 1 }];
339
+ }
118
340
  function zahlungsartText(wert) {
119
341
  if (wert != null && typeof wert === 'object') {
120
342
  return wert.label;
@@ -708,7 +930,7 @@ function buildReceiptLayout(receipt, company, options = {}) {
708
930
  lines.push({ kind: 'qr', data: receipt.qr });
709
931
  lines.push({ kind: 'space', lines: 1 });
710
932
  // --- Kartenzahlungsblock (Hobex) — wie im Vorbild UNTER dem QR-Code.
711
- lines.push(...hobexKartenblock(receipt));
933
+ lines.push(...kartenblock(receipt));
712
934
  // --- Dankestext und Fusszeilen
713
935
  if (company.thanksMessage.length > 0) {
714
936
  lines.push({ kind: 'space', lines: 1 });
@@ -10,3 +10,4 @@ export { verteileRabatt } from '../receipt/discount.js';
10
10
  export { cancelScopeOf, receiptsScopeOf, type RegisterScope, type RegisterUserPerms } from '../register/pairing.js';
11
11
  export { type NetzDrucker, type DruckJob, type DruckJobStatus, type CreatePrintJobOptions, listMyPrinters, createPrintJob, getPrintJob } from './drucker.js';
12
12
  export { listMyTipRecipients } from './trinkgeld.js';
13
+ export { MELDUNGEN, FEHLERREGELN, meldung, meldungGiltFuer, type Meldung, type MeldungsSchluessel, type Fehlerart, type Seite, } from './texte.js';
@@ -14,3 +14,5 @@ export { verteileRabatt } from '../receipt/discount.js';
14
14
  export { cancelScopeOf, receiptsScopeOf } from '../register/pairing.js';
15
15
  export { listMyPrinters, createPrintJob, getPrintJob } from './drucker.js';
16
16
  export { listMyTipRecipients } from './trinkgeld.js';
17
+ // Was die Kasse selbst sagt — ein Katalog fuer Browser-Kasse und App.
18
+ export { MELDUNGEN, FEHLERREGELN, meldung, meldungGiltFuer, } from './texte.js';
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Was die Kasse selbst sagt — auf dem Bildschirm, in beiden Huellen.
3
+ *
4
+ * Die Browser-Kasse und die Kassen-App sind eine Kasse. Was der Kassier bei
5
+ * einem Fehler liest, steht hier genau einmal; beide Apps beziehen den Satz
6
+ * von hier, und wer ihn aendert, aendert ihn auf beiden Seiten oder gar nicht.
7
+ *
8
+ * Nicht hier: die fachlichen Saetze des Backends („Kopplungs-Code ist
9
+ * abgelaufen.", „PIN falsch, noch 2 Versuche"). Die gehen unveraendert durch.
10
+ *
11
+ * Ein Satz sagt, was los ist, und was zu tun ist. Nie Innereien.
12
+ *
13
+ * Schluessel: `bereich.name`. Ein Schluessel wird nie umgedeutet — wer den
14
+ * Sinn aendert, legt einen neuen an. `nur` nennt die Seite, wenn ein Satz nur
15
+ * auf einer Plattform vorkommen kann.
16
+ */
17
+ export type Seite = 'web' | 'app';
18
+ export interface Meldung {
19
+ readonly text: string;
20
+ /** Erlaubte Platzhalter `{name}`; muessen exakt die im Text sein. */
21
+ readonly platzhalter?: readonly string[];
22
+ /** Fehlt es, gilt der Satz fuer beide Seiten. */
23
+ readonly nur?: readonly Seite[];
24
+ }
25
+ declare const MELDUNGEN_ROH: {
26
+ readonly 'netz.keine_verbindung': {
27
+ readonly text: "Keine Verbindung zum Server. Bitte die Internetverbindung prüfen und erneut versuchen.";
28
+ };
29
+ readonly 'netz.zeitablauf': {
30
+ readonly text: "Der Server antwortet nicht. Bitte die Internetverbindung prüfen und erneut versuchen.";
31
+ };
32
+ readonly 'server.unerwartet': {
33
+ readonly text: "Der Server hat unerwartet geantwortet (HTTP {status}). Bitte den Support verständigen.";
34
+ readonly platzhalter: readonly ["status"];
35
+ };
36
+ readonly 'kopplung.code_fehlt': {
37
+ readonly text: "Bitte den Kopplungs-Code eingeben.";
38
+ };
39
+ readonly 'kopplung.fehlgeschlagen': {
40
+ readonly text: "Die Kopplung ist fehlgeschlagen. Bitte erneut versuchen.";
41
+ };
42
+ readonly 'kopplung.unvollstaendig': {
43
+ readonly text: "Die Kopplung ist unvollständig zurückgekommen. Bitte im Panel einen neuen Code erzeugen und noch einmal versuchen.";
44
+ };
45
+ readonly 'geraet.browser_speicher': {
46
+ readonly text: "Dieses Gerät konnte nicht gespeichert werden — der Browser-Speicher steht nicht zur Verfügung. Bitte den privaten Modus verlassen.";
47
+ readonly nur: readonly ["web"];
48
+ };
49
+ readonly 'anmeldung.pin_fehlt': {
50
+ readonly text: "Bitte die PIN eingeben.";
51
+ };
52
+ readonly 'anmeldung.fehlgeschlagen': {
53
+ readonly text: "Die Anmeldung ist fehlgeschlagen. Bitte erneut versuchen.";
54
+ };
55
+ readonly 'anmeldung.nicht_abgeschlossen': {
56
+ readonly text: "Die Anmeldung konnte nicht abgeschlossen werden. Bitte erneut versuchen.";
57
+ };
58
+ readonly 'anmeldung.wird_abgeschlossen': {
59
+ readonly text: "Anmeldung wird gerade abgeschlossen — bitte gleich noch einmal drücken.";
60
+ };
61
+ readonly 'sitzung.abgelaufen': {
62
+ readonly text: "Die Sitzung ist abgelaufen. Bitte erneut anmelden.";
63
+ };
64
+ readonly 'sitzung.abgelaufen_ohne_netz': {
65
+ readonly text: "Keine Verbindung zum Server — die Sitzung ist abgelaufen. Bitte erneut anmelden.";
66
+ };
67
+ readonly 'sitzung.keine': {
68
+ readonly text: "Keine Sitzung.";
69
+ };
70
+ readonly 'rechte.nicht_aenderbar': {
71
+ readonly text: "Das darfst du nicht ändern — das Recht dafür vergibt der Inhaber im Panel.";
72
+ };
73
+ readonly 'kasse.laden_fehlgeschlagen': {
74
+ readonly text: "Die Kasse konnte nicht geladen werden.";
75
+ };
76
+ readonly 'artikel.laden_fehlgeschlagen': {
77
+ readonly text: "Die Artikel konnten nicht geladen werden.";
78
+ };
79
+ readonly 'artikel.keine_freigegeben': {
80
+ readonly text: "Keine Artikel an dieser Kasse. Im Panel unter „Kacheln & Gruppen“ freischalten.";
81
+ };
82
+ readonly 'artikel.keine_gefunden': {
83
+ readonly text: "Keine Artikel gefunden.";
84
+ };
85
+ readonly 'artikel.steuersatz_unbekannt': {
86
+ readonly text: "„{name}“ hat einen Steuersatz, den die Kasse nicht kennt.";
87
+ readonly platzhalter: readonly ["name"];
88
+ };
89
+ readonly 'position.betrag_fehlt': {
90
+ readonly text: "Bitte einen Betrag eingeben.";
91
+ };
92
+ readonly 'position.bezeichnung_fehlt': {
93
+ readonly text: "Bitte eine Bezeichnung eingeben — sie steht am Beleg.";
94
+ };
95
+ readonly 'rabatt.prozent_ungueltig': {
96
+ readonly text: "Bitte einen Prozentwert zwischen 0,1 und 100 eingeben.";
97
+ };
98
+ readonly 'abschluss.fehlgeschlagen': {
99
+ readonly text: "Der Abschluss ist fehlgeschlagen.";
100
+ };
101
+ readonly 'abschluss.unklar': {
102
+ readonly text: "Unklar, ob der Beleg entstanden ist — die Antwort kam nicht an. Bitte nicht noch einmal abschließen: der Beleg kann bereits erstellt und signiert sein. Im Panel unter „Belege“ nachsehen; nur wenn er dort fehlt, den Verkauf erneut abschließen.";
103
+ };
104
+ readonly 'abschluss.nullbeleg_fehlgeschlagen': {
105
+ readonly text: "Der Nullbeleg konnte nicht erstellt werden.";
106
+ };
107
+ readonly 'abschluss.signatur_ausgefallen': {
108
+ readonly text: "Die Signatureinheit hat nicht geantwortet. Der Beleg ist gültig und trägt den Vermerk „Sicherheitseinrichtung ausgefallen“.";
109
+ };
110
+ readonly 'kartenzahlung.nicht_moeglich': {
111
+ readonly text: "Kartenzahlung nicht möglich.";
112
+ };
113
+ readonly 'kartenzahlung.nicht_zustande': {
114
+ readonly text: "Die Kartenzahlung ist nicht zustande gekommen.";
115
+ };
116
+ readonly 'kartenzahlung.nicht_gestartet': {
117
+ readonly text: "Kartenzahlung nicht gestartet: {grund}";
118
+ readonly platzhalter: readonly ["grund"];
119
+ };
120
+ readonly 'kartenzahlung.unklar': {
121
+ readonly text: "Unklar, ob die Kartenzahlung durchgegangen ist — die Verbindung zum Terminal riss ab. Bitte am Terminal-Beleg nachsehen, BEVOR neu kassiert wird: die Karte kann belastet sein.";
122
+ };
123
+ readonly 'kartenzahlung.unklar_mit_kennung': {
124
+ readonly text: "Unklar, ob die Kartenzahlung durchgegangen ist — die Verbindung zum Terminal riss ab. Bitte am Terminal-Beleg nachsehen, BEVOR neu kassiert wird: die Karte kann belastet sein. Kennung der Zahlung: {kennung}.";
125
+ readonly platzhalter: readonly ["kennung"];
126
+ };
127
+ readonly 'kartenzahlung.connect_nicht_verbunden': {
128
+ readonly text: "Kartenzahlung nicht möglich: Kasseneck Connect ist nicht verbunden — Einstellungen → Kasseneck Connect.";
129
+ readonly nur: readonly ["web"];
130
+ };
131
+ readonly 'terminal.keines_gefunden': {
132
+ readonly text: "Kein Hobex-Terminal gefunden — ist es eingeschaltet und im selben Netz wie dieser Rechner?";
133
+ readonly nur: readonly ["web"];
134
+ };
135
+ readonly 'terminal.nicht_bereit': {
136
+ readonly text: "Terminal antwortet, ist aber nicht betriebsbereit: {antwort} — TID prüfen.";
137
+ readonly platzhalter: readonly ["antwort"];
138
+ readonly nur: readonly ["web"];
139
+ };
140
+ readonly 'gptom.app_fehlt': {
141
+ readonly text: "Die GP-Tom-App ist auf diesem Gerät nicht da.";
142
+ readonly nur: readonly ["app"];
143
+ };
144
+ readonly 'gptom.suche_fehlgeschlagen': {
145
+ readonly text: "GP Tom: Suche nach der App fehlgeschlagen: {grund}";
146
+ readonly platzhalter: readonly ["grund"];
147
+ readonly nur: readonly ["app"];
148
+ };
149
+ readonly 'gptom.zahlung_fehlgeschlagen': {
150
+ readonly text: "GP Tom: Zahlung fehlgeschlagen: {grund}";
151
+ readonly platzhalter: readonly ["grund"];
152
+ readonly nur: readonly ["app"];
153
+ };
154
+ readonly 'gptom.terminal_antwortet_nicht': {
155
+ readonly text: "Das Terminal hat nicht geantwortet: {grund}";
156
+ readonly platzhalter: readonly ["grund"];
157
+ readonly nur: readonly ["app"];
158
+ };
159
+ readonly 'gptom.zahlung_nicht_abgeschlossen': {
160
+ readonly text: "Die Zahlung wurde nicht abgeschlossen ({code}).";
161
+ readonly platzhalter: readonly ["code"];
162
+ readonly nur: readonly ["app"];
163
+ };
164
+ readonly 'belege.laden_fehlgeschlagen': {
165
+ readonly text: "Die Belege konnten nicht geladen werden.";
166
+ };
167
+ readonly 'belege.keine_im_zeitraum': {
168
+ readonly text: "Keine Belege in diesem Zeitraum.";
169
+ };
170
+ readonly 'beleg.laden_fehlgeschlagen': {
171
+ readonly text: "Der Beleg konnte nicht geladen werden.";
172
+ };
173
+ readonly 'beleg.nicht_gefunden': {
174
+ readonly text: "Der Beleg wurde nicht gefunden.";
175
+ };
176
+ readonly 'storno.fehlgeschlagen': {
177
+ readonly text: "Das Storno ist fehlgeschlagen.";
178
+ };
179
+ readonly 'storno.grund_fehlt': {
180
+ readonly text: "Bitte einen Grund wählen.";
181
+ };
182
+ readonly 'storno.position_fehlt': {
183
+ readonly text: "Bitte mindestens eine Position wählen.";
184
+ };
185
+ readonly 'druck.fehlgeschlagen': {
186
+ readonly text: "Der Ausdruck ist fehlgeschlagen.";
187
+ };
188
+ readonly 'druck.nicht_moeglich': {
189
+ readonly text: "Druck nicht möglich: {grund}";
190
+ readonly platzhalter: readonly ["grund"];
191
+ };
192
+ readonly 'druck.testdruck_fehlgeschlagen': {
193
+ readonly text: "Der Testdruck ist fehlgeschlagen.";
194
+ };
195
+ readonly 'druck.testdruck_nicht_moeglich': {
196
+ readonly text: "Der Testdruck ist fehlgeschlagen: {grund}";
197
+ readonly platzhalter: readonly ["grund"];
198
+ };
199
+ readonly 'druck.kein_drucker': {
200
+ readonly text: "Kein Bondrucker eingerichtet — in den Einstellungen unter Drucker & Lade.";
201
+ };
202
+ readonly 'druck.drucker_nicht_erreichbar': {
203
+ readonly text: "Drucker nicht erreichbar.";
204
+ };
205
+ readonly 'druck.job_abgelaufen': {
206
+ readonly text: "Drucker hat den Beleg nicht abgeholt (abgelaufen).";
207
+ readonly nur: readonly ["web"];
208
+ };
209
+ readonly 'druck.kein_drucker_gefunden': {
210
+ readonly text: "Kein Drucker gefunden — ist er eingeschaltet und im selben Netz wie dieser Rechner?";
211
+ readonly nur: readonly ["web"];
212
+ };
213
+ readonly 'druck.nur_chrome': {
214
+ readonly text: "{weg}-Druck geht nur in Chrome oder Edge (Windows, Mac, Android) — nicht in Safari und nicht am iPad.";
215
+ readonly platzhalter: readonly ["weg"];
216
+ readonly nur: readonly ["web"];
217
+ };
218
+ readonly 'druck.kein_weg_drucker': {
219
+ readonly text: "Kein {weg}-Drucker verbunden — „{weg}-Drucker verbinden“ und den Drucker im Dialog wählen.";
220
+ readonly platzhalter: readonly ["weg"];
221
+ readonly nur: readonly ["web"];
222
+ };
223
+ readonly 'bluetooth.aus': {
224
+ readonly text: "Bluetooth ist ausgeschaltet. Bitte einschalten und erneut suchen.";
225
+ readonly nur: readonly ["app"];
226
+ };
227
+ readonly 'bluetooth.freigabe_fehlt': {
228
+ readonly text: "Bitte die Freigabe in den Geräte-Einstellungen erteilen.";
229
+ readonly nur: readonly ["app"];
230
+ };
231
+ readonly 'bluetooth.suche_fehlgeschlagen': {
232
+ readonly text: "Die Suche ist fehlgeschlagen: {grund}";
233
+ readonly platzhalter: readonly ["grund"];
234
+ readonly nur: readonly ["app"];
235
+ };
236
+ readonly 'connect.antwortet_nicht': {
237
+ readonly text: "Kasseneck Connect antwortet nicht — läuft das Programm auf diesem Rechner?";
238
+ readonly nur: readonly ["web"];
239
+ };
240
+ readonly 'connect.code_abgelaufen': {
241
+ readonly text: "Der Kopplungs-Code ist abgelaufen — im Agent einen neuen erzeugen („kasseneck-connect pair“).";
242
+ readonly nur: readonly ["web"];
243
+ };
244
+ readonly 'connect.zu_viele_versuche': {
245
+ readonly text: "Zu viele Fehlversuche — eine Minute warten und noch einmal versuchen.";
246
+ readonly nur: readonly ["web"];
247
+ };
248
+ readonly 'connect.drucker_antwortet_nicht': {
249
+ readonly text: "Der Drucker antwortet nicht — Strom, Netzwerk und IP prüfen.";
250
+ readonly nur: readonly ["web"];
251
+ };
252
+ readonly 'connect.nicht_gekoppelt': {
253
+ readonly text: "Diese Kasse ist mit Kasseneck Connect nicht gekoppelt — in den Einstellungen „Koppeln“ drücken.";
254
+ readonly nur: readonly ["web"];
255
+ };
256
+ readonly 'connect.drucker_unbekannt': {
257
+ readonly text: "Diesen Drucker kennt Kasseneck Connect nicht (mehr) — bitte neu suchen.";
258
+ readonly nur: readonly ["web"];
259
+ };
260
+ readonly 'connect.ursprung_nicht_freigegeben': {
261
+ readonly text: "Kasseneck Connect nimmt von dieser Adresse nichts an (Ursprung nicht freigegeben).";
262
+ readonly nur: readonly ["web"];
263
+ };
264
+ readonly 'connect.nicht_installiert': {
265
+ readonly text: "Kasseneck Connect nicht gefunden — bitte installieren.";
266
+ readonly nur: readonly ["web"];
267
+ };
268
+ readonly 'einstellungen.laden_fehlgeschlagen': {
269
+ readonly text: "Die Einstellungen konnten nicht geladen werden.";
270
+ };
271
+ readonly 'einstellungen.speichern_fehlgeschlagen': {
272
+ readonly text: "Die Einstellung konnte nicht gespeichert werden.";
273
+ };
274
+ readonly 'einstellungen.noch_nicht_geladen': {
275
+ readonly text: "Noch nicht geladen — bitte kurz warten oder neu anmelden.";
276
+ };
277
+ readonly 'einstellungen.ip_ungueltig': {
278
+ readonly text: "Keine gültige IP-Adresse (z. B. 192.168.1.50).";
279
+ };
280
+ readonly 'entkopplung.fehlgeschlagen': {
281
+ readonly text: "Entkoppeln fehlgeschlagen.";
282
+ };
283
+ readonly 'logo.hochladen_fehlgeschlagen': {
284
+ readonly text: "Hochladen fehlgeschlagen.";
285
+ };
286
+ readonly 'logo.entfernen_fehlgeschlagen': {
287
+ readonly text: "Entfernen fehlgeschlagen.";
288
+ };
289
+ readonly 'app.nicht_im_browser': {
290
+ readonly text: "Die Kassen-App läuft nicht im Browser — dafür gibt es kasse.kasseneck.at.";
291
+ readonly nur: readonly ["app"];
292
+ };
293
+ readonly 'app.im_browser_oeffnen': {
294
+ readonly text: "Bitte im Browser öffnen: {ziel}";
295
+ readonly platzhalter: readonly ["ziel"];
296
+ readonly nur: readonly ["app"];
297
+ };
298
+ };
299
+ export type MeldungsSchluessel = keyof typeof MELDUNGEN_ROH;
300
+ export declare const MELDUNGEN: Record<MeldungsSchluessel, Meldung>;
301
+ /**
302
+ * In welcher Reihenfolge ein Fehler eingeordnet wird — auf beiden Seiten
303
+ * dieselbe. Die Arten:
304
+ * api — HTTP 200, `status:'error'`: der Satz des Backends, woertlich
305
+ * klartext — schon fuer den Bildschirm geschrieben: sein eigener Text
306
+ * zeitablauf — die Frist lief ab, die Anfrage war draussen
307
+ * netz — keine Verbindung zustande gekommen oder abgerissen
308
+ * unerwartet — der Server hat geantwortet, aber nicht wie zugesagt
309
+ * (HTML statt JSON, 500, fehlende Huelle); `status` = HTTP-Code
310
+ * sonst — alles Uebrige ist technisch: der Ersatzsatz des Vorgangs
311
+ */
312
+ export declare const FEHLERREGELN: readonly [{
313
+ readonly art: "api";
314
+ readonly verhalten: "server_text";
315
+ }, {
316
+ readonly art: "klartext";
317
+ readonly verhalten: "eigener_text";
318
+ }, {
319
+ readonly art: "zeitablauf";
320
+ readonly schluessel: "netz.zeitablauf";
321
+ }, {
322
+ readonly art: "netz";
323
+ readonly schluessel: "netz.keine_verbindung";
324
+ }, {
325
+ readonly art: "unerwartet";
326
+ readonly schluessel: "server.unerwartet";
327
+ }, {
328
+ readonly art: "sonst";
329
+ readonly verhalten: "ersatz";
330
+ }];
331
+ export type Fehlerart = (typeof FEHLERREGELN)[number]['art'];
332
+ /** Der Satz zum Schluessel, Platzhalter ersetzt. Fehlt ein Wert, wirft es — ein `{status}` am Tresen waere schlimmer. */
333
+ export declare function meldung(schluessel: MeldungsSchluessel, werte?: Record<string, string | number>): string;
334
+ /** Gilt der Satz auf dieser Seite? */
335
+ export declare function meldungGiltFuer(schluessel: MeldungsSchluessel, seite: Seite): boolean;
336
+ export {};