@kreiseck/kasseneck-api 0.6.9 → 0.6.45

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 (103) hide show
  1. package/README.md +21 -0
  2. package/dist/cjs/client/aufrufe.d.ts +21 -0
  3. package/dist/cjs/client/aufrufe.js +34 -0
  4. package/dist/cjs/client/cashregisters.d.ts +2 -2
  5. package/dist/cjs/client/index.d.ts +2 -1
  6. package/dist/cjs/client/index.js +3 -1
  7. package/dist/cjs/client/receipts.d.ts +31 -12
  8. package/dist/cjs/client/receipts.js +51 -0
  9. package/dist/cjs/client/reports.d.ts +3 -3
  10. package/dist/cjs/client/status.d.ts +3 -3
  11. package/dist/cjs/index.d.ts +2 -2
  12. package/dist/cjs/index.js +4 -2
  13. package/dist/cjs/kasse/artikel.d.ts +3 -3
  14. package/dist/cjs/kasse/client.d.ts +4 -4
  15. package/dist/cjs/kasse/drucker.d.ts +4 -4
  16. package/dist/cjs/kasse/index.d.ts +1 -1
  17. package/dist/cjs/kasse/index.js +30 -1
  18. package/dist/cjs/kasse/settings.d.ts +113 -26
  19. package/dist/cjs/kasse/settings.js +91 -9
  20. package/dist/cjs/models/index.d.ts +1 -1
  21. package/dist/cjs/models/index.js +3 -1
  22. package/dist/cjs/models/receipt-item.d.ts +41 -0
  23. package/dist/cjs/models/receipt-item.js +41 -1
  24. package/dist/cjs/models/receipt.d.ts +7 -0
  25. package/dist/cjs/models/receipt.js +2 -0
  26. package/dist/cjs/payments/hobex.d.ts +3 -3
  27. package/dist/cjs/payments/stripe.d.ts +3 -3
  28. package/dist/cjs/printing/escpos.js +7 -0
  29. package/dist/cjs/react/index.js +11 -1
  30. package/dist/cjs/receipt/discount.js +1 -1
  31. package/dist/cjs/receipt/layout-escpos.js +9 -4
  32. package/dist/cjs/receipt/layout.d.ts +6 -3
  33. package/dist/cjs/receipt/layout.js +166 -14
  34. package/dist/cjs/register/index.d.ts +1 -1
  35. package/dist/cjs/register/index.js +2 -1
  36. package/dist/cjs/register/pairing.d.ts +17 -0
  37. package/dist/cjs/register/pairing.js +28 -3
  38. package/dist/cjs/register/session.d.ts +3 -3
  39. package/dist/esm/client/aufrufe.d.ts +21 -0
  40. package/dist/esm/client/aufrufe.js +31 -0
  41. package/dist/esm/client/cashregisters.d.ts +2 -2
  42. package/dist/esm/client/index.d.ts +2 -1
  43. package/dist/esm/client/index.js +1 -0
  44. package/dist/esm/client/receipts.d.ts +31 -12
  45. package/dist/esm/client/receipts.js +51 -0
  46. package/dist/esm/client/reports.d.ts +3 -3
  47. package/dist/esm/client/status.d.ts +3 -3
  48. package/dist/esm/index.d.ts +2 -2
  49. package/dist/esm/index.js +2 -2
  50. package/dist/esm/kasse/artikel.d.ts +3 -3
  51. package/dist/esm/kasse/client.d.ts +4 -4
  52. package/dist/esm/kasse/drucker.d.ts +4 -4
  53. package/dist/esm/kasse/index.d.ts +1 -1
  54. package/dist/esm/kasse/index.js +3 -1
  55. package/dist/esm/kasse/settings.d.ts +113 -26
  56. package/dist/esm/kasse/settings.js +90 -8
  57. package/dist/esm/models/index.d.ts +1 -1
  58. package/dist/esm/models/index.js +1 -1
  59. package/dist/esm/models/receipt-item.d.ts +41 -0
  60. package/dist/esm/models/receipt-item.js +39 -1
  61. package/dist/esm/models/receipt.d.ts +7 -0
  62. package/dist/esm/models/receipt.js +2 -0
  63. package/dist/esm/payments/hobex.d.ts +3 -3
  64. package/dist/esm/payments/stripe.d.ts +3 -3
  65. package/dist/esm/printing/escpos.js +7 -0
  66. package/dist/esm/react/index.js +11 -1
  67. package/dist/esm/receipt/discount.js +1 -1
  68. package/dist/esm/receipt/layout-escpos.js +9 -4
  69. package/dist/esm/receipt/layout.d.ts +6 -3
  70. package/dist/esm/receipt/layout.js +167 -15
  71. package/dist/esm/register/index.d.ts +1 -1
  72. package/dist/esm/register/index.js +1 -1
  73. package/dist/esm/register/pairing.d.ts +17 -0
  74. package/dist/esm/register/pairing.js +27 -3
  75. package/dist/esm/register/session.d.ts +3 -3
  76. package/fixtures/belege/rabatt-chef-trinkgeld.json +89 -0
  77. package/fixtures/belege/rabatt-einfach.json +50 -0
  78. package/fixtures/belege/rabatt-trinkgeld.json +75 -0
  79. package/fixtures/belege/rabatt-wertgutschein.json +70 -0
  80. package/fixtures/belege/rabattzeilen.json +5 -3
  81. package/fixtures/belege/storno-rabatt.json +68 -0
  82. package/fixtures/erwartet/rabatt-chef-trinkgeld.grid32.txt +31 -0
  83. package/fixtures/erwartet/rabatt-chef-trinkgeld.grid48.txt +30 -0
  84. package/fixtures/erwartet/rabatt-chef-trinkgeld.lines.json +317 -0
  85. package/fixtures/erwartet/rabatt-einfach.grid32.txt +26 -0
  86. package/fixtures/erwartet/rabatt-einfach.grid48.txt +25 -0
  87. package/fixtures/erwartet/rabatt-einfach.lines.json +250 -0
  88. package/fixtures/erwartet/rabatt-trinkgeld.grid32.txt +31 -0
  89. package/fixtures/erwartet/rabatt-trinkgeld.grid48.txt +30 -0
  90. package/fixtures/erwartet/rabatt-trinkgeld.lines.json +336 -0
  91. package/fixtures/erwartet/rabatt-wertgutschein.grid32.txt +32 -0
  92. package/fixtures/erwartet/rabatt-wertgutschein.grid48.txt +31 -0
  93. package/fixtures/erwartet/rabatt-wertgutschein.lines.json +330 -0
  94. package/fixtures/erwartet/rabattzeilen.grid32.txt +4 -2
  95. package/fixtures/erwartet/rabattzeilen.grid48.txt +4 -2
  96. package/fixtures/erwartet/rabattzeilen.lines.json +18 -8
  97. package/fixtures/erwartet/storno-rabatt.grid32.txt +34 -0
  98. package/fixtures/erwartet/storno-rabatt.grid48.txt +32 -0
  99. package/fixtures/erwartet/storno-rabatt.lines.json +317 -0
  100. package/fixtures/kasse-settings-standard.json +44 -5
  101. package/fixtures/manifest.json +34 -4
  102. package/fixtures/oberflaeche.json +216 -0
  103. package/package.json +3 -2
