@kreiseck/kasseneck-api 0.6.7

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 (294) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +2 -0
  3. package/README.md +206 -0
  4. package/dist/cjs/client/api.d.ts +75 -0
  5. package/dist/cjs/client/api.js +40 -0
  6. package/dist/cjs/client/auth.d.ts +52 -0
  7. package/dist/cjs/client/auth.js +72 -0
  8. package/dist/cjs/client/cashregisters.d.ts +21 -0
  9. package/dist/cjs/client/cashregisters.js +36 -0
  10. package/dist/cjs/client/errors.d.ts +159 -0
  11. package/dist/cjs/client/errors.js +226 -0
  12. package/dist/cjs/client/index.d.ts +8 -0
  13. package/dist/cjs/client/index.js +44 -0
  14. package/dist/cjs/client/receipts.d.ts +245 -0
  15. package/dist/cjs/client/receipts.js +452 -0
  16. package/dist/cjs/client/reports.d.ts +43 -0
  17. package/dist/cjs/client/reports.js +73 -0
  18. package/dist/cjs/client/status.d.ts +43 -0
  19. package/dist/cjs/client/status.js +47 -0
  20. package/dist/cjs/client/transport.d.ts +117 -0
  21. package/dist/cjs/client/transport.js +312 -0
  22. package/dist/cjs/enums/credit-card-provider.d.ts +22 -0
  23. package/dist/cjs/enums/credit-card-provider.js +23 -0
  24. package/dist/cjs/enums/define-enum.d.ts +15 -0
  25. package/dist/cjs/enums/define-enum.js +16 -0
  26. package/dist/cjs/enums/index.d.ts +7 -0
  27. package/dist/cjs/enums/index.js +17 -0
  28. package/dist/cjs/enums/keck-payment-method.d.ts +57 -0
  29. package/dist/cjs/enums/keck-payment-method.js +23 -0
  30. package/dist/cjs/enums/receipt-type.d.ts +41 -0
  31. package/dist/cjs/enums/receipt-type.js +18 -0
  32. package/dist/cjs/enums/stripe-link-mode.d.ts +17 -0
  33. package/dist/cjs/enums/stripe-link-mode.js +18 -0
  34. package/dist/cjs/enums/vat-rate.d.ts +40 -0
  35. package/dist/cjs/enums/vat-rate.js +19 -0
  36. package/dist/cjs/enums/voucher-action.d.ts +10 -0
  37. package/dist/cjs/enums/voucher-action.js +11 -0
  38. package/dist/cjs/enums/voucher-type.d.ts +9 -0
  39. package/dist/cjs/enums/voucher-type.js +10 -0
  40. package/dist/cjs/index.d.ts +5 -0
  41. package/dist/cjs/index.js +97 -0
  42. package/dist/cjs/kasse/artikel.d.ts +83 -0
  43. package/dist/cjs/kasse/artikel.js +79 -0
  44. package/dist/cjs/kasse/client.d.ts +10 -0
  45. package/dist/cjs/kasse/client.js +38 -0
  46. package/dist/cjs/kasse/drucker.d.ts +54 -0
  47. package/dist/cjs/kasse/drucker.js +41 -0
  48. package/dist/cjs/kasse/index.d.ts +11 -0
  49. package/dist/cjs/kasse/index.js +38 -0
  50. package/dist/cjs/kasse/settings.d.ts +115 -0
  51. package/dist/cjs/kasse/settings.js +62 -0
  52. package/dist/cjs/models/cancellation.d.ts +51 -0
  53. package/dist/cjs/models/cancellation.js +45 -0
  54. package/dist/cjs/models/cashregister.d.ts +77 -0
  55. package/dist/cjs/models/cashregister.js +53 -0
  56. package/dist/cjs/models/enum-payload.d.ts +26 -0
  57. package/dist/cjs/models/enum-payload.js +50 -0
  58. package/dist/cjs/models/hobex-receipt.d.ts +63 -0
  59. package/dist/cjs/models/hobex-receipt.js +86 -0
  60. package/dist/cjs/models/index.d.ts +10 -0
  61. package/dist/cjs/models/index.js +46 -0
  62. package/dist/cjs/models/receipt-company.d.ts +71 -0
  63. package/dist/cjs/models/receipt-company.js +52 -0
  64. package/dist/cjs/models/receipt-item.d.ts +100 -0
  65. package/dist/cjs/models/receipt-item.js +108 -0
  66. package/dist/cjs/models/receipt-summary.d.ts +92 -0
  67. package/dist/cjs/models/receipt-summary.js +54 -0
  68. package/dist/cjs/models/receipt.d.ts +108 -0
  69. package/dist/cjs/models/receipt.js +119 -0
  70. package/dist/cjs/models/report-month.d.ts +32 -0
  71. package/dist/cjs/models/report-month.js +65 -0
  72. package/dist/cjs/models/stripe-url-session.d.ts +18 -0
  73. package/dist/cjs/models/stripe-url-session.js +20 -0
  74. package/dist/cjs/models/voucher.d.ts +41 -0
  75. package/dist/cjs/models/voucher.js +56 -0
  76. package/dist/cjs/money.d.ts +23 -0
  77. package/dist/cjs/money.js +35 -0
  78. package/dist/cjs/package.json +1 -0
  79. package/dist/cjs/payments/hobex.d.ts +87 -0
  80. package/dist/cjs/payments/hobex.js +167 -0
  81. package/dist/cjs/payments/index.d.ts +33 -0
  82. package/dist/cjs/payments/index.js +42 -0
  83. package/dist/cjs/payments/stripe.d.ts +61 -0
  84. package/dist/cjs/payments/stripe.js +157 -0
  85. package/dist/cjs/printing/escpos.d.ts +219 -0
  86. package/dist/cjs/printing/escpos.js +636 -0
  87. package/dist/cjs/printing/index.d.ts +3 -0
  88. package/dist/cjs/printing/index.js +33 -0
  89. package/dist/cjs/printing/printable.d.ts +27 -0
  90. package/dist/cjs/printing/printable.js +90 -0
  91. package/dist/cjs/printing/webusb.d.ts +89 -0
  92. package/dist/cjs/printing/webusb.js +110 -0
  93. package/dist/cjs/react/index.d.ts +46 -0
  94. package/dist/cjs/react/index.js +51 -0
  95. package/dist/cjs/receipt/discount.d.ts +12 -0
  96. package/dist/cjs/receipt/discount.js +54 -0
  97. package/dist/cjs/receipt/epos.d.ts +53 -0
  98. package/dist/cjs/receipt/epos.js +95 -0
  99. package/dist/cjs/receipt/grid.d.ts +47 -0
  100. package/dist/cjs/receipt/grid.js +122 -0
  101. package/dist/cjs/receipt/index.d.ts +4 -0
  102. package/dist/cjs/receipt/index.js +29 -0
  103. package/dist/cjs/receipt/layout-escpos.d.ts +35 -0
  104. package/dist/cjs/receipt/layout-escpos.js +67 -0
  105. package/dist/cjs/receipt/layout.d.ts +180 -0
  106. package/dist/cjs/receipt/layout.js +594 -0
  107. package/dist/cjs/register/index.d.ts +51 -0
  108. package/dist/cjs/register/index.js +61 -0
  109. package/dist/cjs/register/pairing.d.ts +248 -0
  110. package/dist/cjs/register/pairing.js +279 -0
  111. package/dist/cjs/register/session.d.ts +37 -0
  112. package/dist/cjs/register/session.js +52 -0
  113. package/dist/cjs/vienna-time.d.ts +23 -0
  114. package/dist/cjs/vienna-time.js +114 -0
  115. package/dist/esm/client/api.d.ts +75 -0
  116. package/dist/esm/client/api.js +37 -0
  117. package/dist/esm/client/auth.d.ts +52 -0
  118. package/dist/esm/client/auth.js +68 -0
  119. package/dist/esm/client/cashregisters.d.ts +21 -0
  120. package/dist/esm/client/cashregisters.js +33 -0
  121. package/dist/esm/client/errors.d.ts +159 -0
  122. package/dist/esm/client/errors.js +212 -0
  123. package/dist/esm/client/index.d.ts +8 -0
  124. package/dist/esm/client/index.js +8 -0
  125. package/dist/esm/client/receipts.d.ts +245 -0
  126. package/dist/esm/client/receipts.js +438 -0
  127. package/dist/esm/client/reports.d.ts +43 -0
  128. package/dist/esm/client/reports.js +69 -0
  129. package/dist/esm/client/status.d.ts +43 -0
  130. package/dist/esm/client/status.js +43 -0
  131. package/dist/esm/client/transport.d.ts +117 -0
  132. package/dist/esm/client/transport.js +307 -0
  133. package/dist/esm/enums/credit-card-provider.d.ts +22 -0
  134. package/dist/esm/enums/credit-card-provider.js +20 -0
  135. package/dist/esm/enums/define-enum.d.ts +15 -0
  136. package/dist/esm/enums/define-enum.js +13 -0
  137. package/dist/esm/enums/index.d.ts +7 -0
  138. package/dist/esm/enums/index.js +7 -0
  139. package/dist/esm/enums/keck-payment-method.d.ts +57 -0
  140. package/dist/esm/enums/keck-payment-method.js +20 -0
  141. package/dist/esm/enums/receipt-type.d.ts +41 -0
  142. package/dist/esm/enums/receipt-type.js +15 -0
  143. package/dist/esm/enums/stripe-link-mode.d.ts +17 -0
  144. package/dist/esm/enums/stripe-link-mode.js +15 -0
  145. package/dist/esm/enums/vat-rate.d.ts +40 -0
  146. package/dist/esm/enums/vat-rate.js +16 -0
  147. package/dist/esm/enums/voucher-action.d.ts +10 -0
  148. package/dist/esm/enums/voucher-action.js +8 -0
  149. package/dist/esm/enums/voucher-type.d.ts +9 -0
  150. package/dist/esm/enums/voucher-type.js +7 -0
  151. package/dist/esm/index.d.ts +5 -0
  152. package/dist/esm/index.js +5 -0
  153. package/dist/esm/kasse/artikel.d.ts +83 -0
  154. package/dist/esm/kasse/artikel.js +70 -0
  155. package/dist/esm/kasse/client.d.ts +10 -0
  156. package/dist/esm/kasse/client.js +33 -0
  157. package/dist/esm/kasse/drucker.d.ts +54 -0
  158. package/dist/esm/kasse/drucker.js +36 -0
  159. package/dist/esm/kasse/index.d.ts +11 -0
  160. package/dist/esm/kasse/index.js +13 -0
  161. package/dist/esm/kasse/settings.d.ts +115 -0
  162. package/dist/esm/kasse/settings.js +58 -0
  163. package/dist/esm/models/cancellation.d.ts +51 -0
  164. package/dist/esm/models/cancellation.js +40 -0
  165. package/dist/esm/models/cashregister.d.ts +77 -0
  166. package/dist/esm/models/cashregister.js +50 -0
  167. package/dist/esm/models/enum-payload.d.ts +26 -0
  168. package/dist/esm/models/enum-payload.js +44 -0
  169. package/dist/esm/models/hobex-receipt.d.ts +63 -0
  170. package/dist/esm/models/hobex-receipt.js +80 -0
  171. package/dist/esm/models/index.d.ts +10 -0
  172. package/dist/esm/models/index.js +10 -0
  173. package/dist/esm/models/receipt-company.d.ts +71 -0
  174. package/dist/esm/models/receipt-company.js +48 -0
  175. package/dist/esm/models/receipt-item.d.ts +100 -0
  176. package/dist/esm/models/receipt-item.js +101 -0
  177. package/dist/esm/models/receipt-summary.d.ts +92 -0
  178. package/dist/esm/models/receipt-summary.js +49 -0
  179. package/dist/esm/models/receipt.d.ts +108 -0
  180. package/dist/esm/models/receipt.js +113 -0
  181. package/dist/esm/models/report-month.d.ts +32 -0
  182. package/dist/esm/models/report-month.js +58 -0
  183. package/dist/esm/models/stripe-url-session.d.ts +18 -0
  184. package/dist/esm/models/stripe-url-session.js +16 -0
  185. package/dist/esm/models/voucher.d.ts +41 -0
  186. package/dist/esm/models/voucher.js +51 -0
  187. package/dist/esm/money.d.ts +23 -0
  188. package/dist/esm/money.js +31 -0
  189. package/dist/esm/payments/hobex.d.ts +87 -0
  190. package/dist/esm/payments/hobex.js +162 -0
  191. package/dist/esm/payments/index.d.ts +33 -0
  192. package/dist/esm/payments/index.js +33 -0
  193. package/dist/esm/payments/stripe.d.ts +61 -0
  194. package/dist/esm/payments/stripe.js +153 -0
  195. package/dist/esm/printing/escpos.d.ts +219 -0
  196. package/dist/esm/printing/escpos.js +616 -0
  197. package/dist/esm/printing/index.d.ts +3 -0
  198. package/dist/esm/printing/index.js +3 -0
  199. package/dist/esm/printing/printable.d.ts +27 -0
  200. package/dist/esm/printing/printable.js +87 -0
  201. package/dist/esm/printing/webusb.d.ts +89 -0
  202. package/dist/esm/printing/webusb.js +101 -0
  203. package/dist/esm/react/index.d.ts +46 -0
  204. package/dist/esm/react/index.js +47 -0
  205. package/dist/esm/receipt/discount.d.ts +12 -0
  206. package/dist/esm/receipt/discount.js +51 -0
  207. package/dist/esm/receipt/epos.d.ts +53 -0
  208. package/dist/esm/receipt/epos.js +86 -0
  209. package/dist/esm/receipt/grid.d.ts +47 -0
  210. package/dist/esm/receipt/grid.js +116 -0
  211. package/dist/esm/receipt/index.d.ts +4 -0
  212. package/dist/esm/receipt/index.js +4 -0
  213. package/dist/esm/receipt/layout-escpos.d.ts +35 -0
  214. package/dist/esm/receipt/layout-escpos.js +64 -0
  215. package/dist/esm/receipt/layout.d.ts +180 -0
  216. package/dist/esm/receipt/layout.js +583 -0
  217. package/dist/esm/register/index.d.ts +51 -0
  218. package/dist/esm/register/index.js +51 -0
  219. package/dist/esm/register/pairing.d.ts +248 -0
  220. package/dist/esm/register/pairing.js +270 -0
  221. package/dist/esm/register/session.d.ts +37 -0
  222. package/dist/esm/register/session.js +48 -0
  223. package/dist/esm/vienna-time.d.ts +23 -0
  224. package/dist/esm/vienna-time.js +110 -0
  225. package/fixtures/belege/langer-artikelname.json +43 -0
  226. package/fixtures/belege/null-ausfall.json +41 -0
  227. package/fixtures/belege/null-jahr.json +41 -0
  228. package/fixtures/belege/null-monat.json +41 -0
  229. package/fixtures/belege/null-pruef.json +41 -0
  230. package/fixtures/belege/null-schluss.json +41 -0
  231. package/fixtures/belege/null-start.json +40 -0
  232. package/fixtures/belege/rabattzeilen.json +61 -0
  233. package/fixtures/belege/signaturausfall-verkauf.json +49 -0
  234. package/fixtures/belege/storno-teil.json +48 -0
  235. package/fixtures/belege/storno-voll.json +54 -0
  236. package/fixtures/belege/testkasse-verkauf.json +50 -0
  237. package/fixtures/belege/testsignatur-verkauf.json +50 -0
  238. package/fixtures/belege/training.json +49 -0
  239. package/fixtures/belege/verkauf-bar.json +49 -0
  240. package/fixtures/belege/verkauf-karte.json +49 -0
  241. package/fixtures/belege/verkauf-kleinunternehmer.json +42 -0
  242. package/fixtures/erwartet/langer-artikelname.grid32.txt +25 -0
  243. package/fixtures/erwartet/langer-artikelname.grid48.txt +24 -0
  244. package/fixtures/erwartet/langer-artikelname.lines.json +216 -0
  245. package/fixtures/erwartet/null-ausfall.grid32.txt +28 -0
  246. package/fixtures/erwartet/null-ausfall.grid48.txt +25 -0
  247. package/fixtures/erwartet/null-ausfall.lines.json +220 -0
  248. package/fixtures/erwartet/null-jahr.grid32.txt +29 -0
  249. package/fixtures/erwartet/null-jahr.grid48.txt +25 -0
  250. package/fixtures/erwartet/null-jahr.lines.json +220 -0
  251. package/fixtures/erwartet/null-monat.grid32.txt +28 -0
  252. package/fixtures/erwartet/null-monat.grid48.txt +25 -0
  253. package/fixtures/erwartet/null-monat.lines.json +220 -0
  254. package/fixtures/erwartet/null-pruef.grid32.txt +28 -0
  255. package/fixtures/erwartet/null-pruef.grid48.txt +25 -0
  256. package/fixtures/erwartet/null-pruef.lines.json +220 -0
  257. package/fixtures/erwartet/null-schluss.grid32.txt +28 -0
  258. package/fixtures/erwartet/null-schluss.grid48.txt +25 -0
  259. package/fixtures/erwartet/null-schluss.lines.json +220 -0
  260. package/fixtures/erwartet/null-start.grid32.txt +28 -0
  261. package/fixtures/erwartet/null-start.grid48.txt +25 -0
  262. package/fixtures/erwartet/null-start.lines.json +220 -0
  263. package/fixtures/erwartet/rabattzeilen.grid32.txt +27 -0
  264. package/fixtures/erwartet/rabattzeilen.grid48.txt +26 -0
  265. package/fixtures/erwartet/rabattzeilen.lines.json +286 -0
  266. package/fixtures/erwartet/signaturausfall-verkauf.grid32.txt +28 -0
  267. package/fixtures/erwartet/signaturausfall-verkauf.grid48.txt +26 -0
  268. package/fixtures/erwartet/signaturausfall-verkauf.lines.json +266 -0
  269. package/fixtures/erwartet/storno-teil.grid32.txt +27 -0
  270. package/fixtures/erwartet/storno-teil.grid48.txt +26 -0
  271. package/fixtures/erwartet/storno-teil.lines.json +237 -0
  272. package/fixtures/erwartet/storno-voll.grid32.txt +29 -0
  273. package/fixtures/erwartet/storno-voll.grid48.txt +28 -0
  274. package/fixtures/erwartet/storno-voll.lines.json +277 -0
  275. package/fixtures/erwartet/testkasse-verkauf.grid32.txt +28 -0
  276. package/fixtures/erwartet/testkasse-verkauf.grid48.txt +27 -0
  277. package/fixtures/erwartet/testkasse-verkauf.lines.json +270 -0
  278. package/fixtures/erwartet/testsignatur-verkauf.grid32.txt +30 -0
  279. package/fixtures/erwartet/testsignatur-verkauf.grid48.txt +27 -0
  280. package/fixtures/erwartet/testsignatur-verkauf.lines.json +270 -0
  281. package/fixtures/erwartet/training.grid32.txt +33 -0
  282. package/fixtures/erwartet/training.grid48.txt +30 -0
  283. package/fixtures/erwartet/training.lines.json +277 -0
  284. package/fixtures/erwartet/verkauf-bar.grid32.txt +25 -0
  285. package/fixtures/erwartet/verkauf-bar.grid48.txt +24 -0
  286. package/fixtures/erwartet/verkauf-bar.lines.json +256 -0
  287. package/fixtures/erwartet/verkauf-karte.grid32.txt +25 -0
  288. package/fixtures/erwartet/verkauf-karte.grid48.txt +24 -0
  289. package/fixtures/erwartet/verkauf-karte.lines.json +256 -0
  290. package/fixtures/erwartet/verkauf-kleinunternehmer.grid32.txt +26 -0
  291. package/fixtures/erwartet/verkauf-kleinunternehmer.grid48.txt +25 -0
  292. package/fixtures/erwartet/verkauf-kleinunternehmer.lines.json +226 -0
  293. package/fixtures/manifest.json +107 -0
  294. package/package.json +135 -0
