@kreiseck/kasseneck-api 1.11.1 → 1.13.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 (187) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +2 -1
  3. package/dist/cjs/client/aufrufe.d.ts +4 -4
  4. package/dist/cjs/client/aufrufe.js +15 -0
  5. package/dist/cjs/client/receipts.d.ts +5 -0
  6. package/dist/cjs/client/receipts.js +65 -6
  7. package/dist/cjs/enums/voucher-action.d.ts +2 -0
  8. package/dist/cjs/enums/voucher-action.js +2 -0
  9. package/dist/cjs/enums/voucher-type.d.ts +10 -0
  10. package/dist/cjs/enums/voucher-type.js +10 -0
  11. package/dist/cjs/index.d.ts +3 -3
  12. package/dist/cjs/index.js +8 -3
  13. package/dist/cjs/models/index.d.ts +2 -2
  14. package/dist/cjs/models/index.js +6 -2
  15. package/dist/cjs/models/receipt-errors.d.ts +1 -1
  16. package/dist/cjs/models/receipt-errors.js +2 -0
  17. package/dist/cjs/models/receipt-item.d.ts +11 -3
  18. package/dist/cjs/models/receipt-item.js +22 -1
  19. package/dist/cjs/models/receipt.d.ts +13 -1
  20. package/dist/cjs/models/receipt.js +18 -2
  21. package/dist/cjs/models/voucher.d.ts +57 -4
  22. package/dist/cjs/models/voucher.js +111 -3
  23. package/dist/cjs/partner/fehler.d.ts +1 -1
  24. package/dist/cjs/partner/fehler.js +2 -0
  25. package/dist/cjs/partner/index.d.ts +1 -1
  26. package/dist/cjs/partner/typen.d.ts +7 -1
  27. package/dist/cjs/partner/typen.js +3 -0
  28. package/dist/cjs/pos/client.d.ts +2 -1
  29. package/dist/cjs/pos/client.js +3 -1
  30. package/dist/cjs/pos/gutschein.d.ts +90 -0
  31. package/dist/cjs/pos/gutschein.js +164 -0
  32. package/dist/cjs/pos/index.d.ts +3 -2
  33. package/dist/cjs/pos/index.js +13 -2
  34. package/dist/cjs/pos/settings.d.ts +3 -0
  35. package/dist/cjs/pos/settings.js +12 -10
  36. package/dist/cjs/pos/texte.d.ts +229 -0
  37. package/dist/cjs/pos/texte.js +96 -1
  38. package/dist/cjs/react/index.js +8 -2
  39. package/dist/cjs/receipt/due.d.ts +83 -2
  40. package/dist/cjs/receipt/due.js +202 -36
  41. package/dist/cjs/receipt/grid.js +13 -1
  42. package/dist/cjs/receipt/gutschein-plan.d.ts +104 -0
  43. package/dist/cjs/receipt/gutschein-plan.js +360 -0
  44. package/dist/cjs/receipt/index.d.ts +1 -1
  45. package/dist/cjs/receipt/index.js +3 -0
  46. package/dist/cjs/receipt/layout.js +175 -8
  47. package/dist/cjs/register/pairing.d.ts +21 -1
  48. package/dist/cjs/register/pairing.js +17 -0
  49. package/dist/cjs/stored/bon-layout.d.ts +8 -0
  50. package/dist/cjs/stored/bon-layout.js +23 -0
  51. package/dist/cjs/stored/draht.d.ts +3 -1
  52. package/dist/cjs/stored/draht.js +53 -6
  53. package/dist/cjs/stored/einstellungen.js +2 -1
  54. package/dist/cjs/stored/gutscheine.d.ts +3 -0
  55. package/dist/cjs/stored/gutscheine.js +60 -0
  56. package/dist/cjs/stored/index.js +2 -17
  57. package/dist/cjs/stored/vokabular.d.ts +6 -4
  58. package/dist/cjs/stored/vokabular.js +48 -2
  59. package/dist/cjs/version.d.ts +1 -1
  60. package/dist/cjs/version.js +1 -1
  61. package/dist/esm/client/aufrufe.d.ts +4 -4
  62. package/dist/esm/client/aufrufe.js +15 -0
  63. package/dist/esm/client/receipts.d.ts +5 -0
  64. package/dist/esm/client/receipts.js +66 -8
  65. package/dist/esm/enums/voucher-action.d.ts +2 -0
  66. package/dist/esm/enums/voucher-action.js +2 -0
  67. package/dist/esm/enums/voucher-type.d.ts +10 -0
  68. package/dist/esm/enums/voucher-type.js +10 -0
  69. package/dist/esm/index.d.ts +3 -3
  70. package/dist/esm/index.js +3 -3
  71. package/dist/esm/models/index.d.ts +2 -2
  72. package/dist/esm/models/index.js +2 -2
  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 +11 -3
  76. package/dist/esm/models/receipt-item.js +20 -1
  77. package/dist/esm/models/receipt.d.ts +13 -1
  78. package/dist/esm/models/receipt.js +18 -2
  79. package/dist/esm/models/voucher.d.ts +57 -4
  80. package/dist/esm/models/voucher.js +108 -3
  81. package/dist/esm/partner/fehler.d.ts +1 -1
  82. package/dist/esm/partner/fehler.js +2 -0
  83. package/dist/esm/partner/index.d.ts +1 -1
  84. package/dist/esm/partner/typen.d.ts +7 -1
  85. package/dist/esm/partner/typen.js +3 -0
  86. package/dist/esm/pos/client.d.ts +2 -1
  87. package/dist/esm/pos/client.js +3 -1
  88. package/dist/esm/pos/gutschein.d.ts +90 -0
  89. package/dist/esm/pos/gutschein.js +158 -0
  90. package/dist/esm/pos/index.d.ts +3 -2
  91. package/dist/esm/pos/index.js +4 -2
  92. package/dist/esm/pos/settings.d.ts +3 -0
  93. package/dist/esm/pos/settings.js +2 -0
  94. package/dist/esm/pos/texte.d.ts +229 -0
  95. package/dist/esm/pos/texte.js +94 -0
  96. package/dist/esm/react/index.js +8 -2
  97. package/dist/esm/receipt/due.d.ts +83 -2
  98. package/dist/esm/receipt/due.js +198 -34
  99. package/dist/esm/receipt/grid.js +13 -1
  100. package/dist/esm/receipt/gutschein-plan.d.ts +104 -0
  101. package/dist/esm/receipt/gutschein-plan.js +352 -0
  102. package/dist/esm/receipt/index.d.ts +1 -1
  103. package/dist/esm/receipt/index.js +1 -1
  104. package/dist/esm/receipt/layout.js +175 -8
  105. package/dist/esm/register/pairing.d.ts +21 -1
  106. package/dist/esm/register/pairing.js +16 -0
  107. package/dist/esm/stored/bon-layout.d.ts +8 -0
  108. package/dist/esm/stored/bon-layout.js +20 -0
  109. package/dist/esm/stored/draht.d.ts +3 -1
  110. package/dist/esm/stored/draht.js +52 -6
  111. package/dist/esm/stored/einstellungen.js +3 -2
  112. package/dist/esm/stored/gutscheine.d.ts +3 -0
  113. package/dist/esm/stored/gutscheine.js +56 -0
  114. package/dist/esm/stored/index.js +2 -17
  115. package/dist/esm/stored/vokabular.d.ts +6 -4
  116. package/dist/esm/stored/vokabular.js +48 -2
  117. package/dist/esm/version.d.ts +1 -1
  118. package/dist/esm/version.js +1 -1
  119. package/fixtures/expected/voucher-cancellation.grid32.txt +30 -0
  120. package/fixtures/expected/voucher-cancellation.grid48.txt +29 -0
  121. package/fixtures/expected/voucher-cancellation.lines.json +252 -0
  122. package/fixtures/expected/voucher-cancellation.sheet32.json +206 -0
  123. package/fixtures/expected/voucher-cancellation.sheet48.json +200 -0
  124. package/fixtures/expected/voucher-multi-purpose-redeem.grid32.txt +25 -0
  125. package/fixtures/expected/voucher-multi-purpose-redeem.grid48.txt +25 -0
  126. package/fixtures/expected/voucher-multi-purpose-redeem.lines.json +259 -0
  127. package/fixtures/expected/voucher-multi-purpose-redeem.sheet32.json +176 -0
  128. package/fixtures/expected/voucher-multi-purpose-redeem.sheet48.json +176 -0
  129. package/fixtures/expected/voucher-multi-purpose-sale.grid32.txt +24 -0
  130. package/fixtures/expected/voucher-multi-purpose-sale.grid48.txt +23 -0
  131. package/fixtures/expected/voucher-multi-purpose-sale.lines.json +216 -0
  132. package/fixtures/expected/voucher-multi-purpose-sale.sheet32.json +170 -0
  133. package/fixtures/expected/voucher-multi-purpose-sale.sheet48.json +164 -0
  134. package/fixtures/expected/voucher-payout.grid32.txt +23 -0
  135. package/fixtures/expected/voucher-payout.grid48.txt +22 -0
  136. package/fixtures/expected/voucher-payout.lines.json +216 -0
  137. package/fixtures/expected/voucher-payout.sheet32.json +164 -0
  138. package/fixtures/expected/voucher-payout.sheet48.json +158 -0
  139. package/fixtures/expected/voucher-service-redeem.grid32.txt +26 -0
  140. package/fixtures/expected/voucher-service-redeem.grid48.txt +25 -0
  141. package/fixtures/expected/voucher-service-redeem.lines.json +261 -0
  142. package/fixtures/expected/voucher-service-redeem.sheet32.json +182 -0
  143. package/fixtures/expected/voucher-service-redeem.sheet48.json +176 -0
  144. package/fixtures/expected/voucher-single-purpose-redeem-exempt.grid32.txt +30 -0
  145. package/fixtures/expected/voucher-single-purpose-redeem-exempt.grid48.txt +29 -0
  146. package/fixtures/expected/voucher-single-purpose-redeem-exempt.lines.json +331 -0
  147. package/fixtures/expected/voucher-single-purpose-redeem-exempt.sheet32.json +206 -0
  148. package/fixtures/expected/voucher-single-purpose-redeem-exempt.sheet48.json +200 -0
  149. package/fixtures/expected/voucher-single-purpose-redeem.grid32.txt +25 -0
  150. package/fixtures/expected/voucher-single-purpose-redeem.grid48.txt +24 -0
  151. package/fixtures/expected/voucher-single-purpose-redeem.lines.json +246 -0
  152. package/fixtures/expected/voucher-single-purpose-redeem.sheet32.json +176 -0
  153. package/fixtures/expected/voucher-single-purpose-redeem.sheet48.json +170 -0
  154. package/fixtures/expected/voucher-single-purpose-sale.grid32.txt +26 -0
  155. package/fixtures/expected/voucher-single-purpose-sale.grid48.txt +24 -0
  156. package/fixtures/expected/voucher-single-purpose-sale.lines.json +225 -0
  157. package/fixtures/expected/voucher-single-purpose-sale.sheet32.json +182 -0
  158. package/fixtures/expected/voucher-single-purpose-sale.sheet48.json +170 -0
  159. package/fixtures/hobex-hps-codes.json +1 -1
  160. package/fixtures/invoice-api.schema.json +1 -1
  161. package/fixtures/invoice-texts.json +1 -1
  162. package/fixtures/manifest.json +64 -0
  163. package/fixtures/pos-texts.json +257 -1
  164. package/fixtures/receipt-due-generated.json +1 -1
  165. package/fixtures/receipt-due-voucher-generated.json +494 -0
  166. package/fixtures/receipts/voucher-cancellation.json +76 -0
  167. package/fixtures/receipts/voucher-multi-purpose-redeem.json +62 -0
  168. package/fixtures/receipts/voucher-multi-purpose-sale.json +63 -0
  169. package/fixtures/receipts/voucher-payout.json +65 -0
  170. package/fixtures/receipts/voucher-service-redeem.json +82 -0
  171. package/fixtures/receipts/voucher-single-purpose-redeem-exempt.json +86 -0
  172. package/fixtures/receipts/voucher-single-purpose-redeem.json +71 -0
  173. package/fixtures/receipts/voucher-single-purpose-sale.json +88 -0
  174. package/fixtures/surface.json +35 -5
  175. package/fixtures/v3/antworten/belege.json +3405 -3
  176. package/fixtures/v3/antworten/belegmail.json +5 -2
  177. package/fixtures/v3/antworten/kasse-belege.json +3423 -3
  178. package/fixtures/v3/antworten/kasse.json +621 -14
  179. package/fixtures/v3/antworten/lager.json +1 -1
  180. package/fixtures/v3/antworten/rechnungen.json +5 -2
  181. package/fixtures/v3/antworten/storno.json +214 -3
  182. package/fixtures/v3/stored/belege.json +5 -2
  183. package/fixtures/v3/stored/kasse.json +225 -4
  184. package/fixtures/v3/stored/rechnungen.json +5 -2
  185. package/fixtures/v3/v3-vokabular.json +1375 -23
  186. package/fixtures/v3/zahlbetrag-faelle.json +293 -3
  187. package/package.json +4 -1