@@ -27,7 +27,36 @@ export interface ReceiptItem {
27
27
  quantity: number;
28
28
  vat: VatRate | number;
29
29
  priceCents: number;
30
+ /**
31
+ * Trinkgeld-Position (vom Backend aus dem Parameter `tip` erzeugt, siehe
32
+ * [ReceiptCommonOptions.tip]) oder Rabatt-Position (`verteileRabatt`).
33
+ * Mitarbeiter-Trinkgeld ist Durchlaeufer (0 %, kein Umsatz),
34
+ * Inhaber-Trinkgeld (`recipient.owner`) Umsatz. Rabatt-Positionen sind
35
+ * negative Entgeltminderungen je Steuersatz; die Kennzeichnung steuert nur
36
+ * die Bon-Darstellung (Summenzeile mit Aufteilung), nie die Beträge.
37
+ */
38
+ kind?: 'tip' | 'discount';
39
+ /** Empfaenger der Trinkgeld-Position; `null` = nicht zugeordnet. */
40
+ recipient?: TipRecipient | null;
41
+ /** Zahlart der Trinkgeld-Position (kann von der des Belegs abweichen). */
42
+ paymentMethod?: string;
43
+ /**
44
+ * Artikel-Verweis (Artikelstamm) — Grundlage der Erloesgruppen-Zuordnung
45
+ * im Bericht. Optional; Handeingaben haben keinen.
46
+ */
47
+ articleId?: string;
30
48
  }
49
+ /** Empfaenger einer Trinkgeld-Position (Kassen-Benutzer, Snapshot des Namens). */
50
+ export interface TipRecipient {
51
+ registerUserId: string;
52
+ name: string;
53
+ /** true: Inhaber — das Trinkgeld ist Umsatz mit USt. */
54
+ owner?: boolean;
55
+ }
56
+ /** Trinkgeld-Position? Die eine Erkennungsstelle — niemand prueft `kind` selbst. */
57
+ export declare function isTipItem(item: ReceiptItem | ReceiptItemPayloadRead): boolean;
58
+ /** Rabatt-Position? Die eine Erkennungsstelle — niemand prueft `kind` selbst. */
59
+ export declare function isDiscountItem(item: ReceiptItem | ReceiptItemPayloadRead): boolean;
31
60
  /**
32
61
  * Nutzlast-Form, die dieses Paket **schreibt** (v2):
33
62
  * `{ name, quantity, unitPriceCents, vatRate }`.
@@ -37,6 +66,12 @@ export interface ReceiptItemPayload {
37
66
  quantity: number;
38
67
  unitPriceCents: number;
39
68
  vatRate: number;
69
+ /** Trinkgeld-/Rabatt-Kennzeichnung, siehe [ReceiptItem.kind]. */
70
+ kind?: 'tip' | 'discount';
71
+ recipient?: TipRecipient | null;
72
+ paymentMethod?: string;
73
+ /** Artikel-Verweis, siehe [ReceiptItem.articleId]. */
74
+ articleId?: string;
40
75
  }
41
76
  /**
42
77
  * Nutzlast-Form, die dieses Paket **liest** — v2 und v1 nebeneinander, alles
@@ -54,6 +89,10 @@ export interface ReceiptItemPayload {
54
89
  */
