@kreiseck/kasseneck-api 0.13.1 → 0.14.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 (172) hide show
  1. package/CHANGELOG.md +91 -0
  2. package/README.md +127 -21
  3. package/dist/cjs/client/auth.d.ts +4 -4
  4. package/dist/cjs/client/auth.js +3 -3
  5. package/dist/cjs/client/cashregisters.d.ts +1 -1
  6. package/dist/cjs/client/cashregisters.js +1 -1
  7. package/dist/cjs/client/errors.d.ts +1 -1
  8. package/dist/cjs/client/errors.js +2 -2
  9. package/dist/cjs/client/receipts.d.ts +8 -1
  10. package/dist/cjs/client/receipts.js +2 -1
  11. package/dist/cjs/client/transport.d.ts +1 -1
  12. package/dist/cjs/client/transport.js +2 -2
  13. package/dist/cjs/kasse/drucker.d.ts +5 -0
  14. package/dist/cjs/kasse/drucker.js +9 -0
  15. package/dist/cjs/kasse/settings.js +5 -2
  16. package/dist/cjs/models/cashregister.d.ts +2 -2
  17. package/dist/cjs/models/receipt-company.d.ts +1 -1
  18. package/dist/cjs/printing/escpos.d.ts +38 -4
  19. package/dist/cjs/printing/escpos.js +87 -15
  20. package/dist/cjs/printing/index.d.ts +2 -2
  21. package/dist/cjs/printing/index.js +5 -1
  22. package/dist/cjs/printing/qr-groesse.d.ts +26 -21
  23. package/dist/cjs/printing/qr-groesse.js +22 -8
  24. package/dist/cjs/react/index.d.ts +53 -0
  25. package/dist/cjs/react/index.js +81 -0
  26. package/dist/cjs/receipt/bild.d.ts +14 -0
  27. package/dist/cjs/receipt/bild.js +65 -0
  28. package/dist/cjs/receipt/blatt.d.ts +107 -0
  29. package/dist/cjs/receipt/blatt.js +137 -0
  30. package/dist/cjs/receipt/epos.d.ts +19 -8
  31. package/dist/cjs/receipt/epos.js +39 -20
  32. package/dist/cjs/receipt/grid.d.ts +1 -1
  33. package/dist/cjs/receipt/grid.js +9 -1
  34. package/dist/cjs/receipt/index.d.ts +4 -2
  35. package/dist/cjs/receipt/index.js +17 -1
  36. package/dist/cjs/receipt/layout-escpos.d.ts +31 -3
  37. package/dist/cjs/receipt/layout-escpos.js +58 -18
  38. package/dist/cjs/register/index.d.ts +1 -1
  39. package/dist/cjs/register/index.js +1 -1
  40. package/dist/cjs/register/pairing.d.ts +2 -2
  41. package/dist/esm/client/auth.d.ts +4 -4
  42. package/dist/esm/client/auth.js +3 -3
  43. package/dist/esm/client/cashregisters.d.ts +1 -1
  44. package/dist/esm/client/cashregisters.js +1 -1
  45. package/dist/esm/client/errors.d.ts +1 -1
  46. package/dist/esm/client/errors.js +2 -2
  47. package/dist/esm/client/receipts.d.ts +8 -1
  48. package/dist/esm/client/receipts.js +2 -1
  49. package/dist/esm/client/transport.d.ts +1 -1
  50. package/dist/esm/client/transport.js +2 -2
  51. package/dist/esm/kasse/drucker.d.ts +5 -0
  52. package/dist/esm/kasse/drucker.js +9 -0
  53. package/dist/esm/kasse/settings.js +5 -2
  54. package/dist/esm/models/cashregister.d.ts +2 -2
  55. package/dist/esm/models/receipt-company.d.ts +1 -1
  56. package/dist/esm/printing/escpos.d.ts +38 -4
  57. package/dist/esm/printing/escpos.js +84 -15
  58. package/dist/esm/printing/index.d.ts +2 -2
  59. package/dist/esm/printing/index.js +2 -2
  60. package/dist/esm/printing/qr-groesse.d.ts +26 -21
  61. package/dist/esm/printing/qr-groesse.js +21 -8
  62. package/dist/esm/react/index.d.ts +53 -0
  63. package/dist/esm/react/index.js +80 -1
  64. package/dist/esm/receipt/bild.d.ts +14 -0
  65. package/dist/esm/receipt/bild.js +62 -0
  66. package/dist/esm/receipt/blatt.d.ts +107 -0
  67. package/dist/esm/receipt/blatt.js +128 -0
  68. package/dist/esm/receipt/epos.d.ts +19 -8
  69. package/dist/esm/receipt/epos.js +40 -22
  70. package/dist/esm/receipt/grid.d.ts +1 -1
  71. package/dist/esm/receipt/grid.js +9 -1
  72. package/dist/esm/receipt/index.d.ts +4 -2
  73. package/dist/esm/receipt/index.js +4 -2
  74. package/dist/esm/receipt/layout-escpos.d.ts +31 -3
  75. package/dist/esm/receipt/layout-escpos.js +58 -20
  76. package/dist/esm/register/index.d.ts +1 -1
  77. package/dist/esm/register/index.js +1 -1
  78. package/dist/esm/register/pairing.d.ts +2 -2
  79. package/fixtures/erwartet/karte-eigener.blatt32.json +159 -0
  80. package/fixtures/erwartet/karte-eigener.blatt48.json +159 -0
  81. package/fixtures/erwartet/karte-gptom-ios.blatt32.json +231 -0
  82. package/fixtures/erwartet/karte-gptom-ios.blatt48.json +231 -0
  83. package/fixtures/erwartet/karte-gptom.blatt32.json +231 -0
  84. package/fixtures/erwartet/karte-gptom.blatt48.json +231 -0
  85. package/fixtures/erwartet/karte-hobex-cloud.blatt32.json +201 -0
  86. package/fixtures/erwartet/karte-hobex-cloud.blatt48.json +201 -0
  87. package/fixtures/erwartet/karte-hobex-hps.blatt32.json +225 -0
  88. package/fixtures/erwartet/karte-hobex-hps.blatt48.json +225 -0
  89. package/fixtures/erwartet/karte-mypos.blatt32.json +255 -0
  90. package/fixtures/erwartet/karte-mypos.blatt48.json +243 -0
  91. package/fixtures/erwartet/karte-stripe-eps.blatt32.json +195 -0
  92. package/fixtures/erwartet/karte-stripe-eps.blatt48.json +195 -0
  93. package/fixtures/erwartet/karte-stripe.blatt32.json +213 -0
  94. package/fixtures/erwartet/karte-stripe.blatt48.json +213 -0
  95. package/fixtures/erwartet/karte-sumup.blatt32.json +219 -0
  96. package/fixtures/erwartet/karte-sumup.blatt48.json +207 -0
  97. package/fixtures/erwartet/langer-artikelname.blatt32.json +177 -0
  98. package/fixtures/erwartet/langer-artikelname.blatt48.json +171 -0
  99. package/fixtures/erwartet/logo-probe-hoch.raster32.txt +120 -0
  100. package/fixtures/erwartet/logo-probe.raster32.txt +54 -0
  101. package/fixtures/erwartet/null-ausfall.blatt32.json +207 -0
  102. package/fixtures/erwartet/null-ausfall.blatt48.json +189 -0
  103. package/fixtures/erwartet/null-ausfall.grid32.txt +2 -0
  104. package/fixtures/erwartet/null-ausfall.grid48.txt +2 -0
  105. package/fixtures/erwartet/null-jahr.blatt32.json +213 -0
  106. package/fixtures/erwartet/null-jahr.blatt48.json +189 -0
  107. package/fixtures/erwartet/null-jahr.grid32.txt +2 -0
  108. package/fixtures/erwartet/null-jahr.grid48.txt +2 -0
  109. package/fixtures/erwartet/null-monat.blatt32.json +207 -0
  110. package/fixtures/erwartet/null-monat.blatt48.json +189 -0
  111. package/fixtures/erwartet/null-monat.grid32.txt +2 -0
  112. package/fixtures/erwartet/null-monat.grid48.txt +2 -0
  113. package/fixtures/erwartet/null-pruef.blatt32.json +207 -0
  114. package/fixtures/erwartet/null-pruef.blatt48.json +189 -0
  115. package/fixtures/erwartet/null-pruef.grid32.txt +2 -0
  116. package/fixtures/erwartet/null-pruef.grid48.txt +2 -0
  117. package/fixtures/erwartet/null-schluss.blatt32.json +207 -0
  118. package/fixtures/erwartet/null-schluss.blatt48.json +189 -0
  119. package/fixtures/erwartet/null-schluss.grid32.txt +2 -0
  120. package/fixtures/erwartet/null-schluss.grid48.txt +2 -0
  121. package/fixtures/erwartet/null-start.blatt32.json +207 -0
  122. package/fixtures/erwartet/null-start.blatt48.json +189 -0
  123. package/fixtures/erwartet/null-start.grid32.txt +2 -0
  124. package/fixtures/erwartet/null-start.grid48.txt +2 -0
  125. package/fixtures/erwartet/rabatt-chef-trinkgeld.blatt32.json +213 -0
  126. package/fixtures/erwartet/rabatt-chef-trinkgeld.blatt48.json +207 -0
  127. package/fixtures/erwartet/rabatt-einfach.blatt32.json +183 -0
  128. package/fixtures/erwartet/rabatt-einfach.blatt48.json +177 -0
  129. package/fixtures/erwartet/rabatt-trinkgeld.blatt32.json +213 -0
  130. package/fixtures/erwartet/rabatt-trinkgeld.blatt48.json +207 -0
  131. package/fixtures/erwartet/rabatt-wertgutschein.blatt32.json +219 -0
  132. package/fixtures/erwartet/rabatt-wertgutschein.blatt48.json +213 -0
  133. package/fixtures/erwartet/rabattzeilen.blatt32.json +201 -0
  134. package/fixtures/erwartet/rabattzeilen.blatt48.json +195 -0
  135. package/fixtures/erwartet/signaturausfall-verkauf.blatt32.json +195 -0
  136. package/fixtures/erwartet/signaturausfall-verkauf.blatt48.json +183 -0
  137. package/fixtures/erwartet/storno-rabatt.blatt32.json +249 -0
  138. package/fixtures/erwartet/storno-rabatt.blatt48.json +237 -0
  139. package/fixtures/erwartet/storno-rabatt.grid32.txt +2 -0
  140. package/fixtures/erwartet/storno-rabatt.grid48.txt +2 -0
  141. package/fixtures/erwartet/storno-teil.blatt32.json +207 -0
  142. package/fixtures/erwartet/storno-teil.blatt48.json +201 -0
  143. package/fixtures/erwartet/storno-teil.grid32.txt +2 -0
  144. package/fixtures/erwartet/storno-teil.grid48.txt +2 -0
  145. package/fixtures/erwartet/storno-voll.blatt32.json +219 -0
  146. package/fixtures/erwartet/storno-voll.blatt48.json +213 -0
  147. package/fixtures/erwartet/storno-voll.grid32.txt +2 -0
  148. package/fixtures/erwartet/storno-voll.grid48.txt +2 -0
  149. package/fixtures/erwartet/testkasse-verkauf.blatt32.json +225 -0
  150. package/fixtures/erwartet/testkasse-verkauf.blatt48.json +219 -0
  151. package/fixtures/erwartet/testkasse-verkauf.grid32.txt +5 -1
  152. package/fixtures/erwartet/testkasse-verkauf.grid48.txt +5 -1
  153. package/fixtures/erwartet/testsignatur-verkauf.blatt32.json +237 -0
  154. package/fixtures/erwartet/testsignatur-verkauf.blatt48.json +219 -0
  155. package/fixtures/erwartet/testsignatur-verkauf.grid32.txt +5 -1
  156. package/fixtures/erwartet/testsignatur-verkauf.grid48.txt +5 -1
  157. package/fixtures/erwartet/training.blatt32.json +237 -0
  158. package/fixtures/erwartet/training.blatt48.json +219 -0
  159. package/fixtures/erwartet/training.grid32.txt +2 -0
  160. package/fixtures/erwartet/training.grid48.txt +2 -0
  161. package/fixtures/erwartet/verkauf-bar.blatt32.json +177 -0
  162. package/fixtures/erwartet/verkauf-bar.blatt48.json +171 -0
  163. package/fixtures/erwartet/verkauf-karte.blatt32.json +177 -0
  164. package/fixtures/erwartet/verkauf-karte.blatt48.json +171 -0
  165. package/fixtures/erwartet/verkauf-kleinunternehmer.blatt32.json +183 -0
  166. package/fixtures/erwartet/verkauf-kleinunternehmer.blatt48.json +177 -0
  167. package/fixtures/hobex-hps-codes.json +1 -1
  168. package/fixtures/kasse-settings-standard.json +1 -1
  169. package/fixtures/kasse-texte.json +1 -1
  170. package/fixtures/manifest.json +109 -43
  171. package/fixtures/oberflaeche.json +1 -1
  172. package/package.json +20 -5