@@ -573,6 +573,152 @@ function gutscheinText(voucher) {
573
573
  }
574
574
  return text;
575
575
  }
576
+ /*
577
+ * Gutscheine der neuen Form (Spec 2026-10-14, „Bon und Beleg“). Der Dart-
578
+ * Zwilling uebernimmt Texte und Spaltenbreiten (Zwoelftel) genau so:
579
+ *
580
+ * Verkauf Einzweck/Leistung Positionszeile 7:5 „1 x <Name> <Code>“ | „50,00 A“
581
+ * Verkauf Mehrzweck dieselbe Zeile (Position im 0-%-Topf oder, vor dem
582
+ * Stichtag, aus dem Eintrag) + Text „ USt bei Einlösung“
583
+ * Einloesung Einzweck/ 8:4 „Gutschein ····-9DWT“ | „-30,50 A“, nach der letzten
584
+ * Leistung Zeile dieses Gutscheins Text „ Einzweck, versteuert mit
585
+ * Beleg K1-ID-4711“ (bzw. „Leistung, …“, „…, befreit
586
+ * verkauft (Kleinunternehmer)“, 47 Zeichen: passt auf
587
+ * 80 mm) und 8:4 „ Rest am Gutschein“ | „19,50 “ bzw.
588
+ * „2 Stück“
589
+ * Einloesung Mehrzweck nach „Zwischensumme“ 8:4 „Gutschein ····-9DWT“ |
590
+ * „-17,50 €“ und „ Rest am Gutschein“ | „32,50 €“
591
+ * Auszahlung 8:4 „Auszahlung Gutschein ····-9DWT“ | „-12,00 D“,
592
+ * Text „ Grund: <Grund>“
593
+ * Aktion Rabattzeile wie bisher (Promotionsgutschein)
594
+ *
595
+ * Vermerke sind Textzeilen mit zwei fuehrenden Leerzeichen; das Raster bricht
596
+ * sie mit demselben Einzug um (grid.ts, haengender Einzug), auf 58 mm etwa
597
+ * „ Einzweck, versteuert mit“ / „ Beleg K1-ID-4711“.
598
+ *
599
+ * Die Zusatzzeilen kommen nur aus dem Eintrag: ein Beleg, den der Server fuer
600
+ * ein aelteres Paket aufbereitet hat (Eintraege mit Position entfernt, Code an
601
+ * der Position, functions/beleg-layout.js `gutscheineFuerPaket`), zeigt seine
602
+ * Positionen wie jede andere Position. Ein Betrag zaehlt immer genau einmal:
603
+ * Positionen in der Steuertabelle, Eintraege mit `zeroRateBucket` nie.
604
+ */
605
+ /** Steht der Betrag des Eintrags als Position am Beleg (Einzweck, Leistung, 0-%-Topf)? */
606
+ function gutscheinStehtAlsPosition(voucher) {
607
+ return voucher.type === index_js_1.VoucherType.single_purpose || voucher.type === index_js_1.VoucherType.service || voucher.zeroRateBucket === true;
608
+ }
609
+ /** Mehrzweckgutschein mit Code (Altart `value`, vom Server als `multi_purpose` gekennzeichnet). */
610
+ function istMehrzweckMitCode(voucher) {
611
+ return voucher.type === index_js_1.VoucherType.value && voucher.voucherType === index_js_1.VoucherType.multi_purpose;
612
+ }
613
+ /**
614
+ * Code fuer die Belegzeile: die Maske des Eintrags, ohne den Platzhalter
615
+ * `no-code`, den der Server Alt-Eintraegen ohne Code setzt (index.js
616
+ * checkVouchersIsValid). Leer, wenn es keinen gibt.
617
+ */
618
+ function gutscheinCodeZusatz(voucher) {
619
+ return typeof voucher.code === 'string' && voucher.code !== '' && voucher.code !== 'no-code' ? ` ${voucher.code}` : '';
620
+ }
621
+ /** Die vier letzten Zeichen des Codes (`codeSuffix`, sonst aus der Maske), oder null. */
622
+ function gutscheinEndung(voucher) {
623
+ if (typeof voucher.codeSuffix === 'string' && voucher.codeSuffix !== '')
624
+ return voucher.codeSuffix;
625
+ return typeof voucher.code === 'string' && voucher.code.length >= 4 && voucher.code !== 'no-code' ? voucher.code.slice(-4) : null;
626
+ }
627
+ const MEHRZWECK_UST_TEXT = ' USt bei Einlösung';
628
+ /**
629
+ * Welche Gutscheinposition (`voucher`/`voucher_float`) gehoert zu welchem
630
+ * Eintrag? Jede Position hoechstens einem Eintrag, Eintraege in ihrer
631
+ * Reihenfolge (der Server haengt die Positionen in derselben Reihenfolge an).
632
+ * Verkauf: erste freie Position der Art mit demselben Betrag und ohne Code im
633
+ * Namen (wie `gutscheineFuerPaket` des Servers). Einloesung: freie Positionen
634
+ * mit genau dem Namen `Gutschein ····-<Endung>`, Auszahlung: `Auszahlung
635
+ * Gutschein ····-<Endung>` (gutschein-beleg.plane) -- der Reihe nach, bis
636
+ * ihr Betrag den des Eintrags erreicht (ein Kleinunternehmer-Einzweck oder
637
+ * eine Leistung ueber mehrere Saetze hat je Satz eine Position; zwei
638
+ * Gutscheine mit derselben Endung teilen sich so nie eine Position). Ohne
639
+ * Betrag am Eintrag genau eine Position.
640
+ */
641
+ function gutscheinPositionen(receipt) {
642
+ const zuordnung = new Map();
643
+ const istGutscheinzeile = (it) => it.kind === 'voucher' || it.kind === 'voucher_float';
644
+ for (const voucher of receipt.vouchers) {
645
+ if (!gutscheinStehtAlsPosition(voucher))
646
+ continue;
647
+ if (voucher.action === index_js_1.VoucherAction.sell) {
648
+ const art = voucher.zeroRateBucket === true ? 'voucher_float' : 'voucher';
649
+ const cents = Number.isInteger(voucher.amountCents) ? Math.abs(voucher.amountCents) : Math.abs(voucher.valueCents ?? 0);
650
+ const i = receipt.items.findIndex((it, k) => !zuordnung.has(k) && it.kind === art
651
+ && Math.abs((0, index_js_2.receiptItemTotalCents)(it)) === cents && !it.name.includes('····'));
652
+ if (i >= 0)
653
+ zuordnung.set(i, { voucher, letzte: true });
654
+ continue;
655
+ }
656
+ const endung = gutscheinEndung(voucher);
657
+ if (endung == null || (voucher.action !== index_js_1.VoucherAction.redeem && voucher.action !== index_js_1.VoucherAction.payout))
658
+ continue;
659
+ const name = `${voucher.action === index_js_1.VoucherAction.payout ? 'Auszahlung ' : ''}Gutschein ····-${endung}`;
660
+ const ziel = Number.isInteger(voucher.amountCents) ? Math.abs(voucher.amountCents) : Math.abs(voucher.valueCents ?? 0);
661
+ const genommen = [];
662
+ let summe = 0;
663
+ receipt.items.forEach((it, k) => {
664
+ if (zuordnung.has(k) || !istGutscheinzeile(it) || it.name !== name)
665
+ return;
666
+ if (genommen.length > 0 && (ziel <= 0 || summe >= ziel))
667
+ return;
668
+ genommen.push(k);
669
+ summe += Math.abs((0, index_js_2.receiptItemTotalCents)(it));
670
+ });
671
+ genommen.forEach((k, n) => zuordnung.set(k, { voucher, letzte: n === genommen.length - 1 }));
672
+ }
673
+ return zuordnung;
674
+ }
675
+ /**
676
+ * Zeilen einer Gutscheinposition mit ihrem Eintrag. Verkauf als gewoehnliche
677
+ * Positionszeile mit dem Code des Eintrags; Einloesung und Auszahlung ohne
678
+ * Menge in der Breite der Rabattzeilen (8:4), damit „Gutschein ····-9DWT“ auch
679
+ * auf 58 mm in einer Zeile steht.
680
+ */
681
+ function gutscheinPositionsZeilen(item, satz, zu) {
682
+ const v = zu.voucher;
683
+ if (v.action === index_js_1.VoucherAction.sell) {
684
+ const zeilen = [positionsZeile({ ...item, name: `${item.name}${gutscheinCodeZusatz(v)}` }, satz)];
685
+ if (v.type === index_js_1.VoucherType.value)
686
+ zeilen.push(textZeile(MEHRZWECK_UST_TEXT));
687
+ return zeilen;
688
+ }
689
+ const zeilen = [{
690
+ kind: 'columns',
691
+ columns: [
692
+ { text: item.name, width: 8, align: 'left' },
693
+ { text: `${formatCents((0, index_js_2.receiptItemTotalCents)(item))} ${satz.category}`, width: 4, align: 'right' },
694
+ ],
695
+ }];
696
+ if (!zu.letzte)
697
+ return zeilen;
698
+ if (v.action === index_js_1.VoucherAction.payout) {
699
+ const grund = typeof v.reason === 'string' ? v.reason.trim() : '';
700
+ if (grund !== '')
701
+ zeilen.push(textZeile(` Grund: ${grund}`));
702
+ return zeilen;
703
+ }
704
+ const art = v.type === index_js_1.VoucherType.service ? 'Leistung' : 'Einzweck';
705
+ if (typeof v.taxedWith === 'string' && v.taxedWith !== '') {
706
+ zeilen.push(textZeile(` ${art}, versteuert mit Beleg ${v.taxedWith}`));
707
+ }
708
+ else if (v.exempt === true) {
709
+ zeilen.push(textZeile(` ${art}, befreit verkauft (Kleinunternehmer)`));
710
+ }
711
+ // Rest: Betrag fluchtet mit den Positionsbetraegen (zwei Zeichen statt
712
+ // „ A“), Stueck rechtsbuendig. Die Gesamtzahl kennt der Beleg nicht.
713
+ if (v.type === index_js_1.VoucherType.service) {
714
+ if (Number.isInteger(v.remainingQuantityAfter))
715
+ zeilen.push(paarZeile(' Rest am Gutschein', `${v.remainingQuantityAfter} Stück`, 8, 4));
716
+ }
717
+ else if (Number.isInteger(v.balanceAfterCents)) {
718
+ zeilen.push(paarZeile(' Rest am Gutschein', `${formatCents(v.balanceAfterCents)} `, 8, 4));
719
+ }
720
+ return zeilen;
721
+ }
576
722
  // ----------------------------------------------------------------- Bauteile
