@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.
- package/README.md +21 -0
- package/dist/cjs/client/aufrufe.d.ts +21 -0
- package/dist/cjs/client/aufrufe.js +34 -0
- package/dist/cjs/client/cashregisters.d.ts +2 -2
- package/dist/cjs/client/index.d.ts +2 -1
- package/dist/cjs/client/index.js +3 -1
- package/dist/cjs/client/receipts.d.ts +31 -12
- package/dist/cjs/client/receipts.js +51 -0
- package/dist/cjs/client/reports.d.ts +3 -3
- package/dist/cjs/client/status.d.ts +3 -3
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/kasse/artikel.d.ts +3 -3
- package/dist/cjs/kasse/client.d.ts +4 -4
- package/dist/cjs/kasse/drucker.d.ts +4 -4
- package/dist/cjs/kasse/index.d.ts +1 -1
- package/dist/cjs/kasse/index.js +30 -1
- package/dist/cjs/kasse/settings.d.ts +113 -26
- package/dist/cjs/kasse/settings.js +91 -9
- package/dist/cjs/models/index.d.ts +1 -1
- package/dist/cjs/models/index.js +3 -1
- package/dist/cjs/models/receipt-item.d.ts +41 -0
- package/dist/cjs/models/receipt-item.js +41 -1
- package/dist/cjs/models/receipt.d.ts +7 -0
- package/dist/cjs/models/receipt.js +2 -0
- package/dist/cjs/payments/hobex.d.ts +3 -3
- package/dist/cjs/payments/stripe.d.ts +3 -3
- package/dist/cjs/printing/escpos.js +7 -0
- package/dist/cjs/react/index.js +11 -1
- package/dist/cjs/receipt/discount.js +1 -1
- package/dist/cjs/receipt/layout-escpos.js +9 -4
- package/dist/cjs/receipt/layout.d.ts +6 -3
- package/dist/cjs/receipt/layout.js +166 -14
- package/dist/cjs/register/index.d.ts +1 -1
- package/dist/cjs/register/index.js +2 -1
- package/dist/cjs/register/pairing.d.ts +17 -0
- package/dist/cjs/register/pairing.js +28 -3
- package/dist/cjs/register/session.d.ts +3 -3
- package/dist/esm/client/aufrufe.d.ts +21 -0
- package/dist/esm/client/aufrufe.js +31 -0
- package/dist/esm/client/cashregisters.d.ts +2 -2
- package/dist/esm/client/index.d.ts +2 -1
- package/dist/esm/client/index.js +1 -0
- package/dist/esm/client/receipts.d.ts +31 -12
- package/dist/esm/client/receipts.js +51 -0
- package/dist/esm/client/reports.d.ts +3 -3
- package/dist/esm/client/status.d.ts +3 -3
- package/dist/esm/index.d.ts +2 -2
- package/dist/esm/index.js +2 -2
- package/dist/esm/kasse/artikel.d.ts +3 -3
- package/dist/esm/kasse/client.d.ts +4 -4
- package/dist/esm/kasse/drucker.d.ts +4 -4
- package/dist/esm/kasse/index.d.ts +1 -1
- package/dist/esm/kasse/index.js +3 -1
- package/dist/esm/kasse/settings.d.ts +113 -26
- package/dist/esm/kasse/settings.js +90 -8
- package/dist/esm/models/index.d.ts +1 -1
- package/dist/esm/models/index.js +1 -1
- package/dist/esm/models/receipt-item.d.ts +41 -0
- package/dist/esm/models/receipt-item.js +39 -1
- package/dist/esm/models/receipt.d.ts +7 -0
- package/dist/esm/models/receipt.js +2 -0
- package/dist/esm/payments/hobex.d.ts +3 -3
- package/dist/esm/payments/stripe.d.ts +3 -3
- package/dist/esm/printing/escpos.js +7 -0
- package/dist/esm/react/index.js +11 -1
- package/dist/esm/receipt/discount.js +1 -1
- package/dist/esm/receipt/layout-escpos.js +9 -4
- package/dist/esm/receipt/layout.d.ts +6 -3
- package/dist/esm/receipt/layout.js +167 -15
- package/dist/esm/register/index.d.ts +1 -1
- package/dist/esm/register/index.js +1 -1
- package/dist/esm/register/pairing.d.ts +17 -0
- package/dist/esm/register/pairing.js +27 -3
- package/dist/esm/register/session.d.ts +3 -3
- package/fixtures/belege/rabatt-chef-trinkgeld.json +89 -0
- package/fixtures/belege/rabatt-einfach.json +50 -0
- package/fixtures/belege/rabatt-trinkgeld.json +75 -0
- package/fixtures/belege/rabatt-wertgutschein.json +70 -0
- package/fixtures/belege/rabattzeilen.json +5 -3
- package/fixtures/belege/storno-rabatt.json +68 -0
- package/fixtures/erwartet/rabatt-chef-trinkgeld.grid32.txt +31 -0
- package/fixtures/erwartet/rabatt-chef-trinkgeld.grid48.txt +30 -0
- package/fixtures/erwartet/rabatt-chef-trinkgeld.lines.json +317 -0
- package/fixtures/erwartet/rabatt-einfach.grid32.txt +26 -0
- package/fixtures/erwartet/rabatt-einfach.grid48.txt +25 -0
- package/fixtures/erwartet/rabatt-einfach.lines.json +250 -0
- package/fixtures/erwartet/rabatt-trinkgeld.grid32.txt +31 -0
- package/fixtures/erwartet/rabatt-trinkgeld.grid48.txt +30 -0
- package/fixtures/erwartet/rabatt-trinkgeld.lines.json +336 -0
- package/fixtures/erwartet/rabatt-wertgutschein.grid32.txt +32 -0
- package/fixtures/erwartet/rabatt-wertgutschein.grid48.txt +31 -0
- package/fixtures/erwartet/rabatt-wertgutschein.lines.json +330 -0
- package/fixtures/erwartet/rabattzeilen.grid32.txt +4 -2
- package/fixtures/erwartet/rabattzeilen.grid48.txt +4 -2
- package/fixtures/erwartet/rabattzeilen.lines.json +18 -8
- package/fixtures/erwartet/storno-rabatt.grid32.txt +34 -0
- package/fixtures/erwartet/storno-rabatt.grid48.txt +32 -0
- package/fixtures/erwartet/storno-rabatt.lines.json +317 -0
- package/fixtures/kasse-settings-standard.json +44 -5
- package/fixtures/manifest.json +34 -4
- package/fixtures/oberflaeche.json +216 -0
- 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
|
-
|
|
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 {
|
|
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:
|
|
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:
|
|
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 {
|
|
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:
|
|
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:
|
|
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
|
}
|
package/dist/esm/react/index.js
CHANGED
|
@@ -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' :
|
|
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
|
|
52
|
-
//
|
|
53
|
-
|
|
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
|
|
26
|
-
* Vorbilds (GP Tom,
|
|
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
|
-
|
|
147
|
-
|
|
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
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
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;
|