@@ -1,11 +1,12 @@
1
1
  import type { ReceiptLayout } from './layout.js';
2
2
  import type { PosPaperSize } from '../printing/escpos.js';
3
- import { type QrModulGroesse } from '../printing/index.js';
3
+ import { type QrModulGroesse, type RasterBild } from '../printing/index.js';
4
+ import { type DruckLogo } from './layout-escpos.js';
4
5
  /**
5
6
  * ePOS-Print XML (Epson TM-Drucker: Server Direct Print, ePOS-Print ueber
6
7
  * HTTP) -- **aus dem Zeichenraster**: jede Rasterzeile wird eine <text>-Zeile
7
- * mit exakt N Zeichen und Zeilenumbruch, Aufdrucke doppelt hoch (Warnungen
8
- * invers), QR als <symbol>, Leerraum als <feed>, am Ende Schnitt. Kein
8
+ * mit exakt N Zeichen und Zeilenumbruch, Aufdrucke als Rahmen aus
9
+ * Rasterzeilen, QR als <symbol>, Leerraum als <feed>, am Ende Schnitt. Kein
9
10
  * eigenes Setzen -- was Bildschirm, ESC/POS und PDF zeigen, druckt der Epson
10
11
  * Zeile fuer Zeile genauso.
11
12
  *
@@ -22,16 +23,20 @@ export interface EposPrintXmlOptions {
22
23
  */
