@kreiseck/kasseneck-api 1.10.1 → 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 (146) hide show
  1. package/CHANGELOG.md +241 -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/index.d.ts +1 -1
  8. package/dist/cjs/index.js +9 -2
  9. package/dist/cjs/inventory/vertrag.d.ts +1 -1
  10. package/dist/cjs/inventory/vertrag.js +2 -0
  11. package/dist/cjs/invoice/vertrag.d.ts +1 -1
  12. package/dist/cjs/invoice/vertrag.js +2 -0
  13. package/dist/cjs/models/cancellation.d.ts +49 -1
  14. package/dist/cjs/models/cancellation.js +139 -1
  15. package/dist/cjs/models/index.d.ts +1 -1
  16. package/dist/cjs/models/index.js +9 -2
  17. package/dist/cjs/models/payment-errors.d.ts +1 -1
  18. package/dist/cjs/models/payment-errors.js +2 -0
  19. package/dist/cjs/models/receipt-email.d.ts +1 -1
  20. package/dist/cjs/models/receipt-email.js +2 -0
  21. package/dist/cjs/models/receipt-errors.d.ts +1 -1
  22. package/dist/cjs/models/receipt-errors.js +2 -0
  23. package/dist/cjs/models/receipt-item.d.ts +10 -0
  24. package/dist/cjs/models/receipt-item.js +3 -0
  25. package/dist/cjs/pos/client.js +10 -0
  26. package/dist/cjs/pos/errors.d.ts +4 -3
  27. package/dist/cjs/pos/errors.js +21 -2
  28. package/dist/cjs/pos/index.d.ts +6 -2
  29. package/dist/cjs/pos/index.js +33 -3
  30. package/dist/cjs/pos/schicht-lesen.d.ts +39 -0
  31. package/dist/cjs/pos/schicht-lesen.js +405 -0
  32. package/dist/cjs/pos/schicht-typen.d.ts +380 -0
  33. package/dist/cjs/pos/schicht-typen.js +59 -0
  34. package/dist/cjs/pos/schicht.d.ts +208 -0
  35. package/dist/cjs/pos/schicht.js +235 -0
  36. package/dist/cjs/pos/settings.d.ts +37 -1
  37. package/dist/cjs/pos/settings.js +43 -3
  38. package/dist/cjs/pos/texte.d.ts +627 -0
  39. package/dist/cjs/pos/texte.js +236 -0
  40. package/dist/cjs/react/index.js +1 -1
  41. package/dist/cjs/receipt/grid.js +2 -2
  42. package/dist/cjs/receipt/layout.d.ts +5 -0
  43. package/dist/cjs/receipt/shift-sheet.d.ts +33 -0
  44. package/dist/cjs/receipt/shift-sheet.js +207 -0
  45. package/dist/cjs/register/errors.d.ts +1 -1
  46. package/dist/cjs/register/errors.js +2 -0
  47. package/dist/cjs/stored/draht.d.ts +1 -1
  48. package/dist/cjs/stored/draht.js +4 -3
  49. package/dist/cjs/stored/einstellungen.js +26 -3
  50. package/dist/cjs/stored/vokabular.d.ts +1 -1
  51. package/dist/cjs/stored/vokabular.js +118 -12
  52. package/dist/cjs/version.d.ts +1 -1
  53. package/dist/cjs/version.js +1 -1
  54. package/dist/esm/client/aufrufe.d.ts +5 -5
  55. package/dist/esm/client/aufrufe.js +22 -2
  56. package/dist/esm/client/errors.js +2 -0
  57. package/dist/esm/client/receipts.d.ts +34 -1
  58. package/dist/esm/client/receipts.js +35 -2
  59. package/dist/esm/index.d.ts +1 -1
  60. package/dist/esm/index.js +1 -1
  61. package/dist/esm/inventory/vertrag.d.ts +1 -1
  62. package/dist/esm/inventory/vertrag.js +2 -0
  63. package/dist/esm/invoice/vertrag.d.ts +1 -1
  64. package/dist/esm/invoice/vertrag.js +2 -0
  65. package/dist/esm/models/cancellation.d.ts +49 -1
  66. package/dist/esm/models/cancellation.js +132 -0
  67. package/dist/esm/models/index.d.ts +1 -1
  68. package/dist/esm/models/index.js +1 -1
  69. package/dist/esm/models/payment-errors.d.ts +1 -1
  70. package/dist/esm/models/payment-errors.js +2 -0
  71. package/dist/esm/models/receipt-email.d.ts +1 -1
  72. package/dist/esm/models/receipt-email.js +2 -0
  73. package/dist/esm/models/receipt-errors.d.ts +1 -1
  74. package/dist/esm/models/receipt-errors.js +2 -0
  75. package/dist/esm/models/receipt-item.d.ts +10 -0
  76. package/dist/esm/models/receipt-item.js +3 -0
  77. package/dist/esm/pos/client.js +11 -1
  78. package/dist/esm/pos/errors.d.ts +4 -3
  79. package/dist/esm/pos/errors.js +21 -2
  80. package/dist/esm/pos/index.d.ts +6 -2
  81. package/dist/esm/pos/index.js +7 -2
  82. package/dist/esm/pos/schicht-lesen.d.ts +39 -0
  83. package/dist/esm/pos/schicht-lesen.js +392 -0
  84. package/dist/esm/pos/schicht-typen.d.ts +380 -0
  85. package/dist/esm/pos/schicht-typen.js +56 -0
  86. package/dist/esm/pos/schicht.d.ts +208 -0
  87. package/dist/esm/pos/schicht.js +222 -0
  88. package/dist/esm/pos/settings.d.ts +37 -1
  89. package/dist/esm/pos/settings.js +40 -2
  90. package/dist/esm/pos/texte.d.ts +627 -0
  91. package/dist/esm/pos/texte.js +236 -0
  92. package/dist/esm/react/index.js +1 -1
  93. package/dist/esm/receipt/grid.js +2 -2
  94. package/dist/esm/receipt/layout.d.ts +5 -0
  95. package/dist/esm/receipt/shift-sheet.d.ts +33 -0
  96. package/dist/esm/receipt/shift-sheet.js +203 -0
  97. package/dist/esm/register/errors.d.ts +1 -1
  98. package/dist/esm/register/errors.js +2 -0
  99. package/dist/esm/stored/draht.d.ts +1 -1
  100. package/dist/esm/stored/draht.js +4 -3
  101. package/dist/esm/stored/einstellungen.js +27 -4
  102. package/dist/esm/stored/vokabular.d.ts +1 -1
  103. package/dist/esm/stored/vokabular.js +118 -12
  104. package/dist/esm/version.d.ts +1 -1
  105. package/dist/esm/version.js +1 -1
  106. package/fixtures/cancellation-tip-cases.json +679 -0
  107. package/fixtures/expected/cash-movement.grid32.txt +15 -0
  108. package/fixtures/expected/cash-movement.grid48.txt +15 -0
  109. package/fixtures/expected/cash-movement.lines.json +171 -0
  110. package/fixtures/expected/shift-close-blind.grid32.txt +28 -0
  111. package/fixtures/expected/shift-close-blind.grid48.txt +27 -0
  112. package/fixtures/expected/shift-close-blind.lines.json +275 -0
  113. package/fixtures/expected/shift-close-put-in.grid32.txt +38 -0
  114. package/fixtures/expected/shift-close-put-in.grid48.txt +35 -0
  115. package/fixtures/expected/shift-close-put-in.lines.json +403 -0
  116. package/fixtures/expected/shift-close-take-out.grid32.txt +38 -0
  117. package/fixtures/expected/shift-close-take-out.grid48.txt +35 -0
  118. package/fixtures/expected/shift-close-take-out.lines.json +403 -0
  119. package/fixtures/expected/shift-close.grid32.txt +37 -0
  120. package/fixtures/expected/shift-close.grid48.txt +35 -0
  121. package/fixtures/expected/shift-close.lines.json +413 -0
  122. package/fixtures/expected/shift-interim.grid32.txt +28 -0
  123. package/fixtures/expected/shift-interim.grid48.txt +27 -0
  124. package/fixtures/expected/shift-interim.lines.json +293 -0
  125. package/fixtures/hobex-hps-codes.json +1 -1
  126. package/fixtures/invoice-api.schema.json +1 -1
  127. package/fixtures/invoice-texts.json +1 -1
  128. package/fixtures/pos-message-cases.json +45 -0
  129. package/fixtures/pos-texts.json +723 -1
  130. package/fixtures/receipt-due-generated.json +1 -1
  131. package/fixtures/shift-cases.json +87 -0
  132. package/fixtures/shift-receipts.json +41 -0
  133. package/fixtures/surface.json +119 -3
  134. package/fixtures/v3/antworten/belege.json +1 -1
  135. package/fixtures/v3/antworten/belegmail.json +1 -1
  136. package/fixtures/v3/antworten/kasse-belege.json +1 -1
  137. package/fixtures/v3/antworten/kasse.json +10874 -4865
  138. package/fixtures/v3/antworten/lager.json +1 -1
  139. package/fixtures/v3/antworten/rechnungen.json +1 -1
  140. package/fixtures/v3/antworten/storno.json +1 -1
  141. package/fixtures/v3/stored/belege.json +18 -6
  142. package/fixtures/v3/stored/kasse.json +4667 -183
  143. package/fixtures/v3/stored/rechnungen.json +1 -1
  144. package/fixtures/v3/v3-vokabular.json +739 -32
  145. package/fixtures/v3/zahlbetrag-faelle.json +1 -1
  146. package/package.json +1 -1