@@ -0,0 +1,594 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SMALL_BUSINESS_NOTICE = exports.AKTUELLES_REGELWERK = void 0;
4
+ exports.formatCents = formatCents;
5
+ exports.receiptSignatureFailed = receiptSignatureFailed;
6
+ exports.receiptSignatureIsTest = receiptSignatureIsTest;
7
+ exports.receiptIsZero = receiptIsZero;
8
+ exports.receiptAmountsAreZero = receiptAmountsAreZero;
9
+ exports.receiptZdaText = receiptZdaText;
10
+ exports.receiptIsSmallBusinessConsistent = receiptIsSmallBusinessConsistent;
11
+ exports.buildReceiptLayout = buildReceiptLayout;
12
+ const index_js_1 = require("../enums/index.js");
13
+ const index_js_2 = require("../models/index.js");
14
+ const vienna_time_js_1 = require("../vienna-time.js");
15
+ const errors_js_1 = require("../client/errors.js");
16
+ exports.AKTUELLES_REGELWERK = 2;
17
+ // -------------------------------------------------------------- Formatierung
18
+ /**
19
+ * Cent-Betrag als Anzeigetext: `1234` -> `"12,34"`. Die **einzige** Stelle, an
20
+ * der aus Cent eine Zeichenkette wird. Erwartet ganze Cent (Geld wird in
21
+ * diesem Paket ausnahmslos ganzzahlig gerechnet).
22
+ */
23
+ function formatCents(cents) {
24
+ return (cents / 100).toFixed(2).replace('.', ',');
25
+ }
26
+ /**
27
+ * Belegzeit als `DD.MM.YYYY HH:mm:ss` in **Wiener** Zeit — Zwilling von
28
+ * `KasseneckReceipt.readableTime`. Der Server liefert seine Zeitstempel
29
+ * uneinheitlich; gedeutet wird deshalb ueber `parseServerTimeStamp` und nie
30
+ * ueber `new Date(text)` (siehe ../vienna-time.ts).
31
+ *
32
+ * Ist der Zeitstempel unlesbar, wirft die Deutung ein gewoehnliches `Error`.
33
+ * Das ist ein Antwortproblem und gehoert in die Fehler-Union dieses Pakets —
34
+ * sonst faellt ein Aufrufer, der nach den Waechtern verzweigt, aus allen
35
+ * heraus (dieselbe Umwandlung wie in client/receipts.ts bei
36
+ * getFirstReceiptDate).
37
+ */
38
+ function belegZeit(timeStamp) {
39
+ let t;
40
+ try {
41
+ t = (0, vienna_time_js_1.toViennaWallClock)((0, vienna_time_js_1.parseServerTimeStamp)(timeStamp));
42
+ }
43
+ catch {
44
+ // Der Zeitstempel selbst wandert NICHT in die Meldung: er kommt aus einer
45
+ // fremden Antwort, und was dort steht, ist nicht unsere Zusage.
46
+ throw new errors_js_1.KasseneckValidationError('buildReceiptLayout', 'Beleg enthaelt keinen lesbaren Zeitstempel', 'response');
47
+ }
48
+ const zwei = (n) => String(n).padStart(2, '0');
49
+ return `${zwei(t.day)}.${zwei(t.month)}.${t.year} ${zwei(t.hour)}:${zwei(t.minute)}:${zwei(t.second)}`;
50
+ }
51
+ /**
52
+ * Zahlungsart als Anzeigetext. Drei Faelle, und alle drei kommen vor:
53
+ * bekannter Eintrag (Beschriftung), unbekannter Schluessel (roh — besser als
54
+ * ein Beleg, der sich nicht anzeigen laesst), und **gar keiner**: Start- und
55
+ * Nullbelege tragen keine Zahlungsart, das Backend sendet dann `null`. Da
56
+ * `typeof null === 'object'` ist, ergab das beim Lesen von `.label` einen
57
+ * nackten TypeError.
58
+ */
59
+ function zahlungsartText(wert) {
60
+ if (wert != null && typeof wert === 'object') {
61
+ return wert.label;
62
+ }
63
+ return typeof wert === 'string' ? wert : '';
64
+ }
65
+ // -------------------------------------------------------- Kleinunternehmer
66
+ /**
67
+ * Hinweis auf die Steuerbefreiung, den ein Kleinunternehmerbeleg tragen muss —
68
+ * ohne ihn stuende in der USt-Tabelle „D 0 %" ohne jede Begruendung, und ab
69
+ * 400 € greift § 11 UStG voll.
70
+ *
71
+ * Wortlaut **woertlich** aus dem Backend (`INVOICE_TAX_NOTE.smallBusiness` in
72
+ * functions/index.js), damit auf Beleg und Rechnung derselbe Satz steht. Er
73
+ * traegt einen Gedankenstrich (U+2013), kein Bindestrich; der Bondrucker
74
+ * bekommt ihn ueber die Ersetzung der ESC/POS-Bruecke.
75
+ *
76
+ * Weder `print_paper.dart` noch das Beleg-PDF des Backends drucken den Hinweis
77
+ * heute — hier liegt das Kennzeichen vor, also steht er hier.
78
+ *
79
+ * Der **Name** ist englisch wie die uebrige Oberflaeche (`DEFAULT_BASE_URL`,
80
+ * `formatCents`, `buildReceiptLayout`); der **Wortlaut** ist selbstverstaendlich
81
+ * deutsch — er steht so auf oesterreichischen Belegen.
82
+ */
83
+ exports.SMALL_BUSINESS_NOTICE = 'Umsatzsteuerbefreit – Kleinunternehmer gemäß § 6 Abs. 1 Z 27 UStG.';
84
+ // ------------------------------------------------------------------ Signatur
85
+ /** Text, den das Backend statt einer Signatur ablegt, wenn die Karte ausfaellt. */
86
+ const SIGNATUR_AUSGEFALLEN = 'Sicherheitseinrichtung ausgefallen';
87
+ /**
88
+ * Derselbe Text base64url-kodiert — so steht er als dritter JWS-Teil im Feld
89
+ * `sig` (Zwilling von `RKSVService.isSigSuccess`). Fest hinterlegt statt zur
90
+ * Laufzeit kodiert: `btoa`/`Buffer` sind in Browser und Node verschieden zu
91
+ * haben, und der Wert ist unveraenderlich.
92
+ */
93
+ const SIGNATUR_AUSGEFALLEN_BASE64URL = 'U2ljaGVyaGVpdHNlaW5yaWNodHVuZyBhdXNnZWZhbGxlbg';
94
+ /** Wurde der Beleg ohne funktionierende Signatureinheit ausgestellt? */
95
+ function receiptSignatureFailed(receipt) {
96
+ return receipt.sig.split('.')[2] === SIGNATUR_AUSGEFALLEN_BASE64URL;
97
+ }
98
+ /**
99
+ * Traegt der Beleg eine **Test**-Signatur? Erkannt am ZDA-Kennzeichen `AT100`
100
+ * im maschinenlesbaren Code (`_R1-AT100_...`) — der Testpfad der Testumgebungen.
101
+ * Ob das ein Problem ist, weiss nur der Aufrufer (Testkonto: normal;
102
+ * Produktionskonto: „TESTSIGNATUR“-Warnung).
103
+ */
104
+ function receiptSignatureIsTest(receipt) {
105
+ return /^_R1-AT100_/.test(receipt.qr ?? '');
106
+ }
107
+ /**
108
+ * Steuersatz einer Position. Ein roher Zahlenwert bedeutet: die Nutzlast
109
+ * fuehrt einen Satz, den dieses Paket noch nicht kennt (siehe
110
+ * models/receipt-item.ts). Der Beleg ist dann bereits ausgestellt und
111
+ * signiert — er muss sich anzeigen lassen, also erhaelt der Satz seine Zahl
112
+ * und einen Platzhalter als Kategorie.
113
+ */
114
+ function steuersatz(vat) {
115
+ if (typeof vat === 'number') {
116
+ return { rate: vat, category: '?' };
117
+ }
118
+ return { rate: vat.rate, category: vat.category };
119
+ }
120
+ /** `20` -> `"20"`, `4.9` -> `"4,9"` (wie im Vorbild). */
121
+ function satzText(rate) {
122
+ return String(rate).replace('.', ',');
123
+ }
124
+ /**
125
+ * Netto in ganzen Cent aus einem Brutto-Betrag.
126
+ *
127
+ * Gerundet wird **von der Null weg** — nicht mit `Math.round`, das `.5` immer
128
+ * Richtung +unendlich schiebt. Sonst spiegelt ein Stornobeleg seinen
129
+ * Originalbeleg nicht: 99 Cent zu 20 % liegen mit 82,5 Cent Netto genau auf der
130
+ * Rundungsgrenze, der Beleg zeigte „0,83 / 0,16", der Storno aber
131
+ * „-0,82 / -0,17" — Beleg und Storno heben sich in der Netto- und
132
+ * MwSt-Spalte nicht auf. Bei 20 % trifft das jeden sechsten Cent-Betrag.
133
+ * (Dieselbe Regel gilt in printing/escpos.ts und in Darts `toStringAsFixed`.)
134
+ */
135
+ function nettoAusBrutto(bruttoCents, rate) {
136
+ const betrag = Math.round((Math.abs(bruttoCents) * 100) / (100 + rate));
137
+ return bruttoCents < 0 ? -betrag : betrag;
138
+ }
139
+ // -------------------------------------------------------------- Gutscheine
140
+ /**
141
+ * Belegtext eines Gutscheins — Zwilling von `KeckVoucher.receiptText`:
142
+ * Gutscheinart, Wert und Name, wobei ein Name, der die Gutscheinart schon
143
+ * nennt, allein stehen bleibt.
144
+ */
145
+ function gutscheinText(voucher) {
146
+ let text = '';
147
+ if (voucher.type === index_js_1.VoucherType.value) {
148
+ text += 'Wertgutschein';
149
+ }
150
+ else if (voucher.type === index_js_1.VoucherType.promo) {
151
+ text += 'Promotionsgutschein';
152
+ }
153
+ const name = voucher.name;
154
+ if (name != null && name.toLowerCase().includes(text.toLowerCase())) {
155
+ return name;
156
+ }
157
+ if (voucher.valueCents != null) {
158
+ text += ` ${gutscheinBetrag(voucher.valueCents)} €`;
159
+ }
160
+ if (name != null && name.length > 0) {
161
+ text += ` - ${name}`;
162
+ }
163
+ return text;
164
+ }
165
+ /**
166
+ * Ganze Betraege kurz (`"10"`), krumme mit zwei Nachkommastellen (`"1,50"`) —
167
+ * Zwilling von `KeckVoucher.formatVoucherAmount`.
168
+ */
169
+ function gutscheinBetrag(cents) {
170
+ return cents % 100 === 0 ? String(cents / 100) : formatCents(cents);
171
+ }
172
+ // ----------------------------------------------------------------- Bauteile
173
+ function textZeile(text, align = 'left', bold = false) {
174
+ return { kind: 'text', text, align, bold };
175
+ }
176
+ /** Zwei Spalten: links beschriftet, rechts der Wert (Vorbild: `addDoubleText`). */
177
+ function paarZeile(links, rechts, linksBreite = 6, rechtsBreite = 6) {
178
+ return {
179
+ kind: 'columns',
180
+ columns: [
181
+ { text: links, width: linksBreite, align: 'left' },
182
+ { text: rechts, width: rechtsBreite, align: 'right' },
183
+ ],
184
+ };
185
+ }
186
+ /** Vier Spalten der USt-Tabelle (Vorbild: `_addTable`). */
187
+ function tabellenZeile(werte, paperSize) {
188
+ const breit = paperSize === 'mm80';
189
+ const breiten = [3, breit ? 4 : 3, 3, breit ? 2 : 3];
190
+ return {
191
+ kind: 'columns',
192
+ columns: werte.map((text, i) => ({
193
+ text,
194
+ width: breiten[i],
195
+ align: i === 3 ? 'right' : 'left',
196
+ })),
197
+ };
198
+ }
199
+ /** Positionszeile: Menge, Bezeichnung, ab Menge 2 der Einzelpreis. */
200
+ function positionsZeile(item, satz) {
201
+ const menge = String(item.quantity);
202
+ // Vorbild: Menge auf zwei Zeichen auffuellen, danach ' x ' — bei drei- und
203
+ // mehrstelligen Mengen faellt das Leerzeichen dahinter weg.
204
+ const kopf = menge.padEnd(2) + (menge.length > 2 ? ' x' : ' x ');
205
+ // "je" und Preis mit geschuetztem Leerzeichen: bricht die Zeile, bleibt "je 0,79" zusammen.
206
+ const einzelpreis = item.quantity > 1 ? ` je\u00a0${formatCents(item.priceCents)}` : '';
207
+ return {
208
+ kind: 'columns',
209
+ columns: [
210
+ { text: `${kopf}${item.name}${einzelpreis}`, width: 7, align: 'left' },
211
+ { text: `${formatCents((0, index_js_2.receiptItemTotalCents)(item))} ${satz.category}`, width: 5, align: 'right' },
212
+ ],
213
+ };
214
+ }
215
+ // ---------------------------------------------------------------- Belegart
216
+ const TESTKASSE_TEXT = 'TESTKASSE — kein gültiger Beleg';
217
+ const TESTSIGNATUR_TEXT = 'TESTSIGNATUR — kein gültiger Beleg';
218
+ /** Erklaerung am Trainingsbeleg (Wortlaut mit dem Betreiber abgestimmt). */
219
+ const TRAINING_ERKLAERUNG = [
220
+ 'Trainingsbuchung — kein Kauf, keine Zahlung, keine steuerliche Buchung.',
221
+ 'Sollten Sie diesen Beleg als Kunde erhalten haben, sagen Sie bitte dem Betrieb Bescheid.',
222
+ ];
223
+ function bannerZeile(text, ton = 'belegart') {
224
+ return { kind: 'banner', text, ton };
225
+ }
226
+ /** Belegtyp als Zeichenkette (Enum-Objekt oder roher Serverwert). */
227
+ function belegtyp(receipt) {
228
+ return typeof receipt.receiptType === 'object' ? receipt.receiptType.value : String(receipt.receiptType ?? '');
229
+ }
230
+ /** Ist das ein Nullbeleg (Start-, Monats-, Jahres-, Schluss-, Pruefbeleg)? */
231
+ function receiptIsZero(receipt) {
232
+ const t = belegtyp(receipt);
233
+ return t === 'zero' || t === 'start';
234
+ }
235
+ /**
236
+ * Sind alle Betraege des Belegs wirklich 0? (Keine Positionen mit Wert, keine
237
+ * Gutscheine.) Ein Nullbeleg, der das nicht erfuellt, ist ein Datenfehler --
238
+ * Regelwerk 2 setzt ihn dann als normalen Beleg.
239
+ */
240
+ function receiptAmountsAreZero(receipt) {
241
+ if (receipt.vouchers.length > 0)
242
+ return false;
243
+ return (0, index_js_2.receiptSumCents)(receipt) === 0 && receipt.items.every((it) => it.priceCents === 0 || it.quantity === 0);
244
+ }
245
+ /**
246
+ * Zertifizierungsdienst aus der QR-Kennung `_R1-<ZDA>_` (RKSV-Detailspezifikation:
247
+ * AT0 geschlossenes System, AT1 A-Trust, AT2 GlobalTrust, AT3 PrimeSign,
248
+ * AT100 Testsignatur). Unbekannte Kennung: nur die Kennung.
249
+ */
250
+ function receiptZdaText(qr) {
251
+ const m = /^_R1-(AT\d+)_/.exec(qr ?? '');
252
+ if (!m)
253
+ return null;
254
+ const kennung = m[1];
255
+ const namen = { AT0: 'geschlossenes System', AT1: 'A-Trust', AT2: 'GlobalTrust', AT3: 'PrimeSign', AT100: 'Testsignatur' };
256
+ const name = namen[kennung];
257
+ return name ? `${name} (${kennung})` : kennung;
258
+ }
259
+ /** „YYYY-MM-DD“ oder ISO -> „TT.MM.JJJJ“ (Wiener Kalendertag); Unlesbares -> null. */
260
+ function pruefDatum(wert) {
261
+ if (!wert)
262
+ return null;
263
+ const nurTag = /^(\d{4})-(\d{2})-(\d{2})$/.exec(wert);
264
+ if (nurTag)
265
+ return `${nurTag[3]}.${nurTag[2]}.${nurTag[1]}`;
266
+ try {
267
+ const t = (0, vienna_time_js_1.toViennaWallClock)((0, vienna_time_js_1.parseServerTimeStamp)(wert));
268
+ const zwei = (n) => String(n).padStart(2, '0');
269
+ return `${zwei(t.day)}.${zwei(t.month)}.${t.year}`;
270
+ }
271
+ catch {
272
+ return null;
273
+ }
274
+ }
275
+ /** Block „Prüfangaben“ des Nullbelegs (Regelwerk 2). Nichts Geheimes: alles steht auch im QR bzw. bei FinanzOnline. */
276
+ function pruefangabenBlock(receipt, angaben) {
277
+ const out = [];
278
+ out.push({ kind: 'rule', char: '-' });
279
+ out.push(textZeile('Prüfangaben', 'center', true));
280
+ out.push(paarZeile('Barumsatz:', '0,00 €'));
281
+ out.push(paarZeile('Signatur:', receiptSignatureFailed(receipt) ? 'ausgefallen' : 'signiert'));
282
+ // Die Zertifikat-Seriennummer ist hexadezimal (RKSV-Detailspezifikation) --
283
+ // mit 0x gekennzeichnet, damit niemand sie fuer eine Dezimalzahl haelt.
284
+ if (receipt.certificateSerialNumber)
285
+ out.push(paarZeile('Signaturkarte:', `0x${receipt.certificateSerialNumber}`));
286
+ const zda = receiptZdaText(receipt.qr);
287
+ if (zda)
288
+ out.push(paarZeile('Zertifizierungsdienst:', zda, 7, 5));
289
+ const karte = pruefDatum(angaben?.karteRegistriertAm);
290
+ if (karte)
291
+ out.push(paarZeile('Karte registriert:', karte));
292
+ const kasse = pruefDatum(angaben?.kasseRegistriertAm);
293
+ if (kasse)
294
+ out.push(paarZeile('Kasse registriert:', kasse));
295
+ out.push({ kind: 'rule', char: '-' });
296
+ return out;
297
+ }
298
+ /** Monat/Jahr des Belegs in Wiener Zeit -- fuer „Nullbeleg 08/2026“. */
299
+ function belegMonatJahr(timeStamp) {
300
+ try {
301
+ const t = (0, vienna_time_js_1.toViennaWallClock)((0, vienna_time_js_1.parseServerTimeStamp)(timeStamp));
302
+ return { monat: String(t.month).padStart(2, '0'), jahr: String(t.year) };
303
+ }
304
+ catch {
305
+ return { monat: '??', jahr: '????' };
306
+ }
307
+ }
308
+ /**
309
+ * Grund eines Stornos lesbar -- aus dem EINEN Katalog des Modells
310
+ * (`CANCELLATION_REASONS`, derselbe, den die Kasse anbietet und das Backend
311
+ * annimmt). Unbekannter Code (Fremdclient): sichtbar, aber ohne zu werfen.
312
+ */
313
+ function stornoGrundText(code) {
314
+ if (!code)
315
+ return null;
316
+ return (0, index_js_2.isCancellationReason)(code) ? index_js_2.CANCELLATION_REASONS[code] : code;
317
+ }
318
+ /**
319
+ * Kleinunternehmer (§ 6 Abs. 1 Z 27 UStG, unechte Befreiung): auf dem Beleg
320
+ * darf keine USt stehen -- jede Position 0 %. Sagt, ob der Beleg dazu passt;
321
+ * das Backend weist Widersprueche beim Ausstellen ab, hier ist es die Probe.
322
+ */
323
+ function receiptIsSmallBusinessConsistent(receipt) {
324
+ return receipt.items.every((it) => Number(steuersatz(it.vat).rate) === 0);
325
+ }
326
+ /**
327
+ * Belegart-Block unter dem Betriebskopf: Titel als Banner, Untertitel als
328
+ * zentrierte Textzeilen. Verkaufsbelege bekommen keinen Block.
329
+ */
330
+ function belegartBlock(receipt) {
331
+ const t = belegtyp(receipt);
332
+ const aus = [];
333
+ if (t === 'cancellation') {
334
+ aus.push(bannerZeile('STORNOBELEG'));
335
+ aus.push(textZeile(receipt.cancellationOf ? `Stornobuchung zu Beleg ${receipt.cancellationOf.receiptId}` : 'Stornobuchung', 'center'));
336
+ const grund = stornoGrundText(typeof receipt.cancellationReason === 'string' ? receipt.cancellationReason : undefined);
337
+ if (grund)
338
+ aus.push(textZeile(`Grund: ${grund}`, 'center'));
339
+ }
340
+ else if (t === 'training') {
341
+ aus.push(bannerZeile('TRAININGSBELEG'));
342
+ for (const z of TRAINING_ERKLAERUNG)
343
+ aus.push(textZeile(z, 'center'));
344
+ }
345
+ else if (t === 'start') {
346
+ aus.push(bannerZeile('STARTBELEG'));
347
+ aus.push(textZeile('Nullbeleg zur Inbetriebnahme', 'center'));
348
+ }
349
+ else if (t === 'zero') {
350
+ const { monat, jahr } = belegMonatJahr(receipt.timeStamp);
351
+ switch (receipt.zeroKind) {
352
+ case 'monthly':
353
+ aus.push(bannerZeile('MONATSBELEG'), textZeile(`Nullbeleg ${monat}/${jahr}`, 'center'));
354
+ break;
355
+ case 'annual':
356
+ aus.push(bannerZeile('JAHRESBELEG'), textZeile(`Nullbeleg ${jahr} — Prüfung mit BMF-App`, 'center'));
357
+ break;
358
+ case 'annual_replacement':
359
+ // Ersatz-Jahresbeleg wird im Folgejahr erzeugt und gilt fuer das Vorjahr.
360
+ aus.push(bannerZeile('JAHRESBELEG'), textZeile(`Nullbeleg ${String(Number(jahr) - 1)} — Prüfung mit BMF-App (Ersatzbeleg)`, 'center'));
361
+ break;
362
+ case 'final':
363
+ aus.push(bannerZeile('SCHLUSSBELEG'), textZeile('Nullbeleg zur Außerbetriebnahme', 'center'));
364
+ break;
365
+ case 'outage_end':
366
+ aus.push(bannerZeile('NULLBELEG'), textZeile('Prüfbeleg nach Signaturausfall', 'center'));
367
+ break;
368
+ default:
369
+ aus.push(bannerZeile('NULLBELEG'), textZeile('Prüfbeleg', 'center'));
370
+ }
371
+ }
372
+ return aus;
373
+ }
374
+ // ------------------------------------------------------------------- Aufbau
375
+ /**
376
+ * Baut das Layout eines Belegs. Reihenfolge und Spaltenbreiten folgen
377
+ * `print_paper.dart` (`setKeckReceipt`).
378
+ */
379
+ function buildReceiptLayout(receipt, company, options = {}) {
380
+ const paperSize = options.paperSize ?? 'mm58';
381
+ const regelwerk = options.regelwerk ?? exports.AKTUELLES_REGELWERK;
382
+ if (regelwerk !== 1 && regelwerk !== 2) {
383
+ throw new errors_js_1.KasseneckValidationError('buildReceiptLayout', `Unbekanntes Layout-Regelwerk ${String(regelwerk)} — bitte Paket aktualisieren`, 'request');
384
+ }
385
+ const lines = [];
386
+ // Regelwerk 2: ein „Nullbeleg“ mit echten Betraegen wird nicht reduziert --
387
+ // die Zahlen stehen drauf, statt hinter einer Null zu verschwinden.
388
+ const nullbeleg = receiptIsZero(receipt) && (regelwerk === 1 || receiptAmountsAreZero(receipt));
389
+ const warnungen = [];
390
+ if (options.testKasse)
391
+ warnungen.push(bannerZeile(TESTKASSE_TEXT, 'warnung'));
392
+ if (options.testSignatur)
393
+ warnungen.push(bannerZeile(TESTSIGNATUR_TEXT, 'warnung'));
394
+ // --- Warnrahmen oben (Testkasse/Testsignatur): ueber dem Kopf, damit ihn niemand uebersieht.
395
+ lines.push(...warnungen);
396
+ // --- Belegkopf: Firma, Anschrift, Steuerangabe, Telefon
397
+ lines.push(textZeile(company.companyName, 'center', true));
398
+ lines.push(textZeile(company.street, 'center'));
399
+ lines.push(textZeile(`${company.zip} ${company.city}`, 'center'));
400
+ lines.push(textZeile((0, index_js_2.receiptCompanyTaxInfo)(company), 'center'));
401
+ lines.push(textZeile(company.phone, 'center'));
402
+ // --- Belegart (Storno, Training, Nullbeleg-Arten) direkt unter dem Kopf
403
+ const belegart = belegartBlock(receipt);
404
+ if (belegart.length > 0) {
405
+ lines.push({ kind: 'space', lines: 1 });
406
+ lines.push(...belegart);
407
+ }
408
+ // --- Nullbeleg: reduziert (§ 132a Abs. 3 BAO / RKSV § 11 Abs. 1 -- Pflichtangaben
409
+ // ja, Positionen/MwSt-Tabelle/Zahlungsart/Fusszeilen nein).
410
+ if (nullbeleg) {
411
+ lines.push({ kind: 'space', lines: 1 });
412
+ lines.push(paarZeile('Datum:', belegZeit(receipt.timeStamp), 4, 8));
413
+ lines.push(paarZeile('Kassen-ID:', receipt.cashregisterId));
414
+ lines.push(paarZeile('Beleg-ID:', receipt.receiptId));
415
+ lines.push({ kind: 'space', lines: 1 });
416
+ if (regelwerk === 1) {
417
+ lines.push(textZeile('Betrag: 0,00 €', 'center', true));
418
+ }
419
+ else {
420
+ lines.push(...pruefangabenBlock(receipt, options.pruefangaben ?? null));
421
+ }
422
+ lines.push({ kind: 'space', lines: 1 });
423
+ if (receiptSignatureFailed(receipt)) {
424
+ lines.push(textZeile(SIGNATUR_AUSGEFALLEN, 'center'));
425
+ lines.push({ kind: 'space', lines: 1 });
426
+ }
427
+ lines.push({ kind: 'qr', data: receipt.qr });
428
+ lines.push({ kind: 'space', lines: 1 });
429
+ lines.push(...warnungen);
430
+ return { lines, paperSize, regelwerk };
431
+ }
432
+ // --- Kundendaten
433
+ if (receipt.customerDetails.length > 0) {
434
+ lines.push({ kind: 'space', lines: 1 });
435
+ receipt.customerDetails.forEach((zeile, i) => {
436
+ lines.push(i === 0 ? paarZeile('Kunde:', zeile, 5, 7) : paarZeile('', zeile, 1, 11));
437
+ });
438
+ }
439
+ // --- Belegkennung
440
+ lines.push({ kind: 'space', lines: 1 });
441
+ lines.push(paarZeile('Datum:', belegZeit(receipt.timeStamp), 4, 8));
442
+ lines.push(paarZeile('Kassen-ID:', receipt.cashregisterId));
443
+ lines.push(paarZeile('Beleg-ID:', receipt.receiptId));
444
+ lines.push({ kind: 'space', lines: 1 });
445
+ // --- Positionen; dabei die Steuergruppen aufsammeln (Reihenfolge des
446
+ // ersten Auftretens, wie im Vorbild).
447
+ const gruppen = new Map();
448
+ const gruppeVon = (satz) => {
449
+ const vorhanden = gruppen.get(satz.rate);
450
+ if (vorhanden !== undefined) {
451
+ return vorhanden;
452
+ }
453
+ const neu = { satz, bruttoCents: 0 };
454
+ gruppen.set(satz.rate, neu);
455
+ return neu;
456
+ };
457
+ for (const item of receipt.items) {
458
+ const satz = steuersatz(item.vat);
459
+ gruppeVon(satz).bruttoCents += (0, index_js_2.receiptItemTotalCents)(item);
460
+ lines.push(positionsZeile(item, satz));
461
+ }
462
+ // --- Gutscheine: verkaufte Wertgutscheine sind Umsatz zu 0 %, eingeloeste
463
+ // Promotionsgutscheine mindern die Steuergruppen anteilig.
464
+ let promoCents = 0;
465
+ for (const voucher of receipt.vouchers) {
466
+ const wert = voucher.valueCents ?? 0;
467
+ if ((0, index_js_2.voucherIsValid)(voucher) && voucher.action === index_js_1.VoucherAction.sell) {
468
+ gruppeVon(steuersatz(index_js_1.VatRate.vat0)).bruttoCents += wert;
469
+ }
470
+ if (voucher.action === index_js_1.VoucherAction.sell && voucher.type === index_js_1.VoucherType.value) {
471
+ lines.push({
472
+ kind: 'columns',
473
+ columns: [
474
+ { text: `1 x ${gutscheinText(voucher)}`, width: 7, align: 'left' },
475
+ { text: `${formatCents(wert)} ${index_js_1.VatRate.vat0.category}`, width: 5, align: 'right' },
476
+ ],
477
+ });
478
+ }
479
+ if (voucher.action === index_js_1.VoucherAction.redeem && voucher.type === index_js_1.VoucherType.promo) {
480
+ promoCents += wert;
481
+ lines.push({
482
+ kind: 'columns',
483
+ columns: [
484
+ { text: gutscheinText(voucher), width: 7, align: 'left' },
485
+ { text: `-${formatCents(wert)} €`, width: 5, align: 'right' },
486
+ ],
487
+ });
488
+ }
489
+ }
490
+ verteilePromoGutschein(gruppen, promoCents);
491
+ // --- USt-Aufteilung
492
+ lines.push({ kind: 'space', lines: 1 });
493
+ lines.push(tabellenZeile(['MwSt%', 'MwSt', 'Netto', 'Brutto'], paperSize));
494
+ for (const gruppe of gruppen.values()) {
495
+ // Netto aus dem Brutto in ganzen Cent, MwSt als Rest: so ergeben Netto und
496
+ // MwSt zusammen immer wieder exakt den Brutto-Betrag der Zeile. (Das
497
+ // Vorbild rundet beide Werte einzeln aus einer Gleitkommazahl und kann
498
+ // dabei um einen Cent auseinanderlaufen.)
499
+ const bruttoCents = gruppe.bruttoCents;
500
+ const nettoCents = nettoAusBrutto(bruttoCents, gruppe.satz.rate);
501
+ lines.push(tabellenZeile([
502
+ `${gruppe.satz.category} ${satzText(gruppe.satz.rate)}%`,
503
+ formatCents(bruttoCents - nettoCents),
504
+ formatCents(nettoCents),
505
+ formatCents(bruttoCents),
506
+ ], paperSize));
507
+ }
508
+ lines.push({ kind: 'rule', char: '-' });
509
+ // --- Summen
510
+ const summeCents = (0, index_js_2.receiptSumCents)(receipt);
511
+ const zwischensummeCents = (0, index_js_2.receiptSubSumCents)(receipt);
512
+ if (summeCents !== zwischensummeCents) {
513
+ lines.push(paarZeile('Zwischensumme', `${formatCents(zwischensummeCents)} €`));
514
+ for (const voucher of receipt.vouchers) {
515
+ if (voucher.action === index_js_1.VoucherAction.redeem && voucher.type === index_js_1.VoucherType.value) {
516
+ lines.push(paarZeile(gutscheinText(voucher), `-${formatCents(voucher.valueCents ?? 0)} €`));
517
+ }
518
+ }
519
+ lines.push({ kind: 'rule', char: '-' });
520
+ }
521
+ lines.push(paarZeile('Gesamt:', `${formatCents(summeCents)} €`));
522
+ lines.push(paarZeile('Zahlungsart:', zahlungsartText(receipt.paymentMethod)));
523
+ lines.push({ kind: 'space', lines: 1 });
524
+ // --- Rechtshinweise und Ausfallhinweis
525
+ const hinweise = company.isSmallBusiness
526
+ ? [exports.SMALL_BUSINESS_NOTICE, ...receipt.legalMessage]
527
+ : receipt.legalMessage;
528
+ if (hinweise.length > 0) {
529
+ for (const zeile of hinweise) {
530
+ lines.push(textZeile(zeile, 'center'));
531
+ }
532
+ lines.push({ kind: 'space', lines: 1 });
533
+ }
534
+ if (receiptSignatureFailed(receipt)) {
535
+ lines.push(textZeile(SIGNATUR_AUSGEFALLEN, 'center'));
536
+ lines.push({ kind: 'space', lines: 1 });
537
+ }
538
+ // --- RKSV-QR-Code
539
+ lines.push({ kind: 'qr', data: receipt.qr });
540
+ lines.push({ kind: 'space', lines: 1 });
541
+ // --- Dankestext und Fusszeilen
542
+ if (company.thanksMessage.length > 0) {
543
+ lines.push({ kind: 'space', lines: 1 });
544
+ for (const zeile of company.thanksMessage) {
545
+ lines.push(textZeile(zeile, 'center'));
546
+ }
547
+ }
548
+ for (const fuss of [company.footer1, company.footer2, company.footer3, company.footer4]) {
549
+ // Leere Fusszeilen werden ausgelassen: sie truegen nichts und erzeugten
550
+ // auf dem Bildschirm eine sichtbare Luecke.
551
+ if (fuss != null && fuss.length > 0) {
552
+ lines.push(textZeile(fuss, 'center'));
553
+ }
554
+ }
555
+ // --- Warnrahmen unten (Testkasse/Testsignatur)
556
+ if (warnungen.length > 0) {
557
+ lines.push({ kind: 'space', lines: 1 });
558
+ lines.push(...warnungen);
559
+ }
560
+ return { lines, paperSize, regelwerk };
561
+ }
562
+ /**
563
+ * Verteilt einen eingeloesten Promotionsgutschein anteilig auf die
564
+ * Steuergruppen — Zwilling von `print_paper.dart` (Zeilen 296-325).
565
+ *
566
+ * Der Gutschein mindert keinen einzelnen Artikel, sondern den Umsatz: jede
567
+ * Gruppe gibt ihren Anteil am Brutto ab. Der durch die ganzzahlige Teilung
568
+ * uebrig bleibende Cent geht an die groesste Gruppe, damit die Tabelle in
569
+ * Summe wieder die Zwischensumme ergibt.
570
+ */
571
+ function verteilePromoGutschein(gruppen, promoCents) {
572
+ const gesamtCents = [...gruppen.values()].reduce((summe, g) => summe + g.bruttoCents, 0);
573
+ const nutzbarCents = promoCents > gesamtCents ? gesamtCents : promoCents;
574
+ if (nutzbarCents <= 0 || gesamtCents <= 0) {
575
+ return;
576
+ }
577
+ const anteile = new Map();
578
+ let verteiltCents = 0;
579
+ for (const [satz, gruppe] of gruppen) {
580
+ const anteil = Math.trunc((nutzbarCents * gruppe.bruttoCents) / gesamtCents);
581
+ anteile.set(satz, anteil);
582
+ verteiltCents += anteil;
583
+ }
584
+ const restCents = nutzbarCents - verteiltCents;
585
+ if (restCents > 0) {
586
+ const groesste = [...gruppen.entries()].sort((a, b) => b[1].bruttoCents - a[1].bruttoCents)[0];
587
+ if (groesste !== undefined && groesste[1].bruttoCents > 0) {
588
+ anteile.set(groesste[0], (anteile.get(groesste[0]) ?? 0) + restCents);
589
+ }
590
+ }
591
+ for (const [satz, gruppe] of gruppen) {
592
+ gruppe.bruttoCents -= anteile.get(satz) ?? 0;
593
+ }
594
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Unterpfad `@kreiseck/kasseneck-api/register` — die **Anmeldung der
3
+ * Browser-Kasse**: ein Geraet koppeln, seine Kassen-Benutzer auflisten, einen
4
+ * davon per PIN anmelden (mit oder ohne Benutzerauswahl), und die entstandene
5
+ * Sitzung erneuern bzw. beenden.
6
+ *
7
+ * Diese Aufrufe stehen vor allen anderen: ohne sie gibt es keine
8
+ * Identitaet, mit der ein Beleg entstehen koennte.
9
+ *
10
+ * **Sie zerfallen in zwei Haelften, und der Unterschied ist wesentlich:**
11
+ *
12
+ * | Aufruf | Anmeldung |
13
+ * |---|---|
14
+ * | [pairRegisterDevice] | **keine** — der Kopplungs-Code ist der Nachweis |
15
+ * | [listRegisterUsersForDevice] | **keine** — der Ausweis des Geraets ist der Nachweis |
16
+ * | [unpairRegisterDevice] | **keine** — der Ausweis des Geraets; sperrt es im Backend (Entkoppeln) |
17
+ * | [registerUserLogin] | **keine** — dieser Aufruf erzeugt sie |
18
+ * | [registerPinLogin] | **keine** — wie registerUserLogin, nur ohne Benutzerauswahl (Geraete-Modus `pin`) |
19
+ * | [renewRegisterSession] | Kassen-Benutzer (`registerUserAuth`) |
20
+ * | [endRegisterSession] | Kassen-Benutzer (`registerUserAuth`) |
21
+ *
22
+ * Die ersten drei nehmen deshalb **keinen Transport** entgegen, sondern nur
23
+ * die Verbindungsangaben, und bauen ihn selbst — die anmeldungsfreie Anmeldung
24
+ * dahinter wird bewusst nicht exportiert, sonst liesse sich damit jeder Aufruf
25
+ * des Pakets ohne Anmeldung bauen (ausfuehrlich in pairing.ts). Die letzten
26
+ * beiden nehmen den Transport wie jeder andere Endpunkt-Aufruf und stehen
27
+ * zusaetzlich in `createKasseneckApi`.
28
+ *
29
+ * Der uebliche Ablauf einer Browser-Kasse:
30
+ *
31
+ * ```ts
32
+ * import {
33
+ * pairRegisterDevice, listRegisterUsersForDevice, registerUserLogin,
34
+ * } from '@kreiseck/kasseneck-api/register';
35
+ *
36
+ * // Einmalig: Code aus dem Panel gegen den Geraeteausweis tauschen.
37
+ * const geraet = await pairRegisterDevice({ code: 'K7NPQR34', label: 'Schank' });
38
+ *
39
+ * // Bei jedem Schichtwechsel: auswaehlen und per PIN anmelden.
40
+ * const { users } = await listRegisterUsersForDevice(geraet);
41
+ * const sitzung = await registerUserLogin({
42
+ * ...geraet, userId: users[0]!.id, pin: '1234', cashregisterId: geraet.cashregisterId,
43
+ * });
44
+ * ```
45
+ *
46
+ * Mit `sitzung.customToken` meldet sich der Verbraucher bei Firebase an; das
47
+ * daraus entstehende ID-Token und `sitzung.sessionId` ergeben zusammen
48
+ * `registerUserAuth` — ab da laeuft alles Weitere ueber den ueblichen Weg.
49
+ */
50
+ export { type RegisterDeviceConnection, type RegisterDeviceCredentials, type PairRegisterDeviceOptions, type PairedRegisterDevice, type RegisterUserKind, type RegisterUserSummary, type RegisterUserPerms, type RegisterScope, type RegisterUser, type RegisterUserSession, type ListRegisterUsersForDeviceOptions, type RegisterUserLoginOptions, type RegisterPinLoginOptions, type RegisterPinPolicy, type RegisterLoginMode, type RegisterDeviceUsers, type RegisterClientInfo, type RegisterGeo, type RegisterGeraeteAngaben, pairRegisterDevice, listRegisterUsersForDevice, unpairRegisterDevice, registerUserLogin, registerPinLogin, } from './pairing.js';
51
+ export { renewRegisterSession, endRegisterSession } from './session.js';