577
723
  function textZeile(text, align = 'left', bold = false) {
578
724
  return { kind: 'text', text, align, bold };
@@ -917,13 +1063,15 @@ function buildReceiptLayout(receipt, company, options = {}) {
917
1063
  .filter((it) => !(0, index_js_2.isTipItem)(it) && !(0, index_js_2.isDiscountItem)(it))
918
1064
  .reduce((summe, it) => summe + (0, index_js_2.receiptItemTotalCents)(it), 0);
919
1065
  let zwischensummeGezeigt = false;
1066
+ const gutscheinZuordnung = gutscheinPositionen(receipt);
920
1067
  let i = 0;
921
1068
  while (i < receipt.items.length) {
922
1069
  const item = receipt.items[i];
923
1070
  if (!(0, index_js_2.isTipItem)(item) && !(0, index_js_2.isDiscountItem)(item)) {
924
1071
  const satz = steuersatz(item.vat);
925
1072
  gruppeVon(satz).bruttoCents += (0, index_js_2.receiptItemTotalCents)(item);
926
- lines.push(positionsZeile(item, satz));
1073
+ const gutschein = gutscheinZuordnung.get(i);
1074
+ lines.push(...(gutschein ? gutscheinPositionsZeilen(item, satz, gutschein) : [positionsZeile(item, satz)]));
927
1075
  i += 1;
928
1076
  continue;
929
1077
  }
@@ -960,22 +1108,31 @@ function buildReceiptLayout(receipt, company, options = {}) {
960
1108
  // Promotionsgutscheine mindern die Steuergruppen anteilig.
961
1109
  // Am Storno-Beleg zaehlen Wertgutscheine mit umgekehrtem Vorzeichen
962
1110
  // (receiptValueVoucherSign): der verkaufte wird zurueckgezahlt.
1111
+ // Eintraege mit `zeroRateBucket` stehen schon als Position (0-%-Topf).
963
1112
  const wgVz = (0, index_js_2.receiptValueVoucherSign)(receipt);
964
1113
  let promoCents = 0;
965
1114
  for (const voucher of receipt.vouchers) {
1115
+ // Betrag steht als Position (Einzweck, Leistung, 0-%-Topf): weder Zeile noch 0-%-Zeile aus dem Eintrag.
1116
+ if (gutscheinStehtAlsPosition(voucher))
1117
+ continue;
966
1118
  const wert = voucher.valueCents ?? 0;
967
1119
  const vz = voucher.type === index_js_1.VoucherType.value ? wgVz : 1;
968
1120
  if ((0, index_js_2.voucherIsValid)(voucher) && voucher.action === index_js_1.VoucherAction.sell) {
969
1121
  gruppeVon(steuersatz(index_js_1.VatRate.vat0)).bruttoCents += vz * wert;
970
1122
  }
971
1123
  if (voucher.action === index_js_1.VoucherAction.sell && voucher.type === index_js_1.VoucherType.value) {
1124
+ // Mehrzweck mit Code (vor dem Stichtag nur als Eintrag): wie die Position im 0-%-Topf.
1125
+ const mehrzweck = istMehrzweckMitCode(voucher);
1126
+ const code = gutscheinCodeZusatz(voucher);
972
1127
  lines.push({
973
1128
  kind: 'columns',
974
1129
  columns: [
975
- { text: `1 x ${gutscheinText(voucher)}`, width: 7, align: 'left' },
1130
+ { text: `1 x ${mehrzweck ? `Mehrzweckgutschein${code}` : gutscheinText(voucher)}`, width: 7, align: 'left' },
976
1131
  { text: `${formatCents(vz * wert)} ${index_js_1.VatRate.vat0.category}`, width: 5, align: 'right' },
977
1132
  ],
978
1133
  });
1134
+ if (mehrzweck)
1135
+ lines.push(textZeile(MEHRZWECK_UST_TEXT));
979
1136
  }
980
1137
  if (voucher.action === index_js_1.VoucherAction.redeem && voucher.type === index_js_1.VoucherType.promo) {
981
1138
  // Am Storno-Beleg steht der Promotionsgutschein nur als Bezug: den Rabatt
@@ -1034,12 +1191,22 @@ function buildReceiptLayout(receipt, company, options = {}) {
1034
1191
  if (summeCents !== zwischensummeCents) {
1035
1192
  lines.push(paarZeile('Zwischensumme', `${formatCents(zwischensummeCents)} €`));
1036
1193
  for (const voucher of receipt.vouchers) {
1037
- if (voucher.action === index_js_1.VoucherAction.redeem && voucher.type === index_js_1.VoucherType.value) {
1038
- // Breite 8:4 — "Wertgutschein GS-1" (Label + Code) passt so auf 58 mm
1039
- // in die Zeile, statt den Code in eine Folgezeile fliessen zu lassen.
1040
- // Am Storno-Beleg geht der Wert auf den Gutschein zurueck: ohne Minus.
1041
- const wert = voucher.valueCents ?? 0;
1042
- lines.push(paarZeile(gutscheinText(voucher), wgVz === 1 ? `-${formatCents(wert)} €` : `${formatCents(wert)} €`, 8, 4));
1194
+ if (voucher.type !== index_js_1.VoucherType.value || voucher.zeroRateBucket === true)
1195
+ continue;
1196
+ if (voucher.action !== index_js_1.VoucherAction.redeem && voucher.action !== index_js_1.VoucherAction.payout)
1197
+ continue;
1198
+ // Breite 8:4 — "Wertgutschein GS-1" (Label + Code) passt so auf 58 mm
1199
+ // in die Zeile, statt den Code in eine Folgezeile fliessen zu lassen.
1200
+ // Am Storno-Beleg geht der Wert auf den Gutschein zurueck: ohne Minus.
1201
+ // Mehrzweck mit Code: „Gutschein ····-9DWT“ und darunter der Rest
1202
+ // (Zahlungszeile laut Spec); eine Auszahlung ohne `zeroRateBucket`
1203
+ // (schreibt der Server nicht) zaehlt wie in receiptSumCents.
1204
+ const wert = voucher.valueCents ?? 0;
1205
+ const endung = istMehrzweckMitCode(voucher) ? gutscheinEndung(voucher) : null;
1206
+ const text = endung != null ? `Gutschein ····-${endung}` : gutscheinText(voucher);
1207
+ lines.push(paarZeile(voucher.action === index_js_1.VoucherAction.payout ? `Auszahlung ${text}` : text, wgVz === 1 ? `-${formatCents(wert)} €` : `${formatCents(wert)} €`, 8, 4));
1208
+ if (endung != null && wgVz === 1 && Number.isInteger(voucher.balanceAfterCents)) {
1209
+ lines.push(paarZeile(' Rest am Gutschein', `${formatCents(voucher.balanceAfterCents)} €`, 8, 4));
1043
1210
  }
1044
1211
  }
1045
1212
  lines.push({ kind: 'rule', char: '-' });
@@ -199,6 +199,12 @@ export interface RegisterUserPerms {
199
199
  stocktakeClose?: boolean;
200
200
  /** Standort der Kasse waehlen (setMyCashregisterStockLocation). */
201
201
  stockLocation?: boolean;
202
+ /** Gutschein verkaufen (fehlt bei altem Server: gilt wie beim Kassier, siehe [voucherPermsOf]). */
203
+ voucherSell?: boolean;
204
+ /** Gutschein einloesen (fehlt bei altem Server: gilt wie beim Kassier). */
205
+ voucherRedeem?: boolean;
206
+ /** Gutscheine verwalten, z. B. Etikett nachdrucken (fehlt: verweigert). */
207
+ voucherManage?: boolean;
202
208
  [weiteresRecht: string]: boolean | RegisterScope | undefined;
203
209
  }
204
210
  /**
@@ -209,7 +215,7 @@ export interface RegisterUserPerms {
209
215
  * nicht fuehrt (Tippfehler, umbenanntes Recht), kommt hier nicht durch.
210
216
  * Die Gegenrichtung schliesst [_RechteVollstaendig] direkt darunter.
211
217
  */
212
- export declare const REGISTER_PERMS: readonly ["sell", "cancel", "articles", "layout", "reports", "takeover", "cancelScope", "receiptsScope", "drawer", "discount", "tipAssign", "stockView", "stockCosts", "stockMove", "stockLoss", "stocktakeCount", "stocktakeClose", "stockLocation"];
218
+ export declare const REGISTER_PERMS: readonly ["sell", "cancel", "articles", "layout", "reports", "takeover", "cancelScope", "receiptsScope", "drawer", "discount", "tipAssign", "stockView", "stockCosts", "stockMove", "stockLoss", "stocktakeCount", "stocktakeClose", "stockLocation", "voucherSell", "voucherRedeem", "voucherManage"];
213
219
  /** Reichweite lesen, mit der Migration des Backends (register-auth.js). */
214
220
  export declare function cancelScopeOf(perms: RegisterUserPerms | null | undefined): RegisterScope;
215
221
  export declare function receiptsScopeOf(perms: RegisterUserPerms | null | undefined): RegisterScope;
@@ -219,6 +225,20 @@ export declare function receiptsScopeOf(perms: RegisterUserPerms | null | undefi
219
225
  * sperrt; alle anderen Lager-Rechte gelten ohne Schluessel als verweigert.
220
226
  */
221
227
  export declare function stockViewOf(perms: RegisterUserPerms | null | undefined): boolean;
228
+ /** Die drei Gutschein-Rechte, aufgeloest (kein Schluessel fehlt mehr). */
229
+ export interface VoucherPerms {
230
+ voucherSell: boolean;
231
+ voucherRedeem: boolean;
232
+ voucherManage: boolean;
233
+ }
234
+ /**
235
+ * Gutschein-Rechte lesen. Das Backend loest sie bei der Anmeldung auf; ein
236
+ * alter Server (und `/api`-Antworten) kennt sie nicht. Dann gilt je fehlendem
237
+ * Schluessel die Vorgabe des Kassiers: verkaufen und einloesen ja, verwalten
238
+ * nein. Ein ausdrueckliches `false` sperrt; was kein Wahrheitswert ist,
239
+ * erteilt nichts.
240
+ */
241
+ export declare function voucherPermsOf(perms: Partial<RegisterUserPerms> | null | undefined): VoucherPerms;
222
242
  /** Der angemeldete Kassen-Benutzer. */
223
243
  export interface RegisterUser {
224
244
  id: string;
@@ -4,6 +4,7 @@ exports.REGISTER_PERMS = void 0;
4
4
  exports.cancelScopeOf = cancelScopeOf;
5
5
  exports.receiptsScopeOf = receiptsScopeOf;
6
6
  exports.stockViewOf = stockViewOf;
7
+ exports.voucherPermsOf = voucherPermsOf;
7
8
  exports.pairRegisterDevice = pairRegisterDevice;
8
9
  exports.unpairRegisterDevice = unpairRegisterDevice;
9
10
  exports.listRegisterSessionsForDevice = listRegisterSessionsForDevice;
@@ -62,6 +63,7 @@ exports.REGISTER_PERMS = [
62
63
  'sell', 'cancel', 'articles', 'layout', 'reports', 'takeover',
63
64
  'cancelScope', 'receiptsScope', 'drawer', 'discount', 'tipAssign',
64
65
  'stockView', 'stockCosts', 'stockMove', 'stockLoss', 'stocktakeCount', 'stocktakeClose', 'stockLocation',
66
+ 'voucherSell', 'voucherRedeem', 'voucherManage',
65
67
  ];
66
68
  /** Reichweite lesen, mit der Migration des Backends (register-auth.js). */
67
69
  function cancelScopeOf(perms) {
@@ -88,6 +90,21 @@ function stockViewOf(perms) {
88
90
  return false;
89
91
  return perms.stockView === undefined ? true : perms.stockView === true;
90
92
  }
93
+ /**
94
+ * Gutschein-Rechte lesen. Das Backend loest sie bei der Anmeldung auf; ein
95
+ * alter Server (und `/api`-Antworten) kennt sie nicht. Dann gilt je fehlendem
96
+ * Schluessel die Vorgabe des Kassiers: verkaufen und einloesen ja, verwalten
97
+ * nein. Ein ausdrueckliches `false` sperrt; was kein Wahrheitswert ist,
98
+ * erteilt nichts.
99
+ */
100
+ function voucherPermsOf(perms) {
101
+ const lies = (wert, vorgabe) => (wert === undefined ? vorgabe : wert === true);
102
+ return {
103
+ voucherSell: lies(perms?.voucherSell, true),
104
+ voucherRedeem: lies(perms?.voucherRedeem, true),
105
+ voucherManage: lies(perms?.voucherManage, false),
106
+ };
107
+ }
91
108
  /**
92
109
  * Geraet koppeln: der Kopplungs-Code wird gegen den dauerhaften Ausweis dieses
93
110
  * Geraets getauscht. **Ohne Anmeldung** — der Code ist der Nachweis.
@@ -0,0 +1,8 @@
1
+ import type { ReceiptWithCompany } from '../client/receipts.js';
2
+ import { type ReceiptLayout } from '../receipt/layout.js';
3
+ /**
4
+ * Layout wie `beleg-layout.layoutFuerBeleg`: 80 mm, Regelwerk mindestens das
5
+ * aktuelle. Kann der Server es nicht bauen, liefert er `null` (dann baut
6
+ * [receiptLayoutFromResult] selbst); ebenso hier.
7
+ */
8
+ export declare function layoutWieServer(b: ReceiptWithCompany, regelwerk: unknown): ReceiptLayout | null;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.layoutWieServer = layoutWieServer;
4
+ const layout_js_1 = require("../receipt/layout.js");
5
+ const gutscheine_js_1 = require("./gutscheine.js");
6
+ /**
7
+ * Layout wie `beleg-layout.layoutFuerBeleg`: 80 mm, Regelwerk mindestens das
8
+ * aktuelle. Kann der Server es nicht bauen, liefert er `null` (dann baut
9
+ * [receiptLayoutFromResult] selbst); ebenso hier.
10
+ */
11
+ function layoutWieServer(b, regelwerk) {
12
+ const ruleset = Math.max(Number(regelwerk) || 1, layout_js_1.CURRENT_LAYOUT_RULESET);
13
+ try {
14
+ // Wie layoutFuerBeleg: Gutscheine mit Code stehen am Bon als Positionen,
15
+ // die uebrigen Eintraege so, wie das vendorte Paket des Servers sie liest.
16
+ return (0, layout_js_1.buildReceiptLayout)((0, gutscheine_js_1.wieVendortesPaket)((0, gutscheine_js_1.gutscheineFuerPaket)(b.receipt)), b.company, {
17
+ paperSize: 'mm80', ruleset, testCashregister: b.testCashregister, testSignature: b.testSignature, registrationInfo: b.registrationInfo,
18
+ });
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
@@ -1,4 +1,4 @@
1
- import { type SchemaEintrag, type SchemaObjekt } from './vokabular.js';
1
+ import { type SchemaEintrag, type SchemaObjekt, type Wertverweis } from './vokabular.js';
2
2
  export type Objekt = Record<string, unknown>;
3
3
  export declare const hat: (o: object, k: string) => boolean;
4
4
  export declare const istObjekt: (w: unknown) => w is Objekt;
@@ -10,6 +10,8 @@ export declare function regel(eintrag: SchemaEintrag): {
10
10
  };
11
11
  /** Innere Namen einer Schema-Ebene (was der Server innen an dieser Stelle kennt). */
12
12
  export declare function innereNamen(schema: SchemaObjekt): Map<string, string>;
13
+ /** Zuordnung eines Wertverweises: benannter Katalog oder eingebettet. */
14
+ export declare function katalogVon(verweis: Wertverweis): Readonly<Record<string, string>> | undefined;
13
15
  /** Rueckgabe-Wahl innen -> aussen (Katalog RUECKGABE; stored.test.ts vergleicht mit dem Vertrag). */
14
16
  export declare const _RUECKGABE_NACH_AUSSEN: Readonly<Record<string, string>>;
15
17
  /**
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports._RUECKGABE_NACH_AUSSEN = exports.istObjekt = exports.hat = void 0;
4
4
  exports.regel = regel;
5
5
  exports.innereNamen = innereNamen;
6
+ exports.katalogVon = katalogVon;
6
7
  exports._storedReceiptInner = _storedReceiptInner;
7
8
  exports.pruefangabenDerHuelle = pruefangabenDerHuelle;
8
9
  exports._receiptEnvelopeToWire = _receiptEnvelopeToWire;
@@ -79,16 +80,21 @@ function anPfad(obj, teile, fn) {
79
80
  */
80
81
  function werteNachAussen(obj, werte) {
81
82
  let raus = obj;
82
- for (const [pfad, { $catalog }] of Object.entries(werte)) {
83
- const katalog = vokabular_js_1.KATALOGE[$catalog];
83
+ for (const [pfad, verweis] of Object.entries(werte)) {
84
+ const katalog = katalogVon(verweis);
84
85
  if (!katalog)
85
- throw new Error(`stored: Katalog ${$catalog} fehlt im Vokabular`);
86
+ throw new Error(`stored: Katalog ${verweis.$catalog} fehlt im Vokabular`);
86
87
  raus = anPfad(raus, pfad.split('.'), (v) => (typeof v === 'string' && (0, exports.hat)(katalog, v) ? katalog[v] : v));
87
88
  }
88
89
  return raus;
89
90
  }
91
+ /** Zuordnung eines Wertverweises: benannter Katalog oder eingebettet. */
92
+ function katalogVon(verweis) {
93
+ const name = verweis.$catalog;
94
+ return typeof name === 'string' ? vokabular_js_1.KATALOGE[name] : verweis;
95
+ }
90
96
  /** Englische Werte eines Katalogs (was an dieser Stelle hinaus darf). */
91
- const aeussereWerte = (name) => new Set(Object.values(vokabular_js_1.KATALOGE[name] ?? {}));
97
+ const aeussereWerte = (verweis) => new Set(Object.values(katalogVon(verweis) ?? {}));
92
98
  // ---- Belege -----------------------------------------------------------------
93
99
  /**
94
100
  * Interne Marke der Storno-Reservierung: `stornoMarke` am Storno-Beleg,
@@ -143,6 +149,47 @@ function belegRueckgabeNachAussen(beleg) {
143
149
  }
144
150
  return raus;
145
151
  }
152
+ // ---- Gutschein-Eintraege am Beleg ---------------------------------------------
153
+ /** Zwilling von `BELEG_GUTSCHEIN` (api-vokabular-v3): aussen -> innen. */
154
+ const BELEG_GUTSCHEIN = {
155
+ voucherType: 'art', voucherId: 'gutscheinId', codeSuffix: 'codeEnde', amountCents: 'betragCents', vatRateBp: 'satzBp',
156
+ article: 'artikel', articleId: 'artikelId', quantity: 'stueck', positions: 'positionen',
157
+ balanceAfterCents: 'saldoNachCents', remainingQuantityAfter: 'stueckOffenNach', taxedWith: 'versteuertMit', exempt: 'befreit',
158
+ reason: 'grund', zeroRateBucket: 'imNullTopf',
159
+ };
160
+ const BELEG_GUTSCHEIN_AUSSEN = Object.fromEntries(Object.entries(BELEG_GUTSCHEIN).map(([aussen, innen]) => [innen, aussen]));
161
+ const GUTSCHEIN_ARTIKEL_AUSSEN = { satzBp: 'vatRateBp', preisCents: 'priceCents' };
162
+ function umbenannt(o, namen) {
163
+ return Object.fromEntries(Object.entries(o).map(([k, w]) => [(0, exports.hat)(namen, k) ? namen[k] : k, w]));
164
+ }
165
+ /** Zwilling von `gutscheinEintragNachAussen`: ohne deutschen Schluessel/Wert dasselbe Objekt (Altbeleg). */
166
+ function gutscheinEintragNachAussen(v) {
167
+ if (!(0, exports.istObjekt)(v))
168
+ return v;
169
+ const art = vokabular_js_1.KATALOGE.GUTSCHEIN_ART ?? {};
170
+ const typNeu = typeof v.type === 'string' && (0, exports.hat)(art, v.type);
171
+ if (!typNeu && !Object.keys(v).some((k) => (0, exports.hat)(BELEG_GUTSCHEIN_AUSSEN, k)))
172
+ return v;
173
+ const raus = {};
174
+ for (const [k, w] of Object.entries(v)) {
175
+ if (k === 'type')
176
+ raus.type = typNeu ? art[w] : w;
177
+ else if (k === 'art')
178
+ raus.voucherType = typeof w === 'string' && (0, exports.hat)(art, w) ? art[w] : w;
179
+ else if (k === 'artikel')
180
+ raus.article = (0, exports.istObjekt)(w) ? umbenannt(w, GUTSCHEIN_ARTIKEL_AUSSEN) : w;
181
+ else
182
+ raus[(0, exports.hat)(BELEG_GUTSCHEIN_AUSSEN, k) ? BELEG_GUTSCHEIN_AUSSEN[k] : k] = w;
183
+ }
184
+ return raus;
185
+ }
186
+ /** Zwilling von `belegGutscheineNachAussen`: `vouchers[]` eines Belegs an Ort und Stelle nach aussen. */
187
+ function belegGutscheineNachAussen(beleg) {
188
+ if (!Array.isArray(beleg.vouchers))
189
+ return beleg;
190
+ const neu = beleg.vouchers.map(gutscheinEintragNachAussen);
191
+ return neu.every((v, i) => v === beleg.vouchers[i]) ? beleg : { ...beleg, vouchers: neu };
192
+ }
146
193
  /**
147
194
  * Zwilling von `beleg-pruefangaben.alsIso`: leer (auch 0, '') -> null;
148
195
  * Firestore-Zeitstempel, Zahl oder Zeichenkette -> ISO-Zeitpunkt in UTC;
@@ -194,7 +241,7 @@ function pruefangabenDerHuelle(p) {
194
241
  function _receiptEnvelopeToWire(huelle) {
195
242
  const schema = vokabular_js_1.SCHEMAS.getReceipt;
196
243
  const draht = werteNachAussen(schluesselNachAussen(huelle, schema.data), schema.werte);
197
- return (0, exports.istObjekt)(draht.receipt) ? { ...draht, receipt: belegRueckgabeNachAussen(draht.receipt) } : draht;
244
+ return (0, exports.istObjekt)(draht.receipt) ? { ...draht, receipt: belegGutscheineNachAussen(belegRueckgabeNachAussen(draht.receipt)) } : draht;
198
245
  }
199
246
  /**
200
247
  * Ein Belegdokument (`users/{uid}/cashregisters/{id}/receipts/{id}`) in der
@@ -250,7 +297,7 @@ function _storedArticleToWire(id, doc, functionName = 'fromStoredArticle') {
250
297
  const schema = vokabular_js_1.SCHEMAS.listMyArticles;
251
298
  const huelle = werteNachAussen(schluesselNachAussen({ articles: [handler] }, schema.data), schema.werte);
252
299
  const artikel = huelle.articles[0];
253
- const katalog = (pfad) => aeussereWerte(schema.werte[pfad].$catalog);
300
+ const katalog = (pfad) => aeussereWerte(schema.werte[pfad]);
254
301
  const raus = nurBekannte(artikel, new Set(vokabular_js_1.ARTIKEL_FELDER), {
255
302
  quantityRule: katalog('articles[].quantityRule'), e1aGroup: katalog('articles[].e1aGroup'),
256
303
  });
@@ -4,6 +4,7 @@ exports._PRUEFER_FELDER = void 0;
4
4
  exports._storedPosSettingsToWire = _storedPosSettingsToWire;
5
5
  const settings_js_1 = require("../pos/settings.js");
6
6
  const draht_js_1 = require("./draht.js");
7
+ const draht_js_2 = require("./draht.js");
7
8
  const vokabular_js_1 = require("./vokabular.js");
8
9
  const bool = (v) => typeof v === 'boolean';
9
10
  const hex = (v) => typeof v === 'string' && /^#[0-9a-fA-F]{6}$/.test(v);
@@ -75,7 +76,7 @@ function teilSchema(teil) {
75
76
  }
76
77
  function katalogFuer(teil, aussen) {
77
78
  const verweis = vokabular_js_1.SCHEMAS.getKasseSettings.werte[`${teil}.${aussen}`];
78
- return verweis ? vokabular_js_1.KATALOGE[verweis.$catalog] ?? null : null;
79
+ return verweis ? (0, draht_js_2.katalogVon)(verweis) ?? null : null;
79
80
  }
80
81
  /** Tasten: aeussere und innere Aktionsnamen, Vorgabe und Paare in innerer Form. */
81
82
  const TASTEN_SCHEMA = (0, draht_js_1.regel)(teilSchema('device').shortcuts).unter;
@@ -0,0 +1,3 @@
1
+ import type { Receipt } from '../models/receipt.js';
2
+ export declare function gutscheineFuerPaket(receipt: Receipt): Receipt;
3
+ export declare function wieVendortesPaket(receipt: Receipt): Receipt;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.gutscheineFuerPaket = gutscheineFuerPaket;
4
+ exports.wieVendortesPaket = wieVendortesPaket;
5
+ /*
6
+ * Zwilling von `gutscheineFuerPaket` (functions/beleg-layout.js): der Bon
7
+ * kennt Gutscheine mit Code nur als Positionen. Eintraege, deren Betrag schon
8
+ * als Position am Beleg steht (Einzweck, Leistung, Mehrzweck im 0-%-Topf),
9
+ * fallen fuer das Zeilenmodell weg, sonst zaehlte es sie ein zweites Mal; die
10
+ * maskierte Code-Endung wandert an die passende Position. Nur Darstellung,
11
+ * der Beleg selbst bleibt unberuehrt.
12
+ */
13
+ const POSITIONS_ARTEN = ['single_purpose', 'service'];
14
+ const centsDerPosition = (it) => Math.round(Math.abs(it.priceCents * it.quantity));
15
+ const centsDesEintrags = (v) => Number.isInteger(v.amountCents) ? Math.abs(v.amountCents) : Math.abs(v.valueCents ?? 0);
16
+ function gutscheineFuerPaket(receipt) {
17
+ const positionen = receipt.items.map((it) => ({ ...it }));
18
+ const vergeben = new Set();
19
+ const codeAnPosition = (v, kinds) => {
20
+ const cents = centsDesEintrags(v);
21
+ const i = positionen.findIndex((it, k) => !vergeben.has(k) && it.kind != null && kinds.includes(it.kind)
22
+ && centsDerPosition(it) === cents && !String(it.name || '').includes('····'));
23
+ if (i < 0)
24
+ return;
25
+ vergeben.add(i);
26
+ if (typeof v.code === 'string' && v.code)
27
+ positionen[i].name = `${positionen[i].name} ${v.code}`;
28
+ };
29
+ const bleiben = [];
30
+ for (const v of receipt.vouchers) {
31
+ if (POSITIONS_ARTEN.includes(v.type)) {
32
+ if (v.action === 'sell')
33
+ codeAnPosition(v, ['voucher']);
34
+ continue;
35
+ }
36
+ if (v.zeroRateBucket === true) {
37
+ codeAnPosition(v, ['voucher_float']);
38
+ continue;
39
+ }
40
+ bleiben.push(v);
41
+ }
42
+ return { ...receipt, items: positionen, vouchers: bleiben };
43
+ }
44
+ /*
45
+ * Das Backend setzt den Bon noch mit dem vendorten Paket 0.33 (functions/
46
+ * package.json). Es liest an einem Gutschein nur name, code, action, type und
47
+ * den Wert; die Kennzeichen der neuen Form (`voucherType`, `codeSuffix`,
48
+ * `balanceAfterCents` ...) sieht es nicht. Was nach `gutscheineFuerPaket`
49
+ * bleibt (Mehrzweck, Aktion), zeigt es darum wie einen Altbeleg
50
+ * („Wertgutschein ····-····-5KNZ - Gutschein“). Dieses Paket zeigt solche
51
+ * Eintraege selbst in der Form der Spec („Gutschein ····-5KNZ“ + Rest); fuer
52
+ * das Layout wie der Server fallen die Kennzeichen hier weg. Entfaellt, sobald
53
+ * der Server ein Paket mit der neuen Form vendort und nicht mehr aufbereitet.
54
+ */
55
+ function wieVendortesPaket(receipt) {
56
+ const vouchers = receipt.vouchers.map((v) => ({
57
+ name: v.name, code: v.code, action: v.action, type: v.type, valueCents: v.valueCents,
58
+ }));
59
+ return { ...receipt, vouchers };
60
+ }
@@ -29,6 +29,7 @@ const settings_js_1 = require("../pos/settings.js");
29
29
  const artikel_js_1 = require("../pos/artikel.js");
30
30
  const errors_js_1 = require("../client/errors.js");
31
31
  const draht_js_1 = require("./draht.js");
32
+ const bon_layout_js_1 = require("./bon-layout.js");
32
33
  const einstellungen_js_1 = require("./einstellungen.js");
33
34
  const istObjekt = (w) => w !== null && typeof w === 'object' && !Array.isArray(w);
34
35
  // ---- Belege -----------------------------------------------------------------
@@ -87,7 +88,7 @@ function fromStoredReceiptWithCompany(doc, options) {
87
88
  const draht = (0, draht_js_1._receiptEnvelopeToWire)(huelle);
88
89
  draht.receipt = zeilenAlsText(draht.receipt);
89
90
  const raus = (0, receipts_js_1.belegMitFirmaAusHuelle)(draht, name);
90
- raus.layout = layoutWieServer(raus, beleg.layoutRegeln);
91
+ raus.layout = (0, bon_layout_js_1.layoutWieServer)(raus, beleg.layoutRegeln);
91
92
  return raus;
92
93
  }
93
94
  /**
@@ -102,22 +103,6 @@ function pruefangabenFuer(beleg, angabe) {
102
103
  return (0, draht_js_1.pruefangabenDerHuelle)(beleg.pruefangaben);
103
104
  return { karteRegistriertAm: angabe?.cardRegisteredAt ?? null, kasseRegistriertAm: angabe?.cashregisterRegisteredAt ?? null };
104
105
  }
105
- /**
106
- * Layout wie `beleg-layout.layoutFuerBeleg`: 80 mm, Regelwerk mindestens das
107
- * aktuelle. Kann der Server es nicht bauen, liefert er `null` (dann baut
108
- * [receiptLayoutFromResult] selbst); ebenso hier.
109
- */
110
- function layoutWieServer(b, regelwerk) {
111
- const ruleset = Math.max(Number(regelwerk) || 1, layout_js_1.CURRENT_LAYOUT_RULESET);
112
- try {
113
- return (0, layout_js_1.buildReceiptLayout)(b.receipt, b.company, {
114
- paperSize: 'mm80', ruleset, testCashregister: b.testCashregister, testSignature: b.testSignature, registrationInfo: b.registrationInfo,
115
- });
116
- }
117
- catch {
118
- return null;
119
- }
120
- }
121
106
  // ---- Firma ------------------------------------------------------------------
122
107
  const KOPF_FELDER = ['companyName', 'street', 'zip', 'city', 'phone', 'uid', 'taxnr', 'isSmallBusiness',
123
108
  'footer1', 'footer2', 'footer3', 'footer4', 'thanksMessage', 'showKreiseckLogo', 'logoUrl'];
@@ -1,16 +1,18 @@
1
1
  /** Fingerabdruck des Vertrags-Exports, aus dem diese Datei stammt (`_quelle.sha256`). */
2
- export declare const VOKABULAR_QUELLE = "e061453f62c8f4af21528eb51a677d6f947553f5b78621d64497f45833dd8b09";
2
+ export declare const VOKABULAR_QUELLE = "733e4385baf4b2aa50605e2ebe5903948ce77b3346aa3bb65c5fd0d450a6898f";
3
3
  /** Schema-Notation des Vertrags: Blatt 'aussen': 'innen', Objekt { __: 'innen', ... }, Liste [ { __: 'innen', ... } ]. */
4
4
  export type SchemaEintrag = string | SchemaObjekt | readonly [SchemaObjekt];
5
5
  export interface SchemaObjekt {
6
6
  readonly [aussen: string]: SchemaEintrag;
7
7
  }
8
+ /** Wertverweis: Name eines Katalogs oder eine eingebettete Zuordnung innen -> aussen. */
9
+ export type Wertverweis = {
10
+ readonly $catalog: string;
11
+ } | Readonly<Record<string, string>>;
8
12
  /** Schema je Endpunkt (aussen -> innen) und Wertverweise auf die Kataloge. */
9
13
  export declare const SCHEMAS: Readonly<Record<'getReceipt' | 'getKasseSettings' | 'listMyArticles', {
10
14
  readonly data: SchemaObjekt;
11
- readonly werte: Readonly<Record<string, {
12
- readonly $catalog: string;
13
- }>>;
15
+ readonly werte: Readonly<Record<string, Wertverweis>>;
14
16
  }>>;
15
17
  /** Die Felder eines Artikels in der Antwort (`listMyArticles`), aussen. */
16
18
  export declare const ARTIKEL_FELDER: readonly string[];