@@ -57,6 +57,9 @@ const MELDUNGEN_ROH = {
57
57
  // Wer die Abmelden-Taste ein zweites Mal drueckt, beendet die Schicht –
58
58
  // der Satz steht in der Rueckfrage, bevor es so weit ist.
59
59
  'logout.press_again': { text: 'Noch einmal drücken beendet die Schicht an dieser Kasse.' },
60
+ // Seit 1.11.0 an seiner Stelle: die Rueckfrage beim Abmelden mit vollem Warenkorb. Ein zweiter Druck meldet ab
61
+ // und leert den Korb; die Schicht der Kassenlade laeuft weiter (das alte Wort „Schicht“ fuehrte dort in die Irre).
62
+ 'logout.cart_cleared': { text: 'Noch einmal drücken meldet dich ab – der Warenkorb wird geleert.' },
60
63
  'permissions.not_changeable': { text: 'Das darfst du nicht ändern – das Recht dafür vergibt der Inhaber im Panel.' },
61
64
  // --- Verkauf und Positionen ----------------------------------------------
62
65
  'cashregister.load_failed': { text: 'Die Kasse konnte nicht geladen werden.' },
@@ -78,6 +81,8 @@ const MELDUNGEN_ROH = {
78
81
  // Keine gesetzliche Obergrenze – steuerfrei ist aber nur ortsuebliches
79
82
  // Trinkgeld (§ 3 Abs 1 Z 16a EStG). Eine Warnung, keine Sperre.
80
83
  'tip.over_half': { text: 'Über 50 % Trinkgeld – wirklich? Steuerfrei ist nur ortsübliches Trinkgeld.' },
84
+ // Bar-Trinkgeld „gleich behalten“ (Hinweis zur Einstellung, Beschriftungen unten).
85
+ 'tip.kept_setting_hint': { text: 'Der Mitarbeiter steckt das Bar-Trinkgeld beim Verkauf gleich ein: es zählt nicht ins Soll der Lade und gilt als schon genommen. An der Kasse lässt es sich je Verkauf umstellen.' },
81
86
  // --- Abschluss -----------------------------------------------------------
82
87
  'completion.failed': { text: 'Der Abschluss ist fehlgeschlagen.' },
83
88
  'completion.unknown': { 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.' },
@@ -166,6 +171,12 @@ const MELDUNGEN_ROH = {
166
171
  // (`unknown_reason`, `note_too_long`, `invalid_items`): am Server geschah
167
172
  // nichts, der Kassier beginnt das Storno von vorn.
168
173
  'cancellation.input_rejected': { text: 'Die Eingabe wurde abgelehnt – bitte das Storno neu beginnen.' },
174
+ // Storno eines Belegs mit Mitarbeiter-Trinkgeld (Schichten-Spec, Nachtrag 5): gesendet wird erst
175
+ // nach der Wahl, ohne Vorauswahl. Fehlt sie, steht dieser Satz unter dem Knopf.
176
+ 'cancellation.tip_refund_missing': { text: 'Bitte wählen: Trinkgeld auch zurückgeben?' },
177
+ 'cancellation.tip_source_missing': { text: 'Bitte wählen: Woher kommt das Trinkgeld zurück?' },
178
+ // Beleg mit Wertgutschein: keine Frage, das Trinkgeld geht mit (Ruling R59, asksRefundTip).
179
+ 'cancellation.tip_refunded_with_voucher': { text: 'Beleg mit Gutschein: Das Trinkgeld wird mitstorniert.' },
169
180
  // --- Getrennt zahlen -----------------------------------------------------
170
181
  // Ein Tisch zahlt in Teilen, ein Beleg fuer alles. Das Teure ist eine schon
171
182
  // belastete Karte: jeder Satz, der sie betrifft, nennt den Betrag, und keiner
@@ -298,6 +309,39 @@ const MELDUNGEN_ROH = {
298
309
  'stocktake.serials_mismatch': { text: 'Je Stück genau eine Seriennummer: {count} erfasst, {quantity} gezählt.', placeholders: ['count', 'quantity'] },
299
310
  'stocktake.reason_missing': { text: 'Bitte einen Grund für das Stornieren eingeben.' },
300
311
  'stocktake.outcome_unknown': { text: 'Unklar, ob die Zählung angekommen ist. „Erneut senden“ schickt dieselbe Zählung – sie zählt auch dann nur einmal.' },
312
+ // --- Schicht und Kassenstand ---------------------------------------------
313
+ // Soll und Differenz nennt kein Satz: ohne Recht `cashView` wird blind
314
+ // gezaehlt. `{number}` ist die Schicht-Nummer, `{since}` die Startzeit
315
+ // („08:00“ bzw. „gestern 22:15“), `{type}` die Beschriftung der Art
316
+ // (`cash_movement.type_*`), `{amount}` der Betrag samt Euro-Zeichen.
317
+ // „Erneut senden“ schickt denselben Vorgang mit demselben Schluessel.
318
+ 'shift.required': { text: 'Bitte zuerst die Schicht öffnen.' },
319
+ 'shift.required_other': { text: 'Die Schicht muss ein berechtigter Mitarbeiter öffnen.' },
320
+ 'shift.recount': { text: 'Der gezählte Betrag weicht ab – bitte noch einmal zählen.' },
321
+ 'shift.comment_required': { text: 'Die Abweichung ist größer als erlaubt – bitte kurz begründen.' },
322
+ 'shift.closed': { text: 'Schicht {number} geschlossen.', placeholders: ['number'] },
323
+ 'shift.long_open': { text: 'Die Schicht läuft seit {since}.', placeholders: ['since'] },
324
+ 'shift.already_open': { text: 'An dieser Lade läuft schon eine Schicht.' },
325
+ 'shift.already_closed': { text: 'Diese Schicht ist schon geschlossen.' },
326
+ 'shift.recount_exhausted': { text: 'Es wurde schon zweimal gezählt. Bitte den Betrag der zweiten Zählung senden oder den Chef rufen.' },
327
+ 'shift.mode_off': { text: 'An dieser Lade ist der Schichtbetrieb aus. Bitte im Panel einschalten.' },
328
+ 'shift.outcome_unknown': { text: 'Unklar, ob das angekommen ist. „Erneut senden“ schickt denselben Vorgang – er zählt nur einmal.' },
329
+ 'cash_movement.exceeds_balance': { text: 'So viel Bargeld ist laut Kassenstand nicht in der Lade.' },
330
+ 'cash_movement.note_required': { text: 'Bitte einen Grund eingeben.' },
331
+ 'cash_movement.booked': { text: '{type} über {amount} gebucht.', placeholders: ['type', 'amount'] },
332
+ 'cash_movement.type_invalid': { text: 'Diese Art der Kassenbewegung ist hier nicht möglich.' },
333
+ 'cash_movement.same_drawer': { text: 'Eine Umbuchung in dieselbe Lade geht nicht. Bitte eine andere Lade wählen.' },
334
+ 'tip_payout.exceeds_claim': { text: 'Mehr als der offene Anspruch kann nicht ausgezahlt werden.' },
335
+ 'denominations.invalid': { text: 'Scheine und Münzen ergeben nicht den Betrag.' },
336
+ 'amount.invalid': { text: 'Bitte einen gültigen Betrag eingeben.' },
337
+ 'drawer.not_found': { text: 'Diese Lade gibt es nicht mehr. Bitte im Panel die Lade dieses Geräts prüfen.' },
338
+ 'drawer.inactive': { text: 'Diese Lade ist stillgelegt. Bitte im Panel eine andere Lade zuordnen.' },
339
+ 'tip_payout.recipient_required': { text: 'Bitte einen Mitarbeiter wählen.' },
340
+ 'tip_payout.none_open': { text: 'In dieser Schicht ist kein Trinkgeld offen.' },
341
+ 'shift.pending_hint': { text: 'Noch nicht bestätigt. „Erneut senden“ schickt denselben Vorgang – er zählt nur einmal.' },
342
+ // Wechselgeld der Lade (1.11.0): beim Öffnen der Schicht liegt weniger in der Lade als eingestellt.
343
+ // `{amount}` was fehlt, `{total}` das eingestellte Wechselgeld, beide samt Euro-Zeichen.
344
+ 'shift.change_float_missing': { text: 'Es fehlen {amount} bis zum Wechselgeld von {total}.', placeholders: ['amount', 'total'] },
301
345
  // --- Nur App -------------------------------------------------------------
302
346
  'app.not_in_browser': { text: 'Die Kassen-App läuft nicht im Browser – dafür gibt es kasse.kasseneck.at.', only: ['app'] },
303
347
  'app.open_in_browser': { text: 'Bitte im Browser öffnen: {target}', placeholders: ['target'], only: ['app'] },
@@ -350,6 +394,24 @@ export const ERROR_RULES = [
350
394
  export const ERROR_CODE_RULES = [
351
395
  { kind: 'api', codes: ['dialect_mismatch', 'response_translation_failed', 'response_unreadable'], key: 'server.response_unreadable' },
352
396
  { kind: 'api', codes: ['route_missing', 'not_found', 'internal_translation_error'], key: 'server.connection_disturbed' },
397
+ // Schicht und Kassenstand (1.9.0): die Saetze des Backends nennen Felder
398
+ // und Schwellen; die Kasse sagt, was zu tun ist. Der Server hat in jedem
399
+ // dieser Faelle nichts gebucht.
400
+ { kind: 'api', codes: ['shift_not_open'], key: 'shift.required' },
401
+ { kind: 'api', codes: ['shift_already_open'], key: 'shift.already_open' },
402
+ { kind: 'api', codes: ['shift_closed'], key: 'shift.already_closed' },
403
+ { kind: 'api', codes: ['comment_required'], key: 'shift.comment_required' },
404
+ { kind: 'api', codes: ['recount_exhausted'], key: 'shift.recount_exhausted' },
405
+ { kind: 'api', codes: ['shift_mode_off'], key: 'shift.mode_off' },
406
+ { kind: 'api', codes: ['cash_movement_exceeds_balance'], key: 'cash_movement.exceeds_balance' },
407
+ { kind: 'api', codes: ['note_required'], key: 'cash_movement.note_required' },
408
+ { kind: 'api', codes: ['invalid_movement_type'], key: 'cash_movement.type_invalid' },
409
+ { kind: 'api', codes: ['transfer_same_drawer'], key: 'cash_movement.same_drawer' },
410
+ { kind: 'api', codes: ['tip_payout_exceeds_claim'], key: 'tip_payout.exceeds_claim' },
411
+ { kind: 'api', codes: ['invalid_denominations'], key: 'denominations.invalid' },
412
+ { kind: 'api', codes: ['invalid_amount'], key: 'amount.invalid' },
413
+ { kind: 'api', codes: ['drawer_not_found'], key: 'drawer.not_found' },
414
+ { kind: 'api', codes: ['drawer_inactive'], key: 'drawer.inactive' },
353
415
  ];
354
416
  /**
355
417
  * Regeln je Ausgang, nach den Code-Regeln und vor der Regel der Art: eine
@@ -522,6 +584,9 @@ const BESCHRIFTUNGEN_ROH = {
522
584
  'checkout.clear_tendered': { text: 'Gegeben löschen' },
523
585
  'checkout.still_missing': { text: 'Es fehlen noch' },
524
586
  'checkout.change': { text: 'Rückgeld' },
587
+ // Schalter beim Trinkgeld (nur Mitarbeiter-Trinkgeld, reine Barzahlung), vorbelegt aus der Einstellung.
588
+ 'checkout.tip_kept': { text: 'gleich behalten' },
589
+ 'tip.kept_setting': { text: 'Bar-Trinkgeld behält der Mitarbeiter gleich' },
525
590
  // Die Warte-Karte, solange der Betrag am Terminal steht; `{time}` ist der
526
591
  // Rest des Kartenfensters als m:ss.
527
592
  'card_payment.amount_on_terminal': { text: 'Betrag steht am Terminal' },
@@ -593,6 +658,16 @@ const BESCHRIFTUNGEN_ROH = {
593
658
  'cancellation.all_cash': { text: 'Alles bar' },
594
659
  'cancellation.payment_remainder': { text: 'Rest {amount}', placeholders: ['amount'] },
595
660
  'cancellation.difference': { text: 'Differenz' },
661
+ // --- Storno mit oder ohne Trinkgeld (Schichten-Spec, Nachtrag 5) -----------
662
+ // Erste Frage bei offenem Mitarbeiter-Trinkgeld, zweite nur bei Ja und Barrueckgabe.
663
+ 'cancellation.tip_refund_question': { text: 'Trinkgeld auch zurückgeben?' },
664
+ 'cancellation.tip_refund_yes': { text: 'Ja' },
665
+ 'cancellation.tip_refund_no': { text: 'Nein' },
666
+ 'cancellation.tip_source_question': { text: 'Woher kommt das Trinkgeld zurück?' },
667
+ 'cancellation.tip_source_drawer': { text: 'Aus der Lade' },
668
+ 'cancellation.tip_source_employee': { text: 'Gibt die Mitarbeiterin direkt' },
669
+ // Trinkgeld-Zeile im Storno-Blatt nach „Nein“ (Fix-Runde 1, R63): sie geht nicht mit.
670
+ 'cancellation.tip_stays': { text: 'bleibt' },
596
671
  // --- Zeichensatz-Test (Testblatt und Drucker & Lade) ----------------------
597
672
  // Das Testblatt druckt diese Texte mit Ersatzbuchstaben (reines ASCII);
598
673
  // die Knoepfe stehen nur am Bildschirm. `{chars}` sind die fehlenden Zeichen einer Zeile
@@ -645,6 +720,167 @@ const BESCHRIFTUNGEN_ROH = {
645
720
  'stocktake.sent': { text: 'gesendet' },
646
721
  'stocktake.unconfirmed': { text: 'unbestätigt' },
647
722
  'stocktake.voided': { text: 'storniert' },
723
+ // --- Schicht und Kassenstand ---------------------------------------------
724
+ 'shift.open_title': { text: 'Schicht öffnen' },
725
+ 'shift.take_over': { text: 'Bestand übernehmen' },
726
+ 'shift.count': { text: 'Zählen' },
727
+ 'shift.by_denomination': { text: 'Nach Scheinen und Münzen' },
728
+ 'shift.close_title': { text: 'Schicht schließen' },
729
+ 'shift.cash_title': { text: 'Kassenstand' },
730
+ 'shift.x_report': { text: 'Zwischenbericht' },
731
+ 'shift.pay_out_tips': { text: 'Trinkgeld auszahlen' },
732
+ 'drawer.reason_title': { text: 'Warum öffnen Sie die Lade?' },
733
+ 'drawer.reason_change': { text: 'Wechselgeld' },
734
+ 'drawer.reason_check': { text: 'Kontrolle' },
735
+ 'drawer.reason_misoperation': { text: 'Fehlbedienung' },
736
+ 'drawer.reason_other': { text: 'Sonstiges' },
737
+ // Je Art einer Kassenbewegung (auch die, die nur der Server vergibt: sie
738
+ // stehen auf dem Z-Bon und im Bericht).
739
+ 'cash_movement.type_change_in': { text: 'Wechselgeld' },
740
+ 'cash_movement.type_cash_drop': { text: 'Abschöpfung' },
741
+ 'cash_movement.type_owner_deposit': { text: 'Privateinlage' },
742
+ 'cash_movement.type_owner_withdrawal': { text: 'Privatentnahme' },
743
+ 'cash_movement.type_tip_payout': { text: 'Trinkgeld-Auszahlung' },
744
+ 'cash_movement.type_other_in': { text: 'Sonstige Einzahlung' },
745
+ 'cash_movement.type_other_out': { text: 'Sonstige Auszahlung' },
746
+ 'cash_movement.type_transfer_out': { text: 'Umbuchung' },
747
+ 'cash_movement.type_transfer_in': { text: 'Umbuchung (Eingang)' },
748
+ 'cash_movement.type_expense': { text: 'Barausgabe' },
749
+ 'cash_movement.type_count_difference': { text: 'Zähldifferenz' },
750
+ 'cash_movement.type_handover_difference': { text: 'Übergabedifferenz' },
751
+ // Bon der Schicht (Z-Bon, X-Bon, Kassenbewegung): Titel und Zeilennamen.
752
+ 'shift_receipt.title_close': { text: 'Schichtabschluss' },
753
+ 'shift_receipt.title_interim': { text: 'Zwischenbericht' },
754
+ 'shift_receipt.title_movement': { text: 'Kassenbewegung' },
755
+ 'shift_receipt.cashregister': { text: 'Kasse' },
756
+ 'shift_receipt.drawer': { text: 'Lade' },
757
+ 'shift_receipt.shift': { text: 'Schicht' },
758
+ 'shift_receipt.number': { text: 'Nr. {number}', placeholders: ['number'] },
759
+ 'shift_receipt.opened': { text: 'Geöffnet' },
760
+ 'shift_receipt.closed': { text: 'Geschlossen' },
761
+ 'shift_receipt.by': { text: 'von' },
762
+ 'shift_receipt.opening_float': { text: 'Startgeld' },
763
+ 'shift_receipt.opening_float_taken_over': { text: 'Startgeld übernommen' },
764
+ 'shift_receipt.cash_sales': { text: 'Bar aus Verkäufen' },
765
+ 'shift_receipt.cash_cancellations': { text: 'Bar aus Storno' },
766
+ 'shift_receipt.movements': { text: 'Bewegungen' },
767
+ 'shift_receipt.expected': { text: 'Soll' },
768
+ 'shift_receipt.counted': { text: 'Ist' },
769
+ 'shift_receipt.difference': { text: 'Differenz' },
770
+ // Nach der Zählung beim Abschluss (1.11.0): „Entnommen“ (negativ) bzw. „Eingelegt“, dann was bleibt (fett).
771
+ 'shift_receipt.taken_out': { text: 'Entnommen' },
772
+ 'shift_receipt.put_in': { text: 'Eingelegt' },
773
+ 'shift_receipt.left_in_drawer': { text: 'Bleibt in der Kassenlade' },
774
+ 'shift_receipt.comment': { text: 'Kommentar' },
775
+ 'shift_receipt.non_cash': { text: 'Unbar (nur Information)' },
776
+ 'shift_receipt.receipts': { text: 'Belege' },
777
+ 'shift_receipt.cancellations': { text: 'Stornos' },
778
+ 'shift_receipt.openings_without_sale': { text: 'Öffnungen ohne Verkauf' },
779
+ // Trinkgeld je Mitarbeiter (Bon, Kassenstand, Nachtrag 3): verdient = sofort + über Lohn, schon
780
+ // genommen = in dieser Schicht aus der Lade ausgezahlt, offen = Anspruch an die Lade.
781
+ 'shift_receipt.tips': { text: 'Trinkgeld' },
782
+ 'shift_receipt.tip_earned': { text: 'verdient' },
783
+ 'shift_receipt.tip_retained': { text: 'über Lohn' },
784
+ 'shift_receipt.tip_paid_out': { text: 'schon genommen' },
785
+ // Beim Storno von der Mitarbeiterin selbst zurueckgegeben (Fix-Runde 1, R62) – nie ein negatives „schon genommen“.
786
+ 'shift_receipt.tip_refunded_by_employee': { text: 'selbst zurückgegeben' },
787
+ // Offene Forderung beim Oeffnen der Schicht (Fix-Runde 2, R64).
788
+ 'shift_receipt.tip_carried_over': { text: 'aus früheren Schichten' },
789
+ 'shift_receipt.tip_open': { text: 'offen' },
790
+ 'shift_receipt.movement_number': { text: 'Nr.' },
791
+ 'shift_receipt.type': { text: 'Art' },
792
+ 'shift_receipt.amount': { text: 'Betrag' },
793
+ 'shift_receipt.location': { text: 'Ort' },
794
+ 'shift_receipt.location_bank': { text: 'Bank' },
795
+ 'shift_receipt.location_safe': { text: 'Tresor' },
796
+ 'shift_receipt.recipient': { text: 'Empfänger' },
797
+ 'shift_receipt.target_drawer': { text: 'Ziel-Lade' },
798
+ 'shift_receipt.note': { text: 'Grund' },
799
+ 'shift_receipt.operator': { text: 'Bediener' },
800
+ 'shift_receipt.time': { text: 'Zeit' },
801
+ 'cash_movement.pay_in': { text: 'Einzahlung' },
802
+ 'cash_movement.pay_out': { text: 'Auszahlung' },
803
+ 'cash_movement.print': { text: 'Beleg drucken' },
804
+ 'drawer.recent_openings': { text: 'Letzte Öffnungen' },
805
+ 'drawer.reason_sale': { text: 'Verkauf' },
806
+ 'drawer.reason_cash_movement': { text: 'Kassenbewegung' },
807
+ 'drawer.reason_shift_open': { text: 'Schichtbeginn' },
808
+ 'drawer.reason_shift_close': { text: 'Schichtabschluss' },
809
+ 'shift.resend': { text: 'Erneut senden' },
810
+ 'shift.next': { text: 'Weiter' },
811
+ 'shift.print_close': { text: 'Schichtabschluss drucken' },
812
+ 'shift.print_again': { text: 'Nochmals drucken' },
813
+ 'shift.back_to_register': { text: 'Zur Kasse' },
814
+ 'shift.later': { text: 'Später senden' },
815
+ 'time.yesterday': { text: 'gestern' },
816
+ // Kassenstand an der Kasse (1.11.0): Karte „In der Kassenlade sollten sein“, Schichtkopf, Zahlungen.
817
+ 'shift.drawer_should_have': { text: 'In der Kassenlade sollten sein' },
818
+ 'shift.drawer_named': { text: 'Kassenlade: {name}', placeholders: ['name'] },
819
+ 'shift.running_since': { text: 'läuft seit {duration}', placeholders: ['duration'] },
820
+ 'shift.since': { text: 'seit {time}', placeholders: ['time'] },
821
+ 'shift.collected_outside_drawer': { text: 'Kassiert, aber nicht in der Kassenlade' },
822
+ 'shift.non_cash_short': { text: 'unbar' },
823
+ 'shift.voucher_redeemed': { text: 'Gutschein eingelöst' },
824
+ 'shift.all_payments': { text: 'Alle Zahlungen dieser Schicht (bar + unbar)' },
825
+ 'shift.receipt_count_one': { text: '1 Beleg' },
826
+ 'shift.receipt_count_other': { text: '{count} Belege', placeholders: ['count'] },
827
+ 'shift.openings_without_sale_count': { text: '{count}× ohne Verkauf geöffnet', placeholders: ['count'] },
828
+ 'cash_movement.pay_in_short': { text: 'Einzahlen' },
829
+ 'cash_movement.pay_out_short': { text: 'Auszahlen' },
830
+ 'drawer.recent_openings_full': { text: 'Letzte Öffnungen der Kassenlade' },
831
+ // Warnzeile an den Druck-Knöpfen: nur ein leiser Hinweis, die Knöpfe bleiben wie sie sind.
832
+ 'printer.warn_unreachable': { text: 'Drucker nicht erreichbar' },
833
+ 'printer.warn_none': { text: 'Kein Drucker eingerichtet' },
834
+ 'printer.check': { text: 'Prüfen' },
835
+ 'printer.set_up': { text: 'Einrichten' },
836
+ // Dauer (Zusammensetzung: durationText): ab 1 Tag „1 Tag 3 Std.“, sonst „8 Std. 32 Min.“, unter 1 Std. nur Minuten.
837
+ 'duration.days_one': { text: '{days} Tag', placeholders: ['days'] },
838
+ 'duration.days_other': { text: '{days} Tage', placeholders: ['days'] },
839
+ 'duration.hours': { text: '{hours} Std.', placeholders: ['hours'] },
840
+ 'duration.minutes': { text: '{minutes} Min.', placeholders: ['minutes'] },
841
+ // Trinkgeld auszahlen (eine Liste mit Kurzwahlen) und Trinkgeld-Karte im Kassenstand.
842
+ 'tip_payout.open_amount': { text: 'offen {amount}', placeholders: ['amount'] },
843
+ 'tip_payout.done': { text: 'erledigt' },
844
+ 'tip_payout.for_all': { text: 'Für alle' },
845
+ 'tip_payout.nothing': { text: 'Nichts' },
846
+ 'tip_payout.all_open': { text: 'Alles Offene' },
847
+ 'tip_payout.title': { text: 'Auszahlung' },
848
+ 'tip_payout.persons_one': { text: '1 Person' },
849
+ 'tip_payout.persons_other': { text: '{count} Personen', placeholders: ['count'] },
850
+ 'tip_payout.pay_amount': { text: '{amount} auszahlen', placeholders: ['amount'] },
851
+ 'tip_payout.pay_out': { text: 'Auszahlen' },
852
+ // Wechselgeld je Kassenlade (Einstellung) und Schritt „Geld entnehmen“ beim Schicht-Abschluss (1.11.0).
853
+ 'drawer.change_float': { text: 'Wechselgeld in der Lade' },
854
+ 'drawer.change_float_hint': { text: 'Bleibt nach jedem Schicht-Abschluss drin. Mehr wird beim Schließen entnommen.' },
855
+ 'drawer.change_float_empty_hint': { text: 'Leer = alles bleibt in der Lade (wie bisher).' },
856
+ 'shift.step_take_out': { text: 'Geld entnehmen' },
857
+ 'shift.left_in_drawer': { text: 'Bleibt in der Kassenlade' },
858
+ 'shift.change_float_next_shift': { text: 'Wechselgeld für die nächste Schicht' },
859
+ 'shift.take_out': { text: 'Entnehmen' },
860
+ 'shift.put_in': { text: 'Einlegen' },
861
+ 'shift.take_out_at_most': { text: 'höchstens {amount}', placeholders: ['amount'] },
862
+ 'shift.put_in_hint': { text: 'Geld in die Kassenlade legen' },
863
+ 'shift.after_count_tap_hint': { text: 'Tippe auf einen Betrag, um ihn zu ändern – der andere rechnet sich selbst.' },
864
+ 'shift.take_out_amount': { text: '{amount} entnehmen', placeholders: ['amount'] },
865
+ 'shift.put_in_amount': { text: '{amount} einlegen', placeholders: ['amount'] },
866
+ 'shift.continue_without_take_out': { text: 'Weiter ohne Entnahme' },
867
+ 'shift.print_envelope_receipt': { text: 'Bon fürs Kuvert drucken' },
868
+ // Bedienhilfe (Vorlesetext) des Vorzeichen-Knopfs −/+ im Feld „Entnehmen“.
869
+ 'shift.toggle_take_out_put_in': { text: 'Entnehmen oder Einlegen umschalten' },
870
+ 'shift.put_in_change_float': { text: 'Wechselgeld einlegen' },
871
+ 'shift.taken_over_from_shift': { text: 'von Schicht {number} übernommen', placeholders: ['number'] },
872
+ // Schritt „Geld entnehmen“: Zeile „Gezählt“, Knopf „Zurück“, letzter Schritt der Schrittleiste „Fertig“;
873
+ // Bedienhilfe der Betragsknöpfe, `{title}` ist ihre Beschriftung („Bleibt in der Kassenlade“, „Entnehmen“ …).
874
+ 'shift.counted': { text: 'Gezählt' },
875
+ 'shift.back': { text: 'Zurück' },
876
+ 'shift.done': { text: 'Fertig' },
877
+ 'shift.edit_amount': { text: '{title} ändern', placeholders: ['title'] },
878
+ // Kassenstand: Ein- und Auszahlungen der Schicht getrennt (Trinkgeld-Auszahlungen zählen zu den Auszahlungen);
879
+ // Buchungsknopf im Blatt Einzahlung/Auszahlung mit Betrag.
880
+ 'shift.pay_ins': { text: 'Einzahlungen' },
881
+ 'shift.pay_outs': { text: 'Auszahlungen' },
882
+ 'cash_movement.pay_in_amount': { text: '{amount} einzahlen', placeholders: ['amount'] },
883
+ 'cash_movement.pay_out_amount': { text: '{amount} auszahlen', placeholders: ['amount'] },
648
884
  };
649
885
  export const LABELS = BESCHRIFTUNGEN_ROH;
650
886
  /**
@@ -45,7 +45,7 @@ function Zeile({ zeile, renderQr, qrVerdeckt, qrVerdecktText }) {
45
45
  case 'text':
46
46
  return (_jsx("div", { className: "keck-receipt-text", style: { textAlign: AUSRICHTUNG[zeile.align], fontWeight: zeile.bold ? 'bold' : 'normal' }, children: zeile.text.length > 0 ? zeile.text : ' ' }));
47
47
  case 'columns':
48
- return (_jsx("div", { className: "keck-receipt-row", style: { display: 'flex', width: '100%' }, children: zeile.columns.map((spalte, index) => (_jsx("div", { className: "keck-receipt-cell", style: {
48
+ return (_jsx("div", { className: "keck-receipt-row", style: { display: 'flex', width: '100%', ...(zeile.bold === true ? { fontWeight: 'bold' } : {}) }, children: zeile.columns.map((spalte, index) => (_jsx("div", { className: "keck-receipt-cell", style: {
49
49
  // Die Breite ist ein Zwoelftel-Anteil (wie beim Bondrucker);
50
50
  // so bleibt die Aufteilung auf allen Ausgabewegen dieselbe.
51
51
  flex: `0 0 ${((spalte.width / 12) * 100).toFixed(4)}%`,
@@ -100,7 +100,7 @@ export function renderReceiptGrid(layout, options = {}) {
100
100
  const zelle = ausrichten(teile[i][r] ?? '', inhalt[i], c.align);
101
101
  text += i < breiten.length - 1 ? zelle + ' '.repeat(breiten[i] - inhalt[i]) : zelle;
102
102
  });
103
- lines.push({ text: text.length === zeichen ? text : ausrichten(text, zeichen, 'left'), kind: 'columns', bold: false });
103
+ lines.push({ text: text.length === zeichen ? text : ausrichten(text, zeichen, 'left'), kind: 'columns', bold: z.bold === true });
104
104
  }
105
105
  if (fliesst) {
106
106
  const i = weiterlaufend[0];
@@ -108,7 +108,7 @@ export function renderReceiptGrid(layout, options = {}) {
108
108
  // Bindestrich-/Hartbrueche der schmalen Spalte nicht als Leerzeichen zurueckbleiben.
109
109
  const rest = restNach(z.columns[i].text, teile[i][0]);
110
110
  for (const t of wrapText(rest, zeichen))
111
- lines.push({ text: ausrichten(t, zeichen, z.columns[i].align), kind: 'columns', bold: false });
111
+ lines.push({ text: ausrichten(t, zeichen, z.columns[i].align), kind: 'columns', bold: z.bold === true });
112
112
  }
113
113
  break;
114
114
  }
@@ -51,6 +51,11 @@ export interface LayoutTextLine {
51
51
  export interface LayoutColumnsLine {
52
52
  kind: 'columns';
53
53
  columns: LayoutColumn[];
54
+ /**
55
+ * Die ganze Zeile fett (z. B. „Bleibt in der Kassenlade“ auf dem Z-Bon).
56
+ * Fehlt es, ist die Zeile normal; im Bauplan steht es nur, wenn `true`.
57
+ */
58
+ bold?: boolean;
54
59
  }
55
60
  /** Eine durchgehende Trennlinie aus [char]. */
56
61
  export interface LayoutRuleLine {
@@ -0,0 +1,33 @@
1
+ import type { CashMovementReceipt, ShiftSummaryReceipt } from '../pos/schicht-typen.js';
2
+ import type { PosPaperSize } from '../printing/escpos.js';
3
+ import { type ReceiptLayout } from './layout.js';
4
+ /**
5
+ * Bons der Schicht als Zeilenmodell: Z-Bon (`shift_close`), X-Bon
6
+ * (`shift_interim`) und Kassenbewegung (`cash_movement`), aus den Bon-Daten
7
+ * der Antwort (`receipt` von `closeMyShift`, `getMyShiftReport`,
8
+ * `recordMyCashMovement`). Gezeichnet wird wie jeder Beleg: Raster, Blatt,
9
+ * ESC/POS, ePOS.
10
+ *
11
+ * Kein Registrierkassenbeleg: keine Signatur, kein QR, kein DEP. Jeder Bon
12
+ * endet mit „Kein Registrierkassenbeleg“.
13
+ *
14
+ * **Blind** (ohne Recht `cashView`) sind Soll, Ist, Differenz, Barumsatz und
15
+ * die gezaehlte Stueckelung `null`: ihre Zeilen fallen weg, nie steht dort
16
+ * „0,00“ oder ein Strich. Dasselbe gilt fuer jede andere leere Angabe.
17
+ */
18
+ export interface ShiftReceiptLayoutOptions {
19
+ /** Papierbreite fuer den Bauplan; Vorgabe `mm58`. Die Druckbreite waehlt der Druckweg. */
20
+ paperSize?: PosPaperSize;
21
+ }
22
+ /**
23
+ * Z-Bon bzw. X-Bon der Schicht. Titel nach `kind`: „Schichtabschluss“ oder
24
+ * „Zwischenbericht“. Wirft (`KasseneckValidationError`, `scope: 'request'`),
25
+ * wenn die Bon-Daten keine Z- oder X-Bon-Daten sind.
26
+ */
27
+ export declare function shiftReceiptLayout(receipt: ShiftSummaryReceipt, options?: ShiftReceiptLayoutOptions): ReceiptLayout;
28
+ /**
29
+ * Bon einer Kassenbewegung (Einzahlung, Auszahlung, Umbuchung). Wirft
30
+ * (`KasseneckValidationError`, `scope: 'request'`), wenn die Bon-Daten keine
31
+ * Kassenbewegung sind.
32
+ */
33
+ export declare function cashMovementLayout(receipt: CashMovementReceipt, options?: ShiftReceiptLayoutOptions): ReceiptLayout;
@@ -0,0 +1,203 @@
1
+ import { KasseneckValidationError } from '../client/errors.js';
2
+ import { labelText, LABELS } from '../pos/texte.js';
3
+ import { bewegungsBon, schichtBon } from '../pos/schicht-lesen.js';
4
+ import { shiftTipLines } from '../pos/schicht.js';
5
+ import { parseServerTimeStamp, toViennaWallClock } from '../vienna-time.js';
6
+ import { CURRENT_LAYOUT_RULESET, formatCents } from './layout.js';
7
+ const PAPIER = ['mm58', 'mm80'];
8
+ function papier(name, options) {
9
+ for (const k of Object.keys(options)) {
10
+ if (k !== 'paperSize')
11
+ throw new KasseneckValidationError(name, `Unbekannte Option "${k}"`, 'request');
12
+ }
13
+ const p = options.paperSize ?? 'mm58';
14
+ if (!PAPIER.includes(p))
15
+ throw new KasseneckValidationError(name, 'paperSize: erlaubt sind mm58, mm80', 'request');
16
+ return p;
17
+ }
18
+ // ---------------------------------------------------------------- Bauteile
19
+ function text(t, align = 'left', bold = false) {
20
+ return { kind: 'text', text: t, align, bold };
21
+ }
22
+ /** Zwei Spalten: Name links, Wert rechts. */
23
+ function paar(links, rechts, linksBreite) {
24
+ return {
25
+ kind: 'columns',
26
+ columns: [
27
+ { text: links, width: linksBreite, align: 'left' },
28
+ { text: rechts, width: 12 - linksBreite, align: 'right' },
29
+ ],
30
+ };
31
+ }
32
+ /** Kopfzeilen (Kasse, Zeiten, Namen): breiter Wert fuer „25.09.2026 17:00“. */
33
+ const kopf = (links, rechts) => paar(links, rechts, 5);
34
+ /** Betragszeilen: breiter Name, schmaler Betrag. */
35
+ const betrag = (links, cents) => paar(links, formatCents(cents), 8);
36
+ /** Betragszeile fett (Ergebnis, z. B. „Bleibt in der Kassenlade“). */
37
+ const betragFett = (links, cents) => ({ ...betrag(links, cents), bold: true });
38
+ const anzahl = (links, n) => paar(links, String(n), 8);
39
+ const regel = () => ({ kind: 'rule', char: '-' });
40
+ /** Zeit in Wien als `DD.MM.YYYY HH:mm`; unlesbar -> `null` (die Zeile faellt weg, der Bon bleibt). */
41
+ function zeit(iso) {
42
+ if (iso === null)
43
+ return null;
44
+ try {
45
+ const t = toViennaWallClock(parseServerTimeStamp(iso));
46
+ const zwei = (n) => String(n).padStart(2, '0');
47
+ return `${zwei(t.day)}.${zwei(t.month)}.${t.year} ${zwei(t.hour)}:${zwei(t.minute)}`;
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ function beschriftung(schluessel, sonst) {
54
+ return Object.prototype.hasOwnProperty.call(LABELS, schluessel) ? labelText(schluessel) : sonst;
55
+ }
56
+ const artText = (art) => beschriftung(`cash_movement.type_${art}`, art);
57
+ const ortText = (ort) => beschriftung(`shift_receipt.location_${ort}`, ort);
58
+ /** Zahlart unbar: `card` wie am Beleg „Kartenzahlung“, sonst der Wert, wie er kommt. */
59
+ function zahlartText(methode) {
60
+ return methode === 'card' ? labelText('payment_method.card') : methode;
61
+ }
62
+ /** Je Schein und Muenze eine eingerueckte Zeile, vom groessten Wert abwaerts; Anzahl 0 faellt weg. */
63
+ function stueckelungZeilen(karte) {
64
+ if (karte === null)
65
+ return [];
66
+ return Object.entries(karte)
67
+ .map(([wert, n]) => [Number(wert), n])
68
+ .filter(([wert, n]) => n > 0 && Number.isSafeInteger(wert))
69
+ .sort((a, b) => b[0] - a[0])
70
+ .map(([wert, n]) => betrag(` ${n} x ${formatCents(wert)}`, wert * n));
71
+ }
72
+ function wennText(zeilen, links, wert) {
73
+ if (wert !== null && wert !== '')
74
+ zeilen.push(kopf(links, wert));
75
+ }
76
+ function kopfblock(zeilen, bon, titel) {
77
+ zeilen.push({ kind: 'banner', text: labelText(titel), tone: 'receipt_type' });
78
+ if (bon.business.name)
79
+ zeilen.push(text(bon.business.name, 'center', true));
80
+ wennText(zeilen, labelText('shift_receipt.cashregister'), bon.cashregister.name);
81
+ wennText(zeilen, labelText('shift_receipt.drawer'), bon.drawer.name);
82
+ }
83
+ /**
84
+ * Nach der Zaehlung beim Abschluss, durch eine Linie vom Zaehlblock getrennt:
85
+ * „Entnommen“ (negativ) bzw. „Eingelegt“, dann „Bleibt in der Kassenlade“
86
+ * (fett). Nur wenn Geld bewegt wurde
87
+ * (`afterCount`); sonst keine Zeile, der Bon bleibt wie bisher. Blind fehlt
88
+ * „Bleibt“ (`null`), die Entnahme selbst bleibt sichtbar. Eine unbekannte Art
89
+ * (kuenftiger Katalogwert) druckt keine Betragszeile.
90
+ */
91
+ function nachZaehlungZeilen(bon) {
92
+ const n = bon.afterCount;
93
+ if (n === undefined || n === null)
94
+ return [];
95
+ const zeilen = [];
96
+ if (n.type === 'cash_drop')
97
+ zeilen.push(betrag(labelText('shift_receipt.taken_out'), -n.amountCents));
98
+ else if (n.type === 'change_in')
99
+ zeilen.push(betrag(labelText('shift_receipt.put_in'), n.amountCents));
100
+ const bleibt = bon.leftInDrawerCents;
101
+ if (bleibt !== undefined && bleibt !== null)
102
+ zeilen.push(betragFett(labelText('shift_receipt.left_in_drawer'), bleibt));
103
+ return zeilen.length > 0 ? [regel(), ...zeilen] : [];
104
+ }
105
+ // ------------------------------------------------------------------ Bons
106
+ /**
107
+ * Z-Bon bzw. X-Bon der Schicht. Titel nach `kind`: „Schichtabschluss“ oder
108
+ * „Zwischenbericht“. Wirft (`KasseneckValidationError`, `scope: 'request'`),
109
+ * wenn die Bon-Daten keine Z- oder X-Bon-Daten sind.
110
+ */
111
+ export function shiftReceiptLayout(receipt, options = {}) {
112
+ const name = 'shiftReceiptLayout';
113
+ const paperSize = papier(name, options);
114
+ const bon = schichtBon({ name, pfad: 'receipt', scope: 'request' }, receipt);
115
+ const zeilen = [];
116
+ kopfblock(zeilen, bon, bon.kind === 'shift_close' ? 'shift_receipt.title_close' : 'shift_receipt.title_interim');
117
+ zeilen.push(kopf(labelText('shift_receipt.shift'), labelText('shift_receipt.number', { number: bon.shift.number })));
118
+ const von = labelText('shift_receipt.by');
119
+ wennText(zeilen, labelText('shift_receipt.opened'), zeit(bon.shift.openedAt));
120
+ wennText(zeilen, von, bon.shift.openedByName);
121
+ wennText(zeilen, labelText('shift_receipt.closed'), zeit(bon.shift.closedAt));
122
+ wennText(zeilen, von, bon.shift.closedByName);
123
+ // Bargeld: Startgeld, Barumsatz, Bewegungen, Soll, Ist, Differenz.
124
+ zeilen.push(regel());
125
+ const start = labelText(bon.openingFloatTakenOver ? 'shift_receipt.opening_float_taken_over' : 'shift_receipt.opening_float');
126
+ if (bon.openingFloatCents !== null)
127
+ zeilen.push(betrag(start, bon.openingFloatCents));
128
+ else if (bon.openingFloatTakenOver)
129
+ zeilen.push(text(start));
130
+ zeilen.push(...stueckelungZeilen(bon.openingDenominations));
131
+ if (bon.cashSalesCents !== null)
132
+ zeilen.push(betrag(labelText('shift_receipt.cash_sales'), bon.cashSalesCents));
133
+ if (bon.cashCancellationsCents !== null)
134
+ zeilen.push(betrag(labelText('shift_receipt.cash_cancellations'), bon.cashCancellationsCents));
135
+ if (bon.movements.length > 0) {
136
+ zeilen.push(text(labelText('shift_receipt.movements'), 'left', true));
137
+ for (const m of bon.movements) {
138
+ zeilen.push(betrag(`${labelText('shift_receipt.number', { number: m.number })} ${artText(m.type)}`, m.amountCents));
139
+ if (m.note)
140
+ zeilen.push(text(` ${m.note}`));
141
+ }
142
+ }
143
+ if (bon.expectedCents !== null)
144
+ zeilen.push(betrag(labelText('shift_receipt.expected'), bon.expectedCents));
145
+ if (bon.countedCents !== null) {
146
+ zeilen.push(betrag(labelText('shift_receipt.counted'), bon.countedCents));
147
+ zeilen.push(...stueckelungZeilen(bon.countedDenominations));
148
+ }
149
+ if (bon.differenceCents !== null)
150
+ zeilen.push(betrag(labelText('shift_receipt.difference'), bon.differenceCents));
151
+ if (bon.comment)
152
+ zeilen.push(text(`${labelText('shift_receipt.comment')}: ${bon.comment}`));
153
+ zeilen.push(...nachZaehlungZeilen(bon));
154
+ // Zur Information: unbar, Zaehler.
155
+ zeilen.push(regel());
156
+ if (bon.nonCashByMethod.length > 0) {
157
+ zeilen.push(text(labelText('shift_receipt.non_cash'), 'left', true));
158
+ for (const u of bon.nonCashByMethod)
159
+ zeilen.push(betrag(zahlartText(u.method), u.cents));
160
+ }
161
+ zeilen.push(anzahl(labelText('shift_receipt.receipts'), bon.receiptCount));
162
+ zeilen.push(anzahl(labelText('shift_receipt.cancellations'), bon.cancellationCount));
163
+ zeilen.push(anzahl(labelText('shift_receipt.openings_without_sale'), bon.openingsWithoutSale));
164
+ // Trinkgeld je Mitarbeiter (shiftTipLines): verdient immer, ueber Lohn, schon genommen und offen
165
+ // nur ungleich 0; wer ueberall 0 hat, entfaellt, ohne Mitarbeiter auch die Ueberschrift.
166
+ const trinkgeld = bon.tips.map((t) => ({ t, z: shiftTipLines(t) })).filter(({ z }) => z.length > 0);
167
+ if (trinkgeld.length > 0) {
168
+ zeilen.push(regel());
169
+ zeilen.push(text(labelText('shift_receipt.tips'), 'left', true));
170
+ for (const { t, z } of trinkgeld) {
171
+ zeilen.push(text(t.name ?? t.registerUserId));
172
+ for (const zeile of z)
173
+ zeilen.push(betrag(` ${zeile.text}`, zeile.cents));
174
+ }
175
+ }
176
+ return { lines: zeilen, paperSize, ruleset: CURRENT_LAYOUT_RULESET };
177
+ }
178
+ /**
179
+ * Bon einer Kassenbewegung (Einzahlung, Auszahlung, Umbuchung). Wirft
180
+ * (`KasseneckValidationError`, `scope: 'request'`), wenn die Bon-Daten keine
181
+ * Kassenbewegung sind.
182
+ */
183
+ export function cashMovementLayout(receipt, options = {}) {
184
+ const name = 'cashMovementLayout';
185
+ const paperSize = papier(name, options);
186
+ const bon = bewegungsBon({ name, pfad: 'receipt', scope: 'request' }, receipt);
187
+ const m = bon.movement;
188
+ const zeilen = [];
189
+ kopfblock(zeilen, bon, 'shift_receipt.title_movement');
190
+ if (bon.shift !== null)
191
+ zeilen.push(kopf(labelText('shift_receipt.shift'), labelText('shift_receipt.number', { number: bon.shift.number })));
192
+ zeilen.push(regel());
193
+ zeilen.push(kopf(labelText('shift_receipt.movement_number'), String(m.number)));
194
+ zeilen.push(kopf(labelText('shift_receipt.type'), artText(m.type)));
195
+ zeilen.push(betrag(labelText('shift_receipt.amount'), m.amountCents));
196
+ wennText(zeilen, labelText('shift_receipt.location'), m.location === null ? null : ortText(m.location));
197
+ wennText(zeilen, labelText('shift_receipt.recipient'), m.recipientName);
198
+ wennText(zeilen, labelText('shift_receipt.target_drawer'), m.targetDrawerName);
199
+ wennText(zeilen, labelText('shift_receipt.note'), m.note);
200
+ wennText(zeilen, labelText('shift_receipt.operator'), m.byName);
201
+ wennText(zeilen, labelText('shift_receipt.time'), zeit(m.at));
202
+ return { lines: zeilen, paperSize, ruleset: CURRENT_LAYOUT_RULESET };
203
+ }
@@ -22,7 +22,7 @@ import { KasseneckApiError } from '../client/errors.js';
22
22
  * ueber `KasseneckApiError.code` lesbar; die Kasse braucht dafuer einen
23
23
  * Rueckfallzweig.
24
24
  */
25
- export declare const REGISTER_ERROR_CODES: readonly ["account_not_found", "admin_required", "api_not_approved", "app_check_invalid", "app_check_missing", "cashregister_in_use", "cashregister_not_assigned", "cashregister_not_found", "cashregister_token_invalid", "cashregister_token_missing", "device_bound_elsewhere", "device_not_found", "device_not_paired", "device_takeover_required", "dialect_mismatch", "internal_translation_error", "licenses_exhausted", "live_not_enabled", "location_outside", "location_required", "login_failed", "login_mode_select_user", "login_unavailable", "method_not_allowed", "mfa_required", "not_found", "pairing_code_expired", "pairing_code_unknown", "pairing_code_used", "pairing_failed", "password_change_required", "register_user_no_business", "register_user_not_allowed", "register_user_not_found", "register_user_only", "response_translation_failed", "session_ended", "session_expired", "session_not_running", "session_other_cashregister", "too_many_attempts", "unauthorized", "user_disabled", "user_verification_failed", "validation", "route_missing"];
25
+ export declare const REGISTER_ERROR_CODES: readonly ["account_not_found", "action_confirmation_required", "admin_required", "api_not_approved", "app_check_invalid", "app_check_missing", "cashregister_in_use", "cashregister_not_assigned", "cashregister_not_found", "cashregister_token_invalid", "cashregister_token_missing", "device_bound_elsewhere", "device_not_found", "device_not_paired", "device_takeover_required", "dialect_mismatch", "internal_translation_error", "licenses_exhausted", "live_not_enabled", "location_outside", "location_required", "login_failed", "login_mode_select_user", "login_unavailable", "method_not_allowed", "mfa_required", "not_found", "pairing_code_expired", "pairing_code_unknown", "pairing_code_used", "pairing_failed", "password_change_required", "register_user_no_business", "register_user_not_allowed", "register_user_not_found", "register_user_only", "response_translation_failed", "second_factor_required", "session_ended", "session_expired", "session_not_running", "session_other_cashregister", "too_many_attempts", "unauthorized", "user_disabled", "user_verification_failed", "validation", "route_missing"];
26
26
  export type RegisterErrorCode = typeof REGISTER_ERROR_CODES[number];
27
27
  export declare function isRegisterErrorCode(value: unknown): value is RegisterErrorCode;
28
28
  /** Der Code eines geworfenen Fehlers, wenn es einer der Kassen-Anmeldung ist; sonst `undefined`. */
@@ -25,6 +25,7 @@ import { feldfehlerVon } from '../client/fehlercodes.js';
25
25
  */
26
26
  export const REGISTER_ERROR_CODES = Object.freeze([
27
27
  'account_not_found',
28
+ 'action_confirmation_required',
28
29
  'admin_required',
29
30
  'api_not_approved',
30
31
  'app_check_invalid',
@@ -60,6 +61,7 @@ export const REGISTER_ERROR_CODES = Object.freeze([
60
61
  'register_user_not_found',
61
62
  'register_user_only',
62
63
  'response_translation_failed',
64
+ 'second_factor_required',
63
65
  'session_ended',
64
66
  'session_expired',
65
67
  'session_not_running',
@@ -14,7 +14,7 @@ export declare function innereNamen(schema: SchemaObjekt): Map<string, string>;
14
14
  export declare const _RUECKGABE_NACH_AUSSEN: Readonly<Record<string, string>>;
15
15
  /**
16
16
  * Ein Belegdokument, wie Handler und Rand es vor der Uebersetzung sehen:
17
- * ohne Storno-Marke und Lager-Standort (storno-core.ohneInterneBelegFelder,
17
+ * ohne Storno-Marke, Lager-Standort, Lade und Schicht (storno-core.ohneInterneBelegFelder,
18
18
  * Rand ohneStornoMarke). Alles andere bleibt woertlich, auch `pruefangaben` im
19
19
  * Beleg selbst (der Server reicht sie so durch). Wirft, wenn es kein
20
20
  * Belegdokument ist.