23
24
  qrBreite?: number;
24
25
  /**
25
- * Deckel fuer die gerechnete QR-Modulgroesse; Vorgabe `mittel`.
26
+ * Deckel fuer die gerechnete QR-Modulgroesse; Vorgabe `auto` (hoechstens 6
27
+ * Punkte je Modul) -- wie ESC/POS, Blatt und der Flutter-Zwilling.
26
28
  *
27
- * Warum `mittel` und nicht `auto`: `auto` deckelt beim Bestandswert des
28
- * **ESC/POS**-Befehls dieses Pakets (4), dieser Weg druckt aber seit jeher
29
- * mit 6. Die Vorgabe nennt den Bestandswert dieses Wegs also ausdruecklich,
30
- * damit ohne Wahl kein Byte anders herauskommt.
29
+ * Bis 0.13 stand hier `mittel`, weil `auto` damals beim ESC/POS-Wert 4
30
+ * deckelte und dieser Weg seit jeher mit 6 druckt. Seit `auto` ueberall 6
31
+ * heisst, ist das derselbe Wert: ohne Wahl kommt kein Byte anders heraus.
31
32
  */
32
33
  qrGroesse?: QrModulGroesse;
33
34
  /** Papierschnitt am Ende, Vorgabe true. */
34
35
  cut?: boolean;
36
+ /** Firmenlogo; ohne Angabe kein Logo. */
37
+ logo?: DruckLogo | null;
38
+ /** "erstellt mit Kasseneck" am Ende. */
39
+ marke?: boolean;
35
40
  }
36
41
  /**
37
42
  * Das XML **samt** dem, was dem QR unterwegs zugestossen ist -- dieselbe
@@ -45,6 +50,8 @@ export interface EposPrintErgebnis {
45
50
  qrAusweich: string | null;
46
51
  }
47
52
  export declare function eposXmlEscape(text: string): string;
53
+ /** Ein Rasterbild als ePOS-`<image>` (einfarbig, Punktmass, Rasterzeilen Base64). */
54
+ export declare function eposBildXml(bild: RasterBild): string;
48
55
  /** Nur das XML; wer die QR-Meldungen braucht, nimmt [eposPrintXmlErgebnis]. */
49
56
  export declare function eposPrintXml(layout: ReceiptLayout, options?: EposPrintXmlOptions): string;
50
57
  /**
@@ -77,6 +84,10 @@ export interface EposDirectOptions {
77
84
  qrBreite?: number;
78
85
  /** Deckel fuer die gerechnete QR-Modulgroesse; siehe [EposPrintXmlOptions.qrGroesse]. */
79
86
  qrGroesse?: QrModulGroesse;
87
+ /** Firmenlogo; ohne Angabe kein Logo. */
88
+ logo?: DruckLogo | null;
89
+ /** "erstellt mit Kasseneck" am Ende. */
90
+ marke?: boolean;
80
91
  timeoutMs?: number;
81
92
  }