55
90
  export interface ReceiptItemPayloadRead {
56
91
  name?: string | null;
92
+ /** Trinkgeld-Position (Backend), siehe [ReceiptItem.kind]. */
93
+ kind?: string | null;
94
+ recipient?: TipRecipient | null;
95
+ paymentMethod?: string | null;
57
96
  /** v2: Menge */
58
97
  quantity?: number | null;
59
98
  /** v2: Einzelpreis in Cent */
@@ -68,6 +107,8 @@ export interface ReceiptItemPayloadRead {
68
107
  priceOne?: number | null;
69
108
  /** v1: Steuersatz */
70
109
  vat?: number | null;
110
+ /** Artikel-Verweis, siehe [ReceiptItem.articleId]. */
111
+ articleId?: string | null;
71
112
  }
72
113
  export declare function toReceiptItemPayload(item: ReceiptItem): ReceiptItemPayload;
73
114
  /**
@@ -1,16 +1,41 @@
1
1
  import { VatRate } from '../enums/index.js';
2
2
  import { readVatRateByRate, requireVatRateByRate } from './enum-payload.js';
3
+ /** Trinkgeld-Position? Die eine Erkennungsstelle — niemand prueft `kind` selbst. */
4
+ export function isTipItem(item) {
5
+ return item != null && item.kind === 'tip';
6
+ }
7
+ /** Rabatt-Position? Die eine Erkennungsstelle — niemand prueft `kind` selbst. */
8
+ export function isDiscountItem(item) {
9
+ return item != null && item.kind === 'discount';
10
+ }
3
11
  export function toReceiptItemPayload(item) {
4
12
  // Schreibpfad bleibt streng: ein unaufgeloester (unbekannter) Steuersatz
5
13
  // darf nicht unbesehen wieder hinausgehen.
6
14
  const vat = typeof item.vat === 'number' ? requireVatRateByRate(item.vat) : item.vat;
7
15
  pruefeMenge(item.quantity);
8
- return {
16
+ const nutzlast = {
9
17
  name: item.name,
10
18
  quantity: item.quantity,
11
19
  unitPriceCents: item.priceCents,
12
20
  vatRate: vat.rate,
13
21
  };
22
+ // Trinkgeld-Kennzeichnung reist mit — sonst wuerde ein Storno dieser
23
+ // Position am Backend als Warenzeile ankommen und aus den Trinkgeld-
24
+ // Aggregaten nicht wieder herausgerechnet.
25
+ if (item.kind === 'tip') {
26
+ nutzlast.kind = 'tip';
27
+ nutzlast.recipient = item.recipient ?? null;
28
+ if (item.paymentMethod != null)
29
+ nutzlast.paymentMethod = item.paymentMethod;
30
+ }
31
+ // Rabatt-Kennzeichnung reist mit — sonst kaeme ein Storno dieser Position
32
+ // am Bon wieder als gewoehnliche Warenzeile mit "1 x" an.
33
+ if (item.kind === 'discount')
34
+ nutzlast.kind = 'discount';
35
+ // Artikel-Verweis reist mit (Erloesgruppen im Bericht); leere Werte nicht.
36
+ if (item.articleId != null && item.articleId !== '')
37
+ nutzlast.articleId = item.articleId;
38
+ return nutzlast;
14
39
  }
15
40
  /**
16
41
  * Liest beide Nutzlast-Formen (siehe [ReceiptItemPayloadRead]). Reihenfolge
@@ -39,6 +64,19 @@ export function fromReceiptItemPayload(payload) {
39
64
  // der Fall entsteht also nur bei einer kaputten Nutzlast).
40
65
  vat: satz != null ? readVatRateByRate(satz) : VatRate.vat0,
41
66
  priceCents: cents != null ? Math.round(cents) : euro != null ? Math.round(euro * 100) : 0,
67
+ // Trinkgeld-Felder nur, wenn es wirklich eine Tip-Position ist: eine
68
+ // Warenzeile bleibt so schlank wie bisher (Golden-Belege, deepEqual).
69
+ ...(payload.kind === 'tip'
70
+ ? {
71
+ kind: 'tip',
72
+ recipient: payload.recipient ?? null,
73
+ ...(payload.paymentMethod != null ? { paymentMethod: payload.paymentMethod } : {}),
74
+ }
75
+ : payload.kind === 'discount'
76
+ ? { kind: 'discount' }
77
+ : {}),
78
+ // Artikel-Verweis erhalten (Storno-Spiegelung schreibt ihn wieder mit).
79
+ ...(typeof payload.articleId === 'string' && payload.articleId !== '' ? { articleId: payload.articleId } : {}),
42
80
  };
43
81
  }
44
82
  /** Erster brauchbarer Zahlenwert der Reihe; `null`/`undefined`/NaN zaehlen nicht. */
@@ -41,6 +41,11 @@ export interface Receipt {
41
41
  legalMessage: string[];
42
42
  signatureSuccess?: boolean;
43
43
  customProjectId?: string;
44
+ /**
45
+ * Summe aller Trinkgeld-Positionen in Cent (Storno negativ) — abgeleitetes
46
+ * Lesefeld des Backends, nur vorhanden, wenn der Beleg Trinkgeld traegt.
47
+ */
48
+ tipCents?: number;
44
49
  /** Nur am Storno-Beleg: das stornierte Original. */
45
50
  cancellationOf?: CancellationOf;
46
51
  /** Nur am Storno-Beleg: Grund-Code aus [CANCELLATION_REASONS]. */
@@ -71,6 +76,8 @@ export interface ReceiptPayload {
71
76
  legalMessage: string;
72
77
  signatureSuccess: boolean | null;
73
78
  customProjectId: string | null;
79
+ /** Nur bei Belegen mit Trinkgeld (siehe [Receipt.tipCents]). */
80
+ tipCents?: number | null;
74
81
  }
75
82
  /**
76
83
  * Nutzlast-Form, die dieses Paket **liest**. Unterscheidet sich von
@@ -42,6 +42,7 @@ export function toReceiptPayload(receipt) {
42
42
  legalMessage: receipt.legalMessage.join('\n'),
43
43
  signatureSuccess: receipt.signatureSuccess ?? null,
44
44
  customProjectId: receipt.customProjectId ?? null,
45
+ ...(receipt.tipCents != null ? { tipCents: receipt.tipCents } : {}),
45
46
  };
46
47
  }
47
48
  // Nullbelege haben keine Positionen -> items kann fehlen/null sein (siehe Dart-Vorbild).
@@ -68,6 +69,7 @@ export function fromReceiptPayload(payload) {
68
69
  legalMessage: leereZeile(payload.legalMessage),
69
70
  signatureSuccess: payload.signatureSuccess ?? undefined,
70
71
  customProjectId: payload.customProjectId ?? undefined,
72
+ ...(typeof payload.tipCents === 'number' ? { tipCents: payload.tipCents } : {}),
71
73
  ...(payload.cancellationOf ? { cancellationOf: { receiptId: payload.cancellationOf.receiptId, fullReceiptId: payload.cancellationOf.fullReceiptId ?? null } } : {}),
72
74
  ...(payload.cancellationReason ? { cancellationReason: payload.cancellationReason } : {}),
73
75
  ...(istZeroKind(payload.zeroKind) ? { zeroKind: payload.zeroKind } : {}),
@@ -1,5 +1,5 @@
1
1
  import { type HobexReceipt } from '../models/index.js';
2
- import type { KasseneckTransport } from '../client/transport.js';
2
+ import type { InternerTransport } from '../client/aufrufe.js';
3
3
  /**
4
4
  * Cent -> Euro. **Die einzige Umrechnungsstelle dieses Moduls.** Betraege
5
5
  * werden im ganzen Paket in Cent gefuehrt (exakte Ganzzahl-Arithmetik); die
@@ -46,7 +46,7 @@ export interface HobexTransactionIdOptions {
46
46
  * **Der Endpunkt heisst `hobexPayApi`** — mit Suffix; ohne ihn gibt es ihn
47
47
  * nicht.
48
48
  */
49
- export declare function hobexPay(rufen: KasseneckTransport, options: HobexPayOptions): Promise<HobexReceipt>;
49
+ export declare function hobexPay(rufen: InternerTransport, options: HobexPayOptions): Promise<HobexReceipt>;
50
50
  /**
51
51
  * Erstattet eine zuvor ueber die Hobex-Cloud getaetigte Zahlung. Das Backend
52
52
  * storniert zuerst (Void, solange der Tagesabschluss aussteht) und erstattet
@@ -61,7 +61,7 @@ export declare function hobexPay(rufen: KasseneckTransport, options: HobexPayOpt
61
61
  *
62
62
  * **Der Endpunkt heisst `hobexRefundApi`** — mit Suffix.
63
63
  */
64
- export declare function hobexRefund(rufen: KasseneckTransport, options: HobexRefundOptions): Promise<void>;
64
+ export declare function hobexRefund(rufen: InternerTransport, options: HobexRefundOptions): Promise<void>;
65
65
  /**
66
66
  * Erzeugt eine neue Hobex-Transaktionskennung: 19 Ziffern aus Zeitanteil und
67
67
  * Zufall (Zwilling von `newHobexTransactionId` im Vorbild, Zeile 522).
@@ -1,6 +1,6 @@
1
1
  import { StripeLinkMode, type StripeLinkModeKey } from '../enums/index.js';
2
2
  import { type ReceiptItem, type StripeUrlSession } from '../models/index.js';
3
- import type { KasseneckTransport } from '../client/transport.js';
3
+ import type { InternerTransport } from '../client/aufrufe.js';
4
4
  export interface CreateStripeLinkOptions {
5
5
  /** Positionen des Zahlungslinks; mindestens eine, alle gueltig. */
6
6
  items: ReceiptItem[];
@@ -50,7 +50,7 @@ export interface StripeCaptureResult {
50
50
  * Methodenname und Endpunktname fallen wie schon beim Monatsbericht
51
51
  * auseinander (siehe client/reports.ts).
52
52
  */
53
- export declare function createStripeLink(rufen: KasseneckTransport, options: CreateStripeLinkOptions): Promise<StripeUrlSession>;
53
+ export declare function createStripeLink(rufen: InternerTransport, options: CreateStripeLinkOptions): Promise<StripeUrlSession>;
54
54
  /**
55
55
  * Zieht die zuvor nur reservierte Zahlung einer `authorization`-Sitzung ein.
56
56
  *
@@ -58,4 +58,4 @@ export declare function createStripeLink(rufen: KasseneckTransport, options: Cre
58
58
  * das Vorbild und so das Backend. Der naheliegende Singular waere ein fehlender
59
59
  * Pflichtparameter.
60
60
  */
61
- export declare function stripeCaptureIntent(rufen: KasseneckTransport, stripeSessionId: string): Promise<StripeCaptureResult>;
61
+ export declare function stripeCaptureIntent(rufen: InternerTransport, stripeSessionId: string): Promise<StripeCaptureResult>;
@@ -613,4 +613,11 @@ export function escPosQrCode(doc, text, options = {}) {
613
613
  if (options.correction !== undefined)
614
614
  qrOptionen.correction = options.correction;
615
615
  anhaengen(doc, qrCodeBytes(text, qrOptionen));
616
+ // Ausrichtung SOFORT zuruecksetzen: `ESC a` gilt nur am Zeilenanfang. Die
617
+ // Spalten-Positionierung (`ESC $`) schickt ihren Reset sonst mitten in der
618
+ // Zeile — Epson ignoriert ihn, und alles nach dem QR rueckte nach rechts
619
+ // (belegt am TM-T20, 2026-08-20).
620
+ if ((options.align ?? 'center') !== 'left') {
621
+ escPosSetStyles(doc, { align: 'left' });
622
+ }
616
623
  }
@@ -16,7 +16,12 @@ export function ReceiptLayoutView({ layout, className, renderQr, qrVerdeckt = fa
16
16
  */
17
17
  export function QrVerdeckt({ data, text = 'Antippen zum Anzeigen', children }) {
18
18
  const [offen, setOffen] = useState(false);
19
- return (_jsxs("button", { type: "button", className: offen ? 'keck-receipt-qr-toggle keck-receipt-qr-toggle--offen' : 'keck-receipt-qr-toggle', "aria-pressed": offen, "aria-label": offen ? 'QR-Code verdecken' : 'QR-Code anzeigen', "data-qr": data, onClick: () => setOffen((o) => !o), style: { position: 'relative', display: 'inline-block', border: 'none', background: 'transparent', padding: 0, cursor: 'pointer' }, children: [_jsx("span", { style: { display: 'inline-block', filter: offen ? undefined : 'blur(6px)', transition: 'filter .15s ease' }, children: children }), !offen && (_jsx("span", { className: "keck-receipt-qr-toggle-text", style: { position: 'absolute', inset: 0, display: 'grid', placeItems: 'center', fontSize: '0.75em', fontWeight: 700 }, children: text }))] }));
19
+ return (_jsxs("button", { type: "button", className: offen ? 'keck-receipt-qr-toggle keck-receipt-qr-toggle--offen' : 'keck-receipt-qr-toggle', "aria-pressed": offen, "aria-label": offen ? 'QR-Code verdecken' : text, "data-qr": data, onClick: () => setOffen((o) => !o), style: { position: 'relative', display: 'inline-block', border: 'none', background: 'transparent', padding: 0, cursor: 'pointer' }, children: [_jsx("span", { style: { display: 'inline-block', filter: offen ? undefined : 'blur(3px)', transition: 'filter .15s ease' }, children: children }), !offen && (
20
+ // Tipp-Finger statt Textzeile: versteht jeder, verdeckt fast nichts.
21
+ // Der Text bleibt als aria-label fuer Hilfsmittel. Einfarbige
22
+ // Strichzeichnung statt Emoji — Emojis rendern je Plattform anders
23
+ // und stechen farblich aus dem Beleg heraus.
24
+ _jsx("span", { "aria-hidden": "true", className: "keck-receipt-qr-toggle-text", style: { position: 'absolute', inset: 0, display: 'grid', placeItems: 'center' }, children: _jsx("span", { style: { display: 'grid', placeItems: 'center', width: '2.2em', height: '2.2em', borderRadius: '50%', background: 'rgba(255,255,255,.92)', boxShadow: '0 1px 4px rgba(0,0,0,.25)', color: 'rgba(0,0,0,.72)' }, children: _jsxs("svg", { className: "keck-receipt-qr-toggle-finger", width: "1.25em", height: "1.25em", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M10 9.5V4a2 2 0 0 0-2-2a2 2 0 0 0-2 2v10" }), _jsx("path", { d: "M14 10V9a2 2 0 0 0-2-2a2 2 0 0 0-2 2v1" }), _jsx("path", { d: "M18 11v-1a2 2 0 0 0-2-2a2 2 0 0 0-2 2" }), _jsx("path", { d: "M18 11a2 2 0 1 1 4 0v3a8 8 0 0 1-8 8h-2c-2.8 0-4.5-.86-5.99-2.34l-3.6-3.6a2 2 0 0 1 2.83-2.82L7 15" })] }) }) }))] }));
20
25
  }
21
26
  function Zeile({ zeile, renderQr, qrVerdeckt, qrVerdecktText }) {
22
27
  switch (zeile.kind) {
@@ -28,6 +33,11 @@ function Zeile({ zeile, renderQr, qrVerdeckt, qrVerdecktText }) {
28
33
  // so bleibt die Aufteilung auf allen Ausgabewegen dieselbe.
29
34
  flex: `0 0 ${((spalte.width / 12) * 100).toFixed(4)}%`,
30
35
  textAlign: AUSRICHTUNG[spalte.align],
36
+ // minWidth 0: Flex-Zellen haben sonst min-content-Breite --
37
+ // ein langes Wort ohne Leerzeichen (Hex-Seriennummer der
38
+ // Signaturkarte) wuerde die Zelle sprengen und abgeschnitten,
39
+ // statt per break-word umzubrechen.
40
+ minWidth: 0,
31
41
  overflowWrap: 'break-word',
32
42
  }, children: spalte.text }, index))) }));
33
43
  case 'rule':
@@ -46,6 +46,6 @@ export function verteileRabatt(positionen, rabattCents, name = 'Rabatt') {
46
46
  }
47
47
  }
48
48
  return liste
49
- .map((g, i) => ({ name, quantity: 1, vat: g.vat, priceCents: -anteile[i] }))
49
+ .map((g, i) => ({ name, quantity: 1, vat: g.vat, priceCents: -anteile[i], kind: 'discount' }))
50
50
  .filter((z) => z.priceCents < 0);
51
51
  }
@@ -47,11 +47,16 @@ export function escPosLayoutBytes(layout, options = {}) {
47
47
  case 'qr':
48
48
  escPosQrCode(doc, zeile.qr ?? '', qrOptionen);
49
49
  break;
50
- case 'banner':
51
- // Belegart/Warnung: fett, doppelte Hoehe; Warnungen zusaetzlich invers.
52
- // Der Text ist bereits zentriert aufgefuellt (Raster).
53
- escPosText(doc, zeile.text.trimEnd(), { styles: { align: 'left', bold: true, height: 2, reverse: zeile.ton === 'warnung' } });
50
+ case 'banner': {
51
+ // Belegart/Warnung: fett zwischen zwei Volllinien derselbe Rahmen-
52
+ // Stil wie im Beleg-Viewer und im PDF (frueher druckte der Bon invers
53
+ // weiss-auf-schwarz und sah damit anders aus als jede Anzeige).
54
+ const rahmen = '='.repeat(ZEICHEN_JE_PAPIER[paperSize]);
55
+ escPosText(doc, rahmen, { styles: { align: 'left', bold: true } });
56
+ escPosText(doc, zeile.text.trimEnd(), { styles: { align: 'left', bold: true, height: 2 } });
57
+ escPosText(doc, rahmen, { styles: { align: 'left', bold: true } });
54
58
  break;
59
+ }
55
60
  default:
56
61
  escPosText(doc, zeile.text.trimEnd(), { styles: { align: 'left', bold: zeile.bold } });
57
62
  break;
@@ -21,9 +21,12 @@ import type { PosPaperSize } from '../printing/escpos.js';
21
21
  * stuende auch auf dem Bildschirm „EUR".
22
22
  *
23
23
  * Bewusst **nicht** enthalten: Firmenlogo und Rasterbilder (brauchen
24
- * Bildverarbeitung, die im Browser anders aussieht als in Node), PDF-Erzeugung,
25
- * Druckeransteuerung und die anbieterspezifischen Kartenzahlungsbloecke des
26
- * Vorbilds (GP Tom, Hobex, SumUp, myPOS, Stripe).
24
+ * Bildverarbeitung, die im Browser anders aussieht als in Node), PDF-Erzeugung
25
+ * und Druckeransteuerung. Von den anbieterspezifischen Kartenzahlungsbloecken
26
+ * des Vorbilds (GP Tom, SumUp, myPOS, Stripe) ist nur der **Hobex-Block**
27
+ * enthalten: die Browser-Kasse bedient das HPS-Terminal selbst (via Kasseneck
28
+ * Connect) und druckt den Bon aus genau diesem Modell — die uebrigen Anbieter
29
+ * drucken in ihren eigenen Apps.
27
30
  */
28
31
  /** Ausrichtung einer Zeile bzw. einer Spalte. */
29
32
  export type LayoutAlign = 'left' | 'center' | 'right';
@@ -1,5 +1,5 @@
1
1
  import { VatRate, VoucherAction, VoucherType } from '../enums/index.js';
2
- import { CANCELLATION_REASONS, isCancellationReason, receiptCompanyTaxInfo, receiptItemTotalCents, receiptSubSumCents, receiptSumCents, voucherIsValid, } from '../models/index.js';
2
+ import { CANCELLATION_REASONS, isCancellationReason, receiptCompanyTaxInfo, receiptItemTotalCents, isDiscountItem, isTipItem, receiptSubSumCents, receiptSumCents, voucherIsValid, } from '../models/index.js';
3
3
  import { parseServerTimeStamp, toViennaWallClock } from '../vienna-time.js';
4
4
  import { KasseneckValidationError } from '../client/errors.js';
5
5
  export const AKTUELLES_REGELWERK = 2;
@@ -45,6 +45,50 @@ function belegZeit(timeStamp) {
45
45
  * `typeof null === 'object'` ist, ergab das beim Lesen von `.label` einen
46
46
  * nackten TypeError.
47
47
  */
48
+ /**
49
+ * Kartenzahlungsblock fuer Hobex (HPS am Geraet wie Cloud-API): die Felder
50
+ * kommen aus `cardPaymentData` in der Form von [hobexReceiptToCardPaymentData]
51
+ * (models/hobex-receipt.ts) — Datum, TID, Genehmigungsnummer, Karte, PAN,
52
+ * Antwortcode. Bei CVM "1" verlangt die Karte eine Unterschrift; dafuer
53
+ * bekommt der Bon eine Linie. Der PAN haengt beim HPS das Ablaufdatum mit
54
+ * Unterstrich an ("…4720_2810") — gedruckt wird nur die maskierte Nummer.
55
+ *
56
+ * Andere Anbieter (GP Tom, SumUp, myPOS, Stripe) drucken in ihren eigenen
57
+ * Apps; ihre Bloecke bleiben bewusst draussen (siehe Kopfkommentar).
58
+ */
59
+ function hobexKartenblock(receipt) {
60
+ const provider = receipt.creditCardProvider;
61
+ const daten = receipt.cardPaymentData;
62
+ if ((provider !== 'hobexHps' && provider !== 'hobexCloudApi') || daten == null) {
63
+ return [];
64
+ }
65
+ const feld = (k) => {
66
+ const v = daten[k];
67
+ return v == null ? '' : String(v);
68
+ };
69
+ const paar = (teile) => teile.filter(Boolean).join(' · ');
70
+ const pan = feld('cardNumber').split('_')[0] ?? '';
71
+ const rc = feld('responseCode');
72
+ const zeilen = [textZeile('Hobex Beleg', 'center', true)];
73
+ const inhalt = [
74
+ feld('date'),
75
+ paar([feld('tid') && `TID ${feld('tid')}`, feld('no') && `Nr. ${feld('no')}`]),
76
+ paar([feld('type'), feld('cardBrand')]),
77
+ pan,
78
+ rc && `RC ${rc}`,
79
+ ];
80
+ for (const zeile of inhalt) {
81
+ if (zeile)
82
+ zeilen.push(textZeile(zeile, 'center'));
83
+ }
84
+ if (feld('cvm') === '1') {
85
+ zeilen.push({ kind: 'space', lines: 2 });
86
+ zeilen.push(textZeile('------------------', 'center'));
87
+ zeilen.push(textZeile('Unterschrift', 'center'));
88
+ }
89
+ zeilen.push({ kind: 'space', lines: 1 });
90
+ return zeilen;
91
+ }
48
92
  function zahlungsartText(wert) {
49
93
  if (wert != null && typeof wert === 'object') {
50
94
  return wert.label;
@@ -143,21 +187,19 @@ function gutscheinText(voucher) {
143
187
  if (name != null && name.toLowerCase().includes(text.toLowerCase())) {
144
188
  return name;
145
189
  }
146
- if (voucher.valueCents != null) {
147
- text += ` ${gutscheinBetrag(voucher.valueCents)} €`;
190
+ // Der Betrag steht in derselben Zeile rechts in der Betragsspalte — ihn
191
+ // auch ins Label zu schreiben war doppelt und brach auf 58 mm haesslich
192
+ // um ("Wertgutschein 3 €" + Folgezeile "€"). Statt dessen der Code: er
193
+ // identifiziert den Gutschein (Mehrzweck: der Kunde will wissen, WELCHER
194
+ // eingeloest wurde).
195
+ if (voucher.code != null && voucher.code !== '') {
196
+ text += ` ${voucher.code}`;
148
197
  }
149
198
  if (name != null && name.length > 0) {
150
199
  text += ` - ${name}`;
151
200
  }
152
201
  return text;
153
202
  }
154
- /**
155
- * Ganze Betraege kurz (`"10"`), krumme mit zwei Nachkommastellen (`"1,50"`) —
156
- * Zwilling von `KeckVoucher.formatVoucherAmount`.
157
- */
158
- function gutscheinBetrag(cents) {
159
- return cents % 100 === 0 ? String(cents / 100) : formatCents(cents);
160
- }
161
203
  // ----------------------------------------------------------------- Bauteile
162
204
  function textZeile(text, align = 'left', bold = false) {
163
205
  return { kind: 'text', text, align, bold };
@@ -185,6 +227,46 @@ function tabellenZeile(werte, paperSize) {
185
227
  })),
186
228
  };
187
229
  }
230
+ /**
231
+ * Trinkgeld auf dem Bon: keine Menge, kein Einzelpreis — Bezeichnung und
232
+ * Summe. Eine Steuer-Kategorie steht hinter dem Betrag (D = Durchlaeufer,
233
+ * sonst Inhaber-Trinkgeld als Umsatz); bei mehreren steht die Aufteilung in
234
+ * einer zweiten, eingerueckten Zeile. Breite 8:4: „Trinkgeld Personal“ (18
235
+ * Zeichen) muss auf 58 mm in die Zeile passen, rechts reichen zehn Zeichen
236
+ * auch fuer „-123,45 D“.
237
+ */
238
+ function trinkgeldZeilen(name, teile) {
239
+ const summe = teile.reduce((s, t) => s + t.cents, 0);
240
+ if (teile.length === 1) {
241
+ return [{
242
+ kind: 'columns',
243
+ columns: [
244
+ { text: name, width: 8, align: 'left' },
245
+ { text: `${formatCents(summe)} ${teile[0].satz.category}`, width: 4, align: 'right' },
246
+ ],
247
+ }];
248
+ }
249
+ const sortiert = [...teile].sort((a, b) => a.satz.category.localeCompare(b.satz.category));
250
+ return [
251
+ {
252
+ kind: 'columns',
253
+ columns: [
254
+ { text: name, width: 8, align: 'left' },
255
+ // Stern statt Kategorie-Buchstabe: fluchtet mit den Warenbetraegen
256
+ // (" B" ist zwei Zeichen, " *" auch) und verweist auf die
257
+ // Aufteilungszeile darunter — klassische Fussnote.
258
+ { text: `${formatCents(summe)} *`, width: 4, align: 'right' },
259
+ ],
260
+ },
261
+ { kind: 'text', text: `* davon ${sortiert.map((t) => `${formatCents(t.cents)} ${t.satz.category}`).join(', ')}`, align: 'left', bold: false },
262
+ ];
263
+ }
264
+ /**
265
+ * Rabatt auf dem Bon: dieselbe Form wie Trinkgeld — Summenzeile, bei einem
266
+ * Steuersatz mit Kategorie-Buchstaben, im Mischfall mit Stern und der
267
+ * Aufteilung als Fussnote darunter (siehe trinkgeldZeilen).
268
+ */
269
+ const rabattZeilen = trinkgeldZeilen;
188
270
  /** Positionszeile: Menge, Bezeichnung, ab Menge 2 der Einzelpreis. */
189
271
  function positionsZeile(item, satz) {
190
272
  const menge = String(item.quantity);
@@ -443,10 +525,58 @@ export function buildReceiptLayout(receipt, company, options = {}) {
443
525
  gruppen.set(satz.rate, neu);
444
526
  return neu;
445
527
  };
446
- for (const item of receipt.items) {
447
- const satz = steuersatz(item.vat);
448
- gruppeVon(satz).bruttoCents += receiptItemTotalCents(item);
449
- lines.push(positionsZeile(item, satz));
528
+ // Trinkgeld-Positionen gleichen Namens werden auf dem Bon zu EINER Zeile
529
+ // mit der Summe zusammengefasst (die Aufteilung des Inhaber-Trinkgelds auf
530
+ // mehrere Steuersaetze bzw. mehrere Empfaenger ist fiskalisch notwendig,
531
+ // fuer den Gast aber nur Rauschen); bei mehr als einer Steuer-Kategorie
532
+ // steht die Aufteilung darunter. USt-Tabelle und DEP bleiben positionsgenau.
533
+ // Rabatt-Positionen gleichen Namens werden wie Trinkgeld zu EINER Zeile mit
534
+ // der Summe zusammengefasst (die Aufteilung je Steuersatz ist fiskalisch
535
+ // notwendig, fuer den Gast aber Rauschen); vor dem ERSTEN Rabatt-Block steht
536
+ // einmalig die Zwischensumme der Waren — der Gast sieht vorher/nachher.
537
+ // USt-Tabelle und DEP bleiben positionsgenau.
538
+ const warenSummeCents = receipt.items
539
+ .filter((it) => !isTipItem(it) && !isDiscountItem(it))
540
+ .reduce((summe, it) => summe + receiptItemTotalCents(it), 0);
541
+ let zwischensummeGezeigt = false;
542
+ let i = 0;
543
+ while (i < receipt.items.length) {
544
+ const item = receipt.items[i];
545
+ if (!isTipItem(item) && !isDiscountItem(item)) {
546
+ const satz = steuersatz(item.vat);
547
+ gruppeVon(satz).bruttoCents += receiptItemTotalCents(item);
548
+ lines.push(positionsZeile(item, satz));
549
+ i += 1;
550
+ continue;
551
+ }
552
+ const artVon = (it) => (isTipItem(it) ? 'tip' : 'discount');
553
+ const art = artVon(item);
554
+ const gruppe = [];
555
+ while (i < receipt.items.length && artVon(receipt.items[i]) === art && !(!isTipItem(receipt.items[i]) && !isDiscountItem(receipt.items[i])) && receipt.items[i].name === item.name) {
556
+ gruppe.push(receipt.items[i]);
557
+ i += 1;
558
+ }
559
+ const teile = [];
560
+ for (const teil of gruppe) {
561
+ const satz = steuersatz(teil.vat);
562
+ gruppeVon(satz).bruttoCents += receiptItemTotalCents(teil);
563
+ const vorhanden = teile.find((t) => t.satz.category === satz.category);
564
+ if (vorhanden)
565
+ vorhanden.cents += receiptItemTotalCents(teil);
566
+ else
567
+ teile.push({ satz, cents: receiptItemTotalCents(teil) });
568
+ }
569
+ if (art === 'discount' && !zwischensummeGezeigt) {
570
+ // Trennlinie vor dem Rabatt-Block: die Warenzeilen enden sichtbar,
571
+ // darunter beginnt die Verrechnung (Zwischensumme, Rabatt).
572
+ lines.push({ kind: 'rule', char: '-' });
573
+ // Zwei Fuellzeichen, wo bei den Warenzeilen " B" steht — so fluchten
574
+ // ALLE Betraege (Waren, Zwischensumme, Rabatt, Trinkgeld) in einer
575
+ // Spalte; die letzten zwei Zeichen sind die Kategorie-/Stern-Spalte.
576
+ lines.push(paarZeile('Zwischensumme', `${formatCents(warenSummeCents)} `, 8, 4));
577
+ zwischensummeGezeigt = true;
578
+ }
579
+ lines.push(...(art === 'discount' ? rabattZeilen(item.name, teile) : trinkgeldZeilen(item.name, teile)));
450
580
  }
451
581
  // --- Gutscheine: verkaufte Wertgutscheine sind Umsatz zu 0 %, eingeloeste
452
582
  // Promotionsgutscheine mindern die Steuergruppen anteilig.
@@ -494,6 +624,24 @@ export function buildReceiptLayout(receipt, company, options = {}) {
494
624
  formatCents(bruttoCents),
495
625
  ], paperSize));
496
626
  }
627
+ // Mitarbeiter-Trinkgeld ist als Null-%-Umsatz erfasst (Erlass 2.4.2.1), aber
628
+ // kein Umsatz des Betriebs — die Klarstellung steht direkt unter der
629
+ // 0-%-Zeile, damit der Beleg (auch als Rechnung) nicht nach steuerfreiem
630
+ // Umsatz aussieht. Inhaber-Trinkgeld ist Umsatz und bekommt keine Zeile.
631
+ const durchlaufendesTrinkgeldCents = receipt.items
632
+ .filter((it) => isTipItem(it) && it.recipient?.owner !== true)
633
+ .reduce((summe, it) => summe + receiptItemTotalCents(it), 0);
634
+ // Nur wenn die 0-%-Gruppe MEHR enthaelt als das Trinkgeld (Wertgutschein-
635
+ // Verkauf ist ebenfalls Kategorie D, beim Kleinunternehmer ist alles 0 %):
636
+ // dann trennt diese Zeile den Durchlaeufer vom uebrigen Null-Prozent-Betrag.
637
+ // Besteht die 0-%-Gruppe ohnehin nur aus dem Trinkgeld, sagt die Position
638
+ // "Trinkgeld Personal" schon alles — die Wiederholung waere Rauschen.
639
+ const nullGruppeCents = gruppen.get(VatRate.vat0.rate)?.bruttoCents ?? 0;
640
+ if (durchlaufendesTrinkgeldCents !== 0 && nullGruppeCents !== durchlaufendesTrinkgeldCents) {
641
+ // Breite 9:3 — auf 58 mm (32 Zeichen) passt die Beschriftung so in eine
642
+ // Zeile; der Betrag braucht rechts hoechstens acht Zeichen.
643
+ lines.push(paarZeile('Trinkgeld, kein Umsatz:', formatCents(durchlaufendesTrinkgeldCents), 9, 3));
644
+ }
497
645
  lines.push({ kind: 'rule', char: '-' });
498
646
  // --- Summen
499
647
  const summeCents = receiptSumCents(receipt);
@@ -502,7 +650,9 @@ export function buildReceiptLayout(receipt, company, options = {}) {
502
650
  lines.push(paarZeile('Zwischensumme', `${formatCents(zwischensummeCents)} €`));
503
651
  for (const voucher of receipt.vouchers) {
504
652
  if (voucher.action === VoucherAction.redeem && voucher.type === VoucherType.value) {
505
- lines.push(paarZeile(gutscheinText(voucher), `-${formatCents(voucher.valueCents ?? 0)} €`));
653
+ // Breite 8:4 — "Wertgutschein GS-1" (Label + Code) passt so auf 58 mm
654
+ // in die Zeile, statt den Code in eine Folgezeile fliessen zu lassen.
655
+ lines.push(paarZeile(gutscheinText(voucher), `-${formatCents(voucher.valueCents ?? 0)} €`, 8, 4));
506
656
  }
507
657
  }
508
658
  lines.push({ kind: 'rule', char: '-' });
@@ -527,6 +677,8 @@ export function buildReceiptLayout(receipt, company, options = {}) {
527
677
  // --- RKSV-QR-Code
528
678
  lines.push({ kind: 'qr', data: receipt.qr });
529
679
  lines.push({ kind: 'space', lines: 1 });
680
+ // --- Kartenzahlungsblock (Hobex) — wie im Vorbild UNTER dem QR-Code.
681
+ lines.push(...hobexKartenblock(receipt));
530
682
  // --- Dankestext und Fusszeilen
531
683
  if (company.thanksMessage.length > 0) {
532
684
  lines.push({ kind: 'space', lines: 1 });
@@ -47,5 +47,5 @@
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
  */
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';
50
+ export { type RegisterDeviceConnection, type RegisterDeviceCredentials, type PairRegisterDeviceOptions, type PairedRegisterDevice, type RegisterUserKind, type RegisterUserSummary, type RegisterUserPerms, REGISTER_PERMS, 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
51
  export { renewRegisterSession, endRegisterSession } from './session.js';
@@ -47,5 +47,5 @@
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
  */
50
- export { pairRegisterDevice, listRegisterUsersForDevice, unpairRegisterDevice, registerUserLogin, registerPinLogin, } from './pairing.js';
50
+ export { REGISTER_PERMS, pairRegisterDevice, listRegisterUsersForDevice, unpairRegisterDevice, registerUserLogin, registerPinLogin, } from './pairing.js';
51
51
  export { renewRegisterSession, endRegisterSession } from './session.js';
@@ -41,6 +41,11 @@ export interface RegisterGeraeteAngaben {
41
41
  geo?: RegisterGeo | null;
42
42
  }
43
43
  export interface PairRegisterDeviceOptions extends RegisterDeviceConnection, RegisterGeraeteAngaben {
44
+ /**
45
+ * Die Kasse ist schon auf einem anderen Geraet geoeffnet (BELEGT-Rueckfrage):
46
+ * `true` uebernimmt sie hierher und widerruft das alte Geraet.
47
+ */
48
+ takeover?: boolean;
44
49
  /**
45
50
  * Achtstelliger Kopplungs-Code aus dem Panel. Er ist 15 Minuten gueltig,
46
51
  * genau **einmal** verwendbar und gilt fuer eine bestimmte Kasse.
@@ -107,6 +112,16 @@ export interface RegisterDeviceUsers {
107
112
  loginMode: RegisterLoginMode;
108
113
  /** Der Betrieb verlangt die Ortung beim Login (Standortsperre an). */
109
114
  standortsperre: boolean;
115
+ /**
116
+ * Darf die gebundene Kasse ueberhaupt Belege erstellen? `bereit: false`
117
+ * nennt den Grund (ausser Betrieb, Schlussbeleg erstellt) — die Kasse
118
+ * sperrt das Kassieren VOR dem ersten Beleg statt an createReceipt zu
119
+ * scheitern. Fehlt das Feld (aelteres Backend), gilt bereit.
120
+ */
121
+ kasse?: {
122
+ bereit: boolean;
123
+ grund?: string | null;
124
+ } | null;
110
125
  }
111
126
  /**
112
127
  * Rechte eines Kassen-Benutzers (Backend: `perms`, Vorlagen `PERMS_KASSIER`
@@ -144,6 +159,8 @@ export interface RegisterUserPerms {
144
159
  tipAssign?: boolean;
145
160
  [weiteresRecht: string]: boolean | RegisterScope | undefined;
146
161
  }
162
+ /** Alle Rechte-Schluessel, die dieses Paket kennt — die Zwillinge pruefen dagegen. */
163
+ export declare const REGISTER_PERMS: readonly ["sell", "cancel", "articles", "layout", "reports", "takeover", "cancelScope", "receiptsScope", "drawer", "discount", "tipAssign"];
147
164
  /** Reichweite lesen, mit der Migration des Backends (register-auth.js). */
148
165
  export declare function cancelScopeOf(perms: RegisterUserPerms | null | undefined): RegisterScope;
149
166
  export declare function receiptsScopeOf(perms: RegisterUserPerms | null | undefined): RegisterScope;