82
93
  export interface EposResponse {
@@ -1,8 +1,13 @@
1
- import { QR_DRUCK_PUNKTE, QR_MINDEST_PUNKTE, QR_MODUL_DECKEL, qrGroesseFuer, } from '../printing/index.js';
2
- import { renderReceiptGrid, ZEICHEN_JE_PAPIER } from './grid.js';
1
+ import { QR_DRUCK_PUNKTE, QR_MINDEST_PUNKTE, QR_MODUL_DECKEL, qrGroesseFuer, qrPasstInVersion, rasterZeilenBase64, } from '../printing/index.js';
2
+ import { ZEICHEN_JE_PAPIER } from './grid.js';
3
+ import { blattFuerDruck } from './layout-escpos.js';
3
4
  export function eposXmlEscape(text) {
4
5
  return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;');
5
6
  }
7
+ /** Ein Rasterbild als ePOS-`<image>` (einfarbig, Punktmass, Rasterzeilen Base64). */
8
+ export function eposBildXml(bild) {
9
+ return `<image width="${bild.breite}" height="${bild.hoehe}" color="color_1" mode="mono">${rasterZeilenBase64(bild)}</image>`;
10
+ }
6
11
  const NS = 'http://www.epson-pos.com/schemas/2011/03/epos-print';
7
12
  /** Nur das XML; wer die QR-Meldungen braucht, nimmt [eposPrintXmlErgebnis]. */
8
13
  export function eposPrintXml(layout, options = {}) {
@@ -19,8 +24,9 @@ export function eposPrintXml(layout, options = {}) {
19
24
  * laesst es weg; genau daran fehlte am echten Beleg der QR.
20
25
  */
21
26
  export function eposPrintXmlErgebnis(layout, options = {}) {
22
- const grid = renderReceiptGrid(layout, { zeichen: options.zeichen ?? ZEICHEN_JE_PAPIER[layout.paperSize] });
23
- const deckel = options.qrGroesse ?? 'mittel';
27
+ const zeichen = options.zeichen ?? ZEICHEN_JE_PAPIER[layout.paperSize];
28
+ const deckel = options.qrGroesse ?? 'auto';
29
+ const blatt = blattFuerDruck(layout, { zeichen, logo: options.logo, marke: options.marke, qrGroesse: deckel });
24
30
  const fest = options.qrBreite === undefined
25
31
  ? null
26
32
  : Math.min(16, Math.max(3, Math.floor(options.qrBreite)));
@@ -39,6 +45,12 @@ export function eposPrintXmlErgebnis(layout, options = {}) {
39
45
  return fest;
40
46
  if (nutzlast === '')
41
47
  return QR_MODUL_DECKEL[deckel];
48
+ // Passt der Inhalt in keine QR-Version, gibt es kein Symbol, das der
49
+ // Drucker setzen koennte -- wie beim Blatt (Anteil 0) geht der Beleg ohne QR.
50
+ if (!qrPasstInVersion(nutzlast)) {
51
+ qrFehler = 'QR-Inhalt passt in keine QR-Version -- Beleg ohne QR';
52
+ return null;
53
+ }
42
54
  const mass = qrGroesseFuer({
43
55
  nutzlast,
44
56
  papierbreitePunkte: QR_DRUCK_PUNKTE[layout.paperSize],
@@ -63,32 +75,34 @@ export function eposPrintXmlErgebnis(layout, options = {}) {
63
75
  out.push('<text font="font_a"/>');
64
76
  out.push('<text align="left"/>');
65
77
  out.push('<text width="1" height="1" reverse="false" em="false"/>');
66
- for (const z of grid.lines) {
67
- switch (z.kind) {
68
- case 'space':
69
- out.push('<feed line="1"/>');
78
+ for (const block of blatt.bloecke) {
79
+ switch (block.art) {
80
+ case 'zeile':
81
+ if (block.leer)
82
+ out.push('<feed line="1"/>');
83
+ else if (block.fett) {
84
+ out.push(`<text em="true">${eposXmlEscape(block.text)}&#10;</text>`);
85
+ out.push('<text em="false"/>');
86
+ }
87
+ else
88
+ out.push(`<text>${eposXmlEscape(block.text)}&#10;</text>`);
89
+ break;
90
+ case 'logo':
91
+ if (options.logo) {
92
+ out.push('<text align="center"/>');
93
+ out.push(eposBildXml(options.logo.raster));
94
+ out.push('<text align="left"/>');
95
+ }
70
96
  break;
71
97
  case 'qr': {
72
- const breite = qrBreiteFuer(z.qr ?? '');
98
+ const breite = qrBreiteFuer(block.nutzlast);
73
99
  if (breite === null)
74
100
  break;
75
101
  out.push('<text align="center"/>');
76
- out.push(`<symbol type="qrcode_model_2" level="level_m" width="${breite}" height="0" size="0">${eposXmlEscape(z.qr ?? '')}</symbol>`);
102
+ out.push(`<symbol type="qrcode_model_2" level="level_m" width="${breite}" height="0" size="0">${eposXmlEscape(block.nutzlast)}</symbol>`);
77
103
  out.push('<text align="left"/>');
78
104
  break;
79
105
  }
80
- case 'banner':
81
- out.push(`<text width="1" height="2" reverse="${z.ton === 'warnung' ? 'true' : 'false'}" em="true">${eposXmlEscape(z.text)}&#10;</text>`);
82
- out.push('<text width="1" height="1" reverse="false" em="false"/>');
83
- break;
84
- default:
85
- if (z.bold)
86
- out.push(`<text em="true">${eposXmlEscape(z.text)}&#10;</text>`);
87
- else
88
- out.push(`<text>${eposXmlEscape(z.text)}&#10;</text>`);
89
- if (z.bold)
90
- out.push('<text em="false"/>');
91
- break;
92
106
  }
93
107
  }
94
108
  if (options.cut !== false) {
@@ -208,6 +222,10 @@ export function eposDirectPrint(layout, o, fetchFn = fetch) {
208
222
  xmlOptionen.qrBreite = o.qrBreite;
209
223
  if (o.qrGroesse !== undefined)
210
224
  xmlOptionen.qrGroesse = o.qrGroesse;
225
+ if (o.logo)
226
+ xmlOptionen.logo = o.logo;
227
+ if (o.marke !== undefined)
228
+ xmlOptionen.marke = o.marke;
211
229
  return eposDirectSend(eposPrintXml({ ...layout, paperSize: papier }, xmlOptionen), o, fetchFn);
212
230
  }
213
231
  /** Verbindungstest: leeres Dokument, druckt nichts, liefert den Druckerstatus. */
@@ -27,7 +27,7 @@ export interface GridLine {
27
27
  text: string;
28
28
  kind: GridLineKind;
29
29
  bold: boolean;
30
- /** Bei `banner`: `warnung` = invers/auffaellig, `belegart` = Rahmen. */
30
+ /** Bei `banner` (Rahmen- und Textzeilen): `warnung` = Test/Ausfall, `belegart` = Storno, Nullbeleg … */
31
31
  ton?: 'belegart' | 'warnung';
32
32
  /** Bei `qr`: die Nutzlast. */
33
33
  qr?: string;
@@ -61,10 +61,18 @@ export function renderReceiptGrid(layout, options = {}) {
61
61
  for (const t of wortzeilenText(z.text, zeichen))
62
62
  lines.push({ text: ausrichten(t, zeichen, z.align), kind: 'text', bold: z.bold });
63
63
  break;
64
- case 'banner':
64
+ case 'banner': {
65
+ // Der Rahmen ist Teil des Rasters: eine Zeile '=' ueber die volle Breite
66
+ // davor und danach. So setzt ihn jeder Weg -- Bildschirm, Bon, ePOS, PDF,
67
+ // myPOS -- Zeichen fuer Zeichen gleich. Vorher zeichnete jeder Weg seinen
68
+ // eigenen Rahmen (Rechteck, doppelt hoch, invers, gefuellt).
69
+ const rahmen = '='.repeat(zeichen);
70
+ lines.push({ text: rahmen, kind: 'banner', bold: true, ton: z.ton });
65
71
  for (const t of wortzeilenText(z.text, zeichen))
66
72
  lines.push({ text: ausrichten(t, zeichen, 'center'), kind: 'banner', bold: true, ton: z.ton });
73
+ lines.push({ text: rahmen, kind: 'banner', bold: true, ton: z.ton });
67
74
  break;
75
+ }
68
76
  case 'rule':
69
77
  lines.push({ text: (z.char || '-').charAt(0).repeat(zeichen), kind: 'rule', bold: false });
70
78
  break;
@@ -1,4 +1,6 @@
1
1
  export { type LayoutAlign, type LayoutColumn, type LayoutTextLine, type LayoutColumnsLine, type LayoutRuleLine, type LayoutSpaceLine, type LayoutQrLine, type LayoutLine, type ReceiptLayout, type BuildReceiptLayoutOptions, buildReceiptLayout, receiptSignatureFailed, receiptSignatureIsTest, receiptIsZero, receiptAmountsAreZero, receiptIsSmallBusinessConsistent, receiptZdaText, type Pruefangaben, AKTUELLES_REGELWERK, type LayoutBannerLine, type LayoutRegelwerk, SMALL_BUSINESS_NOTICE, formatCents, } from './layout.js';
2
- export { type EscPosLayoutOptions, type EscPosLayoutErgebnis, type QrPrintMode, escPosLayoutBytes, escPosLayoutErgebnis, } from './layout-escpos.js';
2
+ export { type EscPosLayoutOptions, type EscPosLayoutErgebnis, type QrPrintMode, type DruckLogo, pruefeLogoRaster, escPosLayoutBytes, escPosLayoutErgebnis, } from './layout-escpos.js';
3
3
  export { type GridLine, type GridLineKind, type ReceiptGrid, type RenderReceiptGridOptions, renderReceiptGrid, gridSpaltenBreiten, gridAlsText, ZEICHEN_JE_PAPIER, } from './grid.js';
4
- export { type EposPrintXmlOptions, type EposPrintErgebnis, type EposDirectOptions, type EposResponse, EposConnectionError, eposPrintXml, eposPrintXmlErgebnis, eposXmlEscape, eposServiceUrl, eposSoapEnvelope, eposParseResponse, eposDirectPrint, eposDirectStatus } from './epos.js';
4
+ export { type EposPrintXmlOptions, type EposPrintErgebnis, type EposDirectOptions, type EposResponse, EposConnectionError, eposPrintXml, eposPrintXmlErgebnis, eposXmlEscape, eposBildXml, eposServiceUrl, eposSoapEnvelope, eposParseResponse, eposDirectPrint, eposDirectStatus } from './epos.js';
5
+ export { type LogoStufe, type BlattLogo, type LogoMass, type BlattBlock, type BelegBlatt, type BelegBlattOptionen, LOGO_STUFEN, LOGO_PIXEL_MAX, PUNKTE_JE_ZEICHEN, PUNKTE_JE_ZEILE, MARKE_TEXT, papierFuerZeichen, logoMass, logoPixelZulaessig, logoRasterMass, qrBlattAnteil, belegBlatt, } from './blatt.js';
6
+ export { logoRaster } from './bild.js';
@@ -1,4 +1,6 @@
1
1
  export { buildReceiptLayout, receiptSignatureFailed, receiptSignatureIsTest, receiptIsZero, receiptAmountsAreZero, receiptIsSmallBusinessConsistent, receiptZdaText, AKTUELLES_REGELWERK, SMALL_BUSINESS_NOTICE, formatCents, } from './layout.js';
2
- export { escPosLayoutBytes, escPosLayoutErgebnis, } from './layout-escpos.js';
2
+ export { pruefeLogoRaster, escPosLayoutBytes, escPosLayoutErgebnis, } from './layout-escpos.js';
3
3
  export { renderReceiptGrid, gridSpaltenBreiten, gridAlsText, ZEICHEN_JE_PAPIER, } from './grid.js';
4
- export { EposConnectionError, eposPrintXml, eposPrintXmlErgebnis, eposXmlEscape, eposServiceUrl, eposSoapEnvelope, eposParseResponse, eposDirectPrint, eposDirectStatus } from './epos.js';
4
+ export { EposConnectionError, eposPrintXml, eposPrintXmlErgebnis, eposXmlEscape, eposBildXml, eposServiceUrl, eposSoapEnvelope, eposParseResponse, eposDirectPrint, eposDirectStatus } from './epos.js';
5
+ export { LOGO_STUFEN, LOGO_PIXEL_MAX, PUNKTE_JE_ZEICHEN, PUNKTE_JE_ZEILE, MARKE_TEXT, papierFuerZeichen, logoMass, logoPixelZulaessig, logoRasterMass, qrBlattAnteil, belegBlatt, } from './blatt.js';
6
+ export { logoRaster } from './bild.js';
@@ -1,5 +1,6 @@
1
- import { type PosCodeTable, type PosCutMode, type PosPaperSize, type QrCorrection, type QrMatrix, type QrModulGroesse, type QrSize } from '../printing/index.js';
1
+ import { type PosCodeTable, type PosCutMode, type PosPaperSize, type QrCorrection, type QrMatrix, type QrModulGroesse, type QrSize, type RasterBild } from '../printing/index.js';
2
2
  import type { ReceiptLayout } from './layout.js';
3
+ import { type BelegBlatt, type LogoStufe, type LogoMass } from './blatt.js';
3
4
  /**
4
5
  * Bruecke vom Layout-Modell zu ESC/POS-Bytes — der Bondrucker-Ausgabeweg.
5
6
  *
@@ -28,6 +29,29 @@ import type { ReceiptLayout } from './layout.js';
28
29
  * (`qrMatrix`), weil dieses Paket keine QR-Codes rechnet.
29
30
  */
30
31
  export type QrPrintMode = 'native' | 'nativeModel1' | 'imageRaster';
32
+ /** Firmenlogo fuer den Druck: Stufe, Pixelmass des Originals und das fertige Rasterbild (`logoRaster`). */
33
+ export interface DruckLogo {
34
+ stufe: LogoStufe;
35
+ pxBreite: number;
36
+ pxHoehe: number;
37
+ raster: RasterBild;
38
+ }
39
+ /** Das Rasterbild muss genau so gross sein, wie das Blatt das Logo setzt -- sonst stuende am Bon ein anderes Logo als am Schirm. */
40
+ export declare function pruefeLogoRaster(logo: DruckLogo, mass: LogoMass, zeichen: number): void;
41
+ /**
42
+ * Baut das Blatt fuer einen Druckweg (ESC/POS, ePOS) an EINER Stelle: die
43
+ * Zuordnung `DruckLogo` -> `BlattLogo` und die Groessenpruefung des mitgebrachten
44
+ * Rasterbilds teilen sich beide Wege -- ohne das muesste eine Aenderung daran an
45
+ * zwei Stellen nachgezogen werden. Wirft VOR jeder Ausgabe (also bevor der
46
+ * Aufrufer auch nur ein Byte/Zeichen geschrieben hat), wenn das Rasterbild nicht
47
+ * zur Logo-Stufe passt.
48
+ */
49
+ export declare function blattFuerDruck(layout: ReceiptLayout, optionen: {
50
+ zeichen: number;
51
+ logo?: DruckLogo | null;
52
+ marke?: boolean;
53
+ qrGroesse: QrModulGroesse;
54
+ }): BelegBlatt;
31
55
  export interface EscPosLayoutOptions {
32
56
  /** Papierbreite; Vorgabe ist die des Layouts (dessen Spaltenbreiten daran haengen). */
33
57
  paperSize?: PosPaperSize;
@@ -40,9 +64,9 @@ export interface EscPosLayoutOptions {
40
64
  * dann passt der Aufrufer selbst auf, dass das Symbol aufs Papier geht.
41
65
  */
42
66
  qrSize?: QrSize;
43
- /** Fehlerkorrekturstufe des QR-Codes. */
67
+ /** Fehlerkorrekturstufe des QR-Codes; Vorgabe `M` (wie ePOS, Bildweg und Blatt). */
44
68
  qrCorrection?: QrCorrection;
45
- /** Deckel fuer die gerechnete Modulgroesse; Vorgabe `auto`. */
69
+ /** Deckel fuer die gerechnete Modulgroesse; Vorgabe `auto` (hoechstens 6 Punkte je Modul). */
46
70
  qrGroesse?: QrModulGroesse;
47
71
  /** Druckweg des QR; Vorgabe `native` (der Bestandsweg). */
48
72
  qrModus?: QrPrintMode;
@@ -54,6 +78,10 @@ export interface EscPosLayoutOptions {
54
78
  * aus der QR-Bibliothek, die er ohnehin fuer den Bildschirm benutzt.
55
79
  */
56
80
  qrMatrix?: (nutzlast: string) => QrMatrix;
81
+ /** Firmenlogo; ohne Angabe kein Logo (Bestand). */
82
+ logo?: DruckLogo | null;
83
+ /** "erstellt mit Kasseneck" am Ende (Konto-Flag `kreiseck_logo`). */
84
+ marke?: boolean;
57
85
  }
58
86
  /**
59
87
  * Der Bytestrom **samt** dem, was dem QR unterwegs zugestossen ist.
@@ -1,5 +1,35 @@
1
- import { createEscPosDocument, escPosBytes, escPosCut, escPosFeed, escPosPrintableText, escPosQrCode, escPosQrRaster, escPosReset, escPosText, QR_DRUCK_PUNKTE, qrGroesseFuer, } from '../printing/index.js';
2
- import { renderReceiptGrid, ZEICHEN_JE_PAPIER } from './grid.js';
1
+ import { createEscPosDocument, escPosBytes, escPosCut, escPosFeed, escPosPrintableText, escPosQrCode, escPosQrRaster, escPosRasterBild, escPosReset, escPosText, QR_DRUCK_PUNKTE, qrGroesseFuer, qrPasstInVersion, } from '../printing/index.js';
2
+ import { belegBlatt, logoRasterMass } from './blatt.js';
3
+ import { ZEICHEN_JE_PAPIER } from './grid.js';
4
+ /** Das Rasterbild muss genau so gross sein, wie das Blatt das Logo setzt -- sonst stuende am Bon ein anderes Logo als am Schirm. */
5
+ export function pruefeLogoRaster(logo, mass, zeichen) {
6
+ const soll = logoRasterMass(mass, zeichen);
7
+ if (logo.raster.breite !== soll.breite || logo.raster.hoehe !== soll.hoehe) {
8
+ throw new Error(`Logo-Raster ${logo.raster.breite}x${logo.raster.hoehe} passt nicht zum Blatt (${soll.breite}x${soll.hoehe})`);
9
+ }
10
+ }
11
+ /**
12
+ * Baut das Blatt fuer einen Druckweg (ESC/POS, ePOS) an EINER Stelle: die
13
+ * Zuordnung `DruckLogo` -> `BlattLogo` und die Groessenpruefung des mitgebrachten
14
+ * Rasterbilds teilen sich beide Wege -- ohne das muesste eine Aenderung daran an
15
+ * zwei Stellen nachgezogen werden. Wirft VOR jeder Ausgabe (also bevor der
16
+ * Aufrufer auch nur ein Byte/Zeichen geschrieben hat), wenn das Rasterbild nicht
17
+ * zur Logo-Stufe passt.
18
+ */
19
+ export function blattFuerDruck(layout, optionen) {
20
+ const blattOptionen = {
21
+ zeichen: optionen.zeichen,
22
+ marke: optionen.marke === true,
23
+ qrGroesse: optionen.qrGroesse,
24
+ };
25
+ if (optionen.logo)
26
+ blattOptionen.logo = { stufe: optionen.logo.stufe, pxBreite: optionen.logo.pxBreite, pxHoehe: optionen.logo.pxHoehe };
27
+ const blatt = belegBlatt(layout, blattOptionen);
28
+ const logoBlock = blatt.bloecke.find((b) => b.art === 'logo');
29
+ if (optionen.logo && logoBlock && logoBlock.art === 'logo')
30
+ pruefeLogoRaster(optionen.logo, logoBlock, blatt.zeichen);
31
+ return blatt;
32
+ }
3
33
  /**
4
34
  * Druckbar gemachtes Layout: Texte durch [escPosPrintableText] (Codepage,
5
35
  * "EUR" statt "€", Striche), damit das Raster mit den Zeichen rechnet, die
@@ -68,6 +98,13 @@ export function escPosLayoutErgebnis(layout, options = {}) {
68
98
  * `qrFehler` aus `escPosQrCode`, und der Beleg geht ohne QR hinaus.
69
99
  */
70
100
  const qrZeile = (nutzlast) => {
101
+ // Ein Inhalt, der in keine QR-Version passt, laesst sich weder als Befehl
102
+ // noch als Bild drucken -- beide Wege wuerfen. Der Beleg geht ohne QR
103
+ // hinaus und sagt es ueber `qrFehler`, wie das Blatt (Anteil 0).
104
+ if (nutzlast !== '' && !qrPasstInVersion(nutzlast)) {
105
+ doc.qrFehler = 'QR-Inhalt passt in keine QR-Version -- Beleg ohne QR';
106
+ return;
107
+ }
71
108
  const raster = options.qrMatrix;
72
109
  if (nutzlast !== '' && raster !== undefined) {
73
110
  if (modus === 'imageRaster') {
@@ -94,27 +131,28 @@ export function escPosLayoutErgebnis(layout, options = {}) {
94
131
  }
95
132
  escPosQrCode(doc, nutzlast, qrOptionen);
96
133
  };
97
- const grid = renderReceiptGrid(druckbaresLayout(layout), { zeichen: ZEICHEN_JE_PAPIER[paperSize] });
98
- for (const zeile of grid.lines) {
99
- switch (zeile.kind) {
100
- case 'space':
101
- escPosFeed(doc, 1);
102
- break;
134
+ // `blattFuerDruck` prueft das Logo-Raster bereits VOR der Rueckgabe -- die
135
+ // Schleife unten schreibt darum nie ein Byte auf ein falsch grosses Logo.
136
+ const blatt = blattFuerDruck(druckbaresLayout(layout), {
137
+ zeichen: ZEICHEN_JE_PAPIER[paperSize],
138
+ logo: options.logo,
139
+ marke: options.marke,
140
+ qrGroesse: options.qrGroesse ?? 'auto',
141
+ });
142
+ for (const block of blatt.bloecke) {
143
+ switch (block.art) {
103
144
  case 'qr':
104
- qrZeile(zeile.qr ?? '');
145
+ qrZeile(block.nutzlast);
105
146
  break;
106
- case 'banner': {
107
- // Belegart/Warnung: fett zwischen zwei Volllinien — derselbe Rahmen-
108
- // Stil wie im Beleg-Viewer und im PDF (frueher druckte der Bon invers
109
- // weiss-auf-schwarz und sah damit anders aus als jede Anzeige).
110
- const rahmen = '='.repeat(ZEICHEN_JE_PAPIER[paperSize]);
111
- escPosText(doc, rahmen, { styles: { align: 'left', bold: true } });
112
- escPosText(doc, zeile.text.trimEnd(), { styles: { align: 'left', bold: true, height: 2 } });
113
- escPosText(doc, rahmen, { styles: { align: 'left', bold: true } });
147
+ case 'logo':
148
+ if (options.logo)
149
+ escPosRasterBild(doc, options.logo.raster, { align: 'center' });
114
150
  break;
115
- }
116
- default:
117
- escPosText(doc, zeile.text.trimEnd(), { styles: { align: 'left', bold: zeile.bold } });
151
+ case 'zeile':
152
+ if (block.leer)
153
+ escPosFeed(doc, 1);
154
+ else
155
+ escPosText(doc, block.text.trimEnd(), { styles: { align: 'left', bold: block.fett } });
118
156
  break;
119
157
  }
120
158
  }
@@ -43,7 +43,7 @@
43
43
  * });
44
44
  * ```
45
45
  *
46
- * Mit `sitzung.customToken` meldet sich der Verbraucher bei Firebase an; das
46
+ * Mit `sitzung.customToken` meldet sich der Verbraucher beim Anmeldedienst an; das
47
47
  * daraus entstehende ID-Token und `sitzung.sessionId` ergeben zusammen
48
48
  * `registerUserAuth` — ab da laeuft alles Weitere ueber den ueblichen Weg.
49
49
  */
@@ -43,7 +43,7 @@
43
43
  * });
44
44
  * ```
45
45
  *
46
- * Mit `sitzung.customToken` meldet sich der Verbraucher bei Firebase an; das
46
+ * Mit `sitzung.customToken` meldet sich der Verbraucher beim Anmeldedienst an; das
47
47
  * daraus entstehende ID-Token und `sitzung.sessionId` ergeben zusammen
48
48
  * `registerUserAuth` — ab da laeuft alles Weitere ueber den ueblichen Weg.
49
49
  */
@@ -188,9 +188,9 @@ export interface RegisterUser {
188
188
  /** Ergebnis der PIN-Anmeldung. */
189
189
  export interface RegisterUserSession {
190
190
  /**
191
- * Firebase-Custom-Token. Damit meldet der Verbraucher sich bei Firebase an
191
+ * Custom-Token des Anmeldediensts. Damit meldet der Verbraucher sich dort an
192
192
  * und bekommt das ID-Token, das `registerUserAuth` braucht; dieses Paket
193
- * kennt Firebase nicht.
193
+ * kennt den Anmeldedienst nicht.
194
194
  */
195
195
  customToken: string;
196
196
  /** Laufende Sitzung — Kopfzeile `register-session` jedes weiteren Aufrufs. */
@@ -0,0 +1,159 @@
1
+ {
2
+ "zeichen": 32,
3
+ "bloecke": [
4
+ {
5
+ "art": "logo",
6
+ "breiteAnteil": 0.62,
7
+ "hoeheZeilen": 3.968
8
+ },
9
+ {
10
+ "art": "zeile",
11
+ "text": " ",
12
+ "fett": false,
13
+ "leer": true
14
+ },
15
+ {
16
+ "art": "zeile",
17
+ "text": " Bäckerei Muster ",
18
+ "fett": true,
19
+ "leer": false
20
+ },
21
+ {
22
+ "art": "zeile",
23
+ "text": " Hauptplatz 1 ",
24
+ "fett": false,
25
+ "leer": false
26
+ },
27
+ {
28
+ "art": "zeile",
29
+ "text": " 4830 Hallstatt ",
30
+ "fett": false,
31
+ "leer": false
32
+ },
33
+ {
34
+ "art": "zeile",
35
+ "text": " ATU12345678 ",
36
+ "fett": false,
37
+ "leer": false
38
+ },
39
+ {
40
+ "art": "zeile",
41
+ "text": " +43 6134 12345 ",
42
+ "fett": false,
43
+ "leer": false
44
+ },
45
+ {
46
+ "art": "zeile",
47
+ "text": " ",
48
+ "fett": false,
49
+ "leer": true
50
+ },
51
+ {
52
+ "art": "zeile",
53
+ "text": "Datum: 13.08.2026 10:15:30",
54
+ "fett": false,
55
+ "leer": false
56
+ },
57
+ {
58
+ "art": "zeile",
59
+ "text": "Kassen-ID: KASSE1",
60
+ "fett": false,
61
+ "leer": false
62
+ },
63
+ {
64
+ "art": "zeile",
65
+ "text": "Beleg-ID: AT0-KASSE1-66",
66
+ "fett": false,
67
+ "leer": false
68
+ },
69
+ {
70
+ "art": "zeile",
71
+ "text": " ",
72
+ "fett": false,
73
+ "leer": true
74
+ },
75
+ {
76
+ "art": "zeile",
77
+ "text": "1 x Behandlung 120,00 A",
78
+ "fett": false,
79
+ "leer": false
80
+ },
81
+ {
82
+ "art": "zeile",
83
+ "text": " ",
84
+ "fett": false,
85
+ "leer": true
86
+ },
87
+ {
88
+ "art": "zeile",
89
+ "text": "MwSt% MwSt Netto Brutto",
90
+ "fett": false,
91
+ "leer": false
92
+ },
93
+ {
94
+ "art": "zeile",
95
+ "text": "A 20% 20,00 100,00 120,00",
96
+ "fett": false,
97
+ "leer": false
98
+ },
99
+ {
100
+ "art": "zeile",
101
+ "text": "--------------------------------",
102
+ "fett": false,
103
+ "leer": false
104
+ },
105
+ {
106
+ "art": "zeile",
107
+ "text": "Gesamt: 120,00 €",
108
+ "fett": false,
109
+ "leer": false
110
+ },
111
+ {
112
+ "art": "zeile",
113
+ "text": "Zahlungsart: Kartenzahlung",
114
+ "fett": false,
115
+ "leer": false
116
+ },
117
+ {
118
+ "art": "zeile",
119
+ "text": " ",
120
+ "fett": false,
121
+ "leer": true
122
+ },
123
+ {
124
+ "art": "qr",
125
+ "nutzlast": "_R1-AT1_KASSE1_AT0-KASSE1-66_2026-08-13T10:15:30_120,00_0,00_0,00_0,00_0,00_UMSATZ_VORGAENGER_6F0404F0_SIGNATUR",
126
+ "breiteAnteil": 0.828125
127
+ },
128
+ {
129
+ "art": "zeile",
130
+ "text": " ",
131
+ "fett": false,
132
+ "leer": true
133
+ },
134
+ {
135
+ "art": "zeile",
136
+ "text": " Vielen Dank für Ihren Einkauf ",
137
+ "fett": false,
138
+ "leer": false
139
+ },
140
+ {
141
+ "art": "zeile",
142
+ "text": " www.baeckerei-muster.at ",
143
+ "fett": false,
144
+ "leer": false
145
+ },
146
+ {
147
+ "art": "zeile",
148
+ "text": " ",
149
+ "fett": false,
150
+ "leer": true
151
+ },
152
+ {
153
+ "art": "zeile",
154
+ "text": " erstellt mit Kasseneck ",
155
+ "fett": false,
156
+ "leer": false
157
+ }
158
+ ]
159
+ }