@kreiseck/kasseneck-api 1.0.0-rc.4 → 1.0.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.
- package/CHANGELOG.md +77 -0
- package/README.md +40 -4
- package/dist/cjs/index.d.ts +1 -1
- package/dist/cjs/index.js +4 -1
- package/dist/cjs/pos/index.d.ts +1 -1
- package/dist/cjs/pos/index.js +6 -1
- package/dist/cjs/pos/texte.d.ts +95 -2
- package/dist/cjs/pos/texte.js +111 -3
- package/dist/cjs/receipt/due.d.ts +33 -0
- package/dist/cjs/receipt/due.js +61 -15
- package/dist/cjs/receipt/index.d.ts +1 -1
- package/dist/cjs/receipt/index.js +4 -1
- package/dist/cjs/stored/index.d.ts +1 -0
- package/dist/cjs/stored/index.js +7 -0
- package/dist/cjs/stored/layout.d.ts +55 -0
- package/dist/cjs/stored/layout.js +119 -0
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/pos/index.d.ts +1 -1
- package/dist/esm/pos/index.js +1 -1
- package/dist/esm/pos/texte.d.ts +95 -2
- package/dist/esm/pos/texte.js +108 -2
- package/dist/esm/receipt/due.d.ts +33 -0
- package/dist/esm/receipt/due.js +58 -15
- package/dist/esm/receipt/index.d.ts +1 -1
- package/dist/esm/receipt/index.js +1 -1
- package/dist/esm/stored/index.d.ts +1 -0
- package/dist/esm/stored/index.js +2 -0
- package/dist/esm/stored/layout.d.ts +55 -0
- package/dist/esm/stored/layout.js +113 -0
- package/dist/esm/version.d.ts +1 -1
- package/dist/esm/version.js +1 -1
- package/fixtures/hobex-hps-codes.json +1 -1
- package/fixtures/invoice-api.schema.json +1 -1
- package/fixtures/invoice-texts.json +1 -1
- package/fixtures/pos-message-cases.json +31 -1
- package/fixtures/pos-texts.json +68 -1
- package/fixtures/receipt-due-errors.json +56 -0
- package/fixtures/surface.json +11 -1
- package/fixtures/v3/antworten/belege.json +1 -1
- package/fixtures/v3/antworten/belegmail.json +1 -1
- package/fixtures/v3/antworten/kasse-belege.json +1 -1
- package/fixtures/v3/antworten/kasse.json +2 -2
- package/fixtures/v3/antworten/rechnungen.json +1 -1
- package/fixtures/v3/antworten/storno.json +1 -1
- package/fixtures/v3/stored/belege.json +1 -1
- package/fixtures/v3/stored/kasse.json +2 -2
- package/fixtures/v3/stored/rechnungen.json +1 -1
- package/fixtures/v3/v3-vokabular.json +1 -1
- package/fixtures/v3/zahlbetrag-faelle.json +1 -1
- package/package.json +1 -1
package/dist/esm/pos/texte.js
CHANGED
|
@@ -20,11 +20,24 @@
|
|
|
20
20
|
* `only`), die Platzhalter und die Arten der Fehlerregeln (Abschnitte
|
|
21
21
|
* `placeholders` und `structure`).
|
|
22
22
|
*/
|
|
23
|
+
import { isOutcomeUnknown, KasseneckApiError, KasseneckHttpError, KasseneckNetworkError } from '../client/errors.js';
|
|
23
24
|
const MELDUNGEN_ROH = {
|
|
24
25
|
// --- Transport -----------------------------------------------------------
|
|
25
26
|
'network.no_connection': { text: 'Keine Verbindung zum Server. Bitte die Internetverbindung prüfen und erneut versuchen.' },
|
|
26
27
|
'network.timeout': { text: 'Der Server antwortet nicht. Bitte die Internetverbindung prüfen und erneut versuchen.' },
|
|
28
|
+
// Frist oder Netzfehler auf einem Aufruf mit Wirkung (ERROR_OUTCOME_RULES):
|
|
29
|
+
// der Vorgang kann trotzdem gebucht sein, darum nie „erneut versuchen“.
|
|
30
|
+
'network.outcome_unknown': { text: 'Der Server hat nicht geantwortet, der Vorgang kann trotzdem gebucht sein. Bitte vor einem neuen Versuch prüfen, ob der letzte Vorgang schon gebucht ist.' },
|
|
27
31
|
'server.unexpected': { text: 'Der Server hat unerwartet geantwortet (HTTP {status}). Bitte den Support verständigen.', placeholders: ['status'] },
|
|
32
|
+
// Rand-Codes, die ein Kassier nicht deuten kann (siehe ERROR_CODE_RULES): der
|
|
33
|
+
// technische Satz bleibt in `error.message`, auf den Schirm kommt dieser.
|
|
34
|
+
// Nur fuer Codes, bei denen der Server nichts ausgefuehrt hat: dann darf
|
|
35
|
+
// der Satz zum neuen Versuch raten.
|
|
36
|
+
'server.connection_disturbed': { text: 'Die Verbindung zum Kassenserver ist gestört. Bitte kurz warten und erneut versuchen.' },
|
|
37
|
+
// Codes mit unklarem Ausgang (der Vorgang kann gebucht sein): nie zum
|
|
38
|
+
// Wiederholen raten, erst nachsehen. „Kasse neu öffnen“ passt fuer beide
|
|
39
|
+
// Seiten (Web: neu laden, App: neu starten).
|
|
40
|
+
'server.response_unreadable': { text: 'Die Antwort des Kassenservers war nicht lesbar. Bitte die Kasse neu öffnen und vor einem neuen Versuch prüfen, ob der letzte Vorgang schon gebucht ist.' },
|
|
28
41
|
// --- Kopplung ------------------------------------------------------------
|
|
29
42
|
'pairing.code_missing': { text: 'Bitte den Kopplungs-Code eingeben.' },
|
|
30
43
|
'pairing.failed': { text: 'Die Kopplung ist fehlgeschlagen. Bitte erneut versuchen.' },
|
|
@@ -263,8 +276,8 @@ const MELDUNGEN_ROH = {
|
|
|
263
276
|
export const MESSAGES = MELDUNGEN_ROH;
|
|
264
277
|
/**
|
|
265
278
|
* In welcher Reihenfolge ein Fehler eingeordnet wird, auf beiden Seiten
|
|
266
|
-
* dieselbe.
|
|
267
|
-
* (`behavior`) oder
|
|
279
|
+
* dieselbe. Genau eine Regel je Art (`kind`), mit einem Verhalten
|
|
280
|
+
* (`behavior`) oder dem Schluessel des Satzes (`key`). Die Arten:
|
|
268
281
|
* api - HTTP 200, `status:'error'`: der Satz des Backends, woertlich
|
|
269
282
|
* (`server_text`)
|
|
270
283
|
* plain_text - schon fuer den Bildschirm geschrieben: sein eigener Text
|
|
@@ -277,6 +290,11 @@ export const MESSAGES = MELDUNGEN_ROH;
|
|
|
277
290
|
* (`fallback`)
|
|
278
291
|
* Unter 0.x hiessen sie `klartext`, `zeitablauf`, `netz`, `unerwartet`,
|
|
279
292
|
* `sonst` (Tabelle in `fixtures/renames-1.0.json`).
|
|
293
|
+
*
|
|
294
|
+
* Seit 1.0.0-rc.5 verfeinern [ERROR_CODE_RULES] und [ERROR_OUTCOME_RULES]
|
|
295
|
+
* diese Liste; sie stehen bewusst getrennt, damit ein Leser, der nur nach
|
|
296
|
+
* `kind` sucht, weiter genau den Satz von rc.4 zeigt. [findErrorRule] wendet
|
|
297
|
+
* alle drei in der richtigen Reihenfolge an.
|
|
280
298
|
*/
|
|
281
299
|
export const ERROR_RULES = [
|
|
282
300
|
{ kind: 'api', behavior: 'server_text' },
|
|
@@ -286,6 +304,87 @@ export const ERROR_RULES = [
|
|
|
286
304
|
{ kind: 'unexpected', key: 'server.unexpected' },
|
|
287
305
|
{ kind: 'other', behavior: 'fallback' },
|
|
288
306
|
];
|
|
307
|
+
/**
|
|
308
|
+
* Regeln je Code (`error.code`), vor der Regel der Art: sie ersetzen den
|
|
309
|
+
* technischen Satz des Pakets bzw. des Rands durch einen Menschentext. Der
|
|
310
|
+
* technische Satz bleibt in `error.message` fuers Protokoll.
|
|
311
|
+
* - `dialect_mismatch`, `response_translation_failed`, `response_unreadable`:
|
|
312
|
+
* Ausgang unklar, der Vorgang kann gebucht sein; der Satz raet nie zum
|
|
313
|
+
* Wiederholen, sondern zum Nachsehen.
|
|
314
|
+
* - `route_missing`, `not_found`, `internal_translation_error`: am Server
|
|
315
|
+
* geschah nichts, ein neuer Versuch ist sicher (auch auf den Geldwegen:
|
|
316
|
+
* alle drei stehen in `PAYMENT_CALL_REJECTED_CODES`).
|
|
317
|
+
*/
|
|
318
|
+
export const ERROR_CODE_RULES = [
|
|
319
|
+
{ kind: 'api', codes: ['dialect_mismatch', 'response_translation_failed', 'response_unreadable'], key: 'server.response_unreadable' },
|
|
320
|
+
{ kind: 'api', codes: ['route_missing', 'not_found', 'internal_translation_error'], key: 'server.connection_disturbed' },
|
|
321
|
+
];
|
|
322
|
+
/**
|
|
323
|
+
* Regeln je Ausgang, nach den Code-Regeln und vor der Regel der Art: eine
|
|
324
|
+
* Frist oder ein Netzfehler auf einem Aufruf mit Wirkung (Ausgang unklar,
|
|
325
|
+
* siehe [messageOutcome]) raet nie zum Wiederholen. Ein Netzfehler auf einem
|
|
326
|
+
* Aufruf ohne Wirkung behaelt den Satz von rc.4 („erneut versuchen“).
|
|
327
|
+
*/
|
|
328
|
+
export const ERROR_OUTCOME_RULES = [
|
|
329
|
+
{ kind: 'timeout', outcome: 'unknown', key: 'network.outcome_unknown' },
|
|
330
|
+
{ kind: 'network', outcome: 'unknown', key: 'network.outcome_unknown' },
|
|
331
|
+
];
|
|
332
|
+
/**
|
|
333
|
+
* Die Regel fuer einen Fehler: zuerst eine Code-Regel mit passender Art und
|
|
334
|
+
* passendem `code`, dann eine Ausgangs-Regel mit passender Art und
|
|
335
|
+
* passendem `outcome`, sonst die Regel der Art aus [ERROR_RULES]. Beide
|
|
336
|
+
* Kassen ordnen so ein.
|
|
337
|
+
*/
|
|
338
|
+
export function findErrorRule(kind, detail = {}) {
|
|
339
|
+
const { code, outcome } = detail;
|
|
340
|
+
if (code !== undefined && code !== null) {
|
|
341
|
+
const treffer = ERROR_CODE_RULES.find((r) => r.kind === kind && r.codes.includes(code));
|
|
342
|
+
if (treffer)
|
|
343
|
+
return treffer;
|
|
344
|
+
}
|
|
345
|
+
if (outcome !== undefined && outcome !== null) {
|
|
346
|
+
const treffer = ERROR_OUTCOME_RULES.find((r) => r.kind === kind && r.outcome === outcome);
|
|
347
|
+
if (treffer)
|
|
348
|
+
return treffer;
|
|
349
|
+
}
|
|
350
|
+
// Jede Art hat genau eine Regel; hierher kommt nur eine fremde Art nicht.
|
|
351
|
+
return ERROR_RULES.find((r) => r.kind === kind) ?? ERROR_RULES[ERROR_RULES.length - 1];
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Kassen-Aufrufe mit Wirkung, die sich nach einem Netzfehler nicht gefahrlos
|
|
355
|
+
* wiederholen lassen: die sechs, deren Ausgang das Paket selbst als unklar
|
|
356
|
+
* fuehrt (Beleg, auch Null- und Startbeleg ueber `createReceipt`, Storno,
|
|
357
|
+
* FinanzOnline, die drei Geldwege), dazu der Druckjob (zweiter Bon) und die
|
|
358
|
+
* Belegmail (zweite Mail). Ob die Anfrage vor dem Abriss schon draussen war,
|
|
359
|
+
* sieht das Paket nicht (fetch wirft in beiden Faellen gleich); darum gilt
|
|
360
|
+
* hier immer der vorsichtige Satz.
|
|
361
|
+
*/
|
|
362
|
+
export const CALLS_WITH_EFFECT = Object.freeze([
|
|
363
|
+
'createReceipt',
|
|
364
|
+
'cancelReceipt',
|
|
365
|
+
'financeWebService',
|
|
366
|
+
'hobexPayApi',
|
|
367
|
+
'hobexRefundApi',
|
|
368
|
+
'stripeCaptureIntent',
|
|
369
|
+
'createPrintJob',
|
|
370
|
+
'sendReceiptEmail',
|
|
371
|
+
]);
|
|
372
|
+
const MIT_WIRKUNG = new Set(CALLS_WITH_EFFECT);
|
|
373
|
+
/**
|
|
374
|
+
* Der Ausgang, nach dem der Satz gewaehlt wird: `'unknown'`, wenn das Paket
|
|
375
|
+
* ihn als unklar fuehrt ([isOutcomeUnknown]) oder wenn eine Frist bzw. ein
|
|
376
|
+
* Netzfehler einen Aufruf aus [CALLS_WITH_EFFECT] traf; sonst der Ausgang
|
|
377
|
+
* des Fehlers (`'rejected'`) bzw. `undefined` fuer fremde Fehler.
|
|
378
|
+
*/
|
|
379
|
+
export function messageOutcome(error) {
|
|
380
|
+
if (isOutcomeUnknown(error))
|
|
381
|
+
return 'unknown';
|
|
382
|
+
if (error instanceof KasseneckNetworkError)
|
|
383
|
+
return MIT_WIRKUNG.has(error.functionName.split('/')[0]) ? 'unknown' : error.outcome;
|
|
384
|
+
if (error instanceof KasseneckApiError || error instanceof KasseneckHttpError)
|
|
385
|
+
return error.outcome;
|
|
386
|
+
return undefined;
|
|
387
|
+
}
|
|
289
388
|
/**
|
|
290
389
|
* Beleg per E-Mail senden: welcher `code` des Backends welchen Satz bekommt.
|
|
291
390
|
*
|
|
@@ -369,6 +468,10 @@ const BESCHRIFTUNGEN_ROH = {
|
|
|
369
468
|
'message.dismiss_warning': { text: 'Verstanden – Warnung ausblenden: {message}', placeholders: ['message'] },
|
|
370
469
|
// --- Kopplung und Abmelden -----------------------------------------------
|
|
371
470
|
'pairing.pair_again': { text: 'Neu koppeln' },
|
|
471
|
+
// Ein Geraet ohne Namen (`deviceLabel: null` unter /v3) in der Auswahl.
|
|
472
|
+
'register.device_unnamed': { text: 'Kasse' },
|
|
473
|
+
// Restzeit der PIN-Sperre unter dem Satz des Backends.
|
|
474
|
+
'login.locked_seconds': { text: 'Noch {seconds} s gesperrt', placeholders: ['seconds'] },
|
|
372
475
|
'logout.question': { text: 'Wirklich abmelden?' },
|
|
373
476
|
'logout.keep_working': { text: 'Weiter arbeiten' },
|
|
374
477
|
'device.unpair': { text: 'Gerät entkoppeln' },
|
|
@@ -397,6 +500,9 @@ const BESCHRIFTUNGEN_ROH = {
|
|
|
397
500
|
'split.payment': { text: 'Zahlung {n}', placeholders: ['n'] },
|
|
398
501
|
'split.amount': { text: 'Betrag' },
|
|
399
502
|
'split.remaining': { text: 'Rest' },
|
|
503
|
+
// Letzte Runde bei Getrennt zahlen: der Rest samt Rundungscent;
|
|
504
|
+
// `{cents}` traegt das Vorzeichen (`+1`, `−1`).
|
|
505
|
+
'split.remaining_with_rounding': { text: 'Rest inkl. Rundung {amount} ({cents} ct)', placeholders: ['amount', 'cents'] },
|
|
400
506
|
'split.divide': { text: '÷ {n}', placeholders: ['n'] },
|
|
401
507
|
'split.tip_basis': { text: '% von diesem Betrag' },
|
|
402
508
|
'split.of_which_tip': { text: 'davon Trinkgeld {amount}', placeholders: ['amount'] },
|
|
@@ -33,6 +33,10 @@ import type { Voucher } from '../models/voucher.js';
|
|
|
33
33
|
* rabattiert). Das weiss nur die Kasse; darum ist `tipRecipient` Pflicht,
|
|
34
34
|
* sobald Trinkgeld ohne `recipients` vorkommt. Ohne angemeldeten Benutzer
|
|
35
35
|
* (Geraete-Schluessel) gilt `'staff'`.
|
|
36
|
+
*
|
|
37
|
+
* **Nicht rechenbar:** jede unbrauchbare Eingabe wirft [ReceiptDueError]
|
|
38
|
+
* (`code: 'receipt_due_unavailable'`, `reason` im Einzelnen), nie still eine
|
|
39
|
+
* falsche Zahl. Dann ist nichts gesendet.
|
|
36
40
|
*/
|
|
37
41
|
/** Wer das Trinkgeld ohne `recipients` bekommt (Kennzeichen des angemeldeten Kassen-Benutzers). */
|
|
38
42
|
export type ReceiptDueTipRecipient = 'owner' | 'staff';
|
|
@@ -78,6 +82,35 @@ export interface ReceiptDueBreakdown {
|
|
|
78
82
|
/** Toepfe je auf Cent gerundet; `null` bei Start- und Nullbeleg. */
|
|
79
83
|
bucketsCents: ReceiptDueBuckets | null;
|
|
80
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Warum sich der Zahlbetrag nicht rechnen laesst. Fest wie ein Backend-Code;
|
|
87
|
+
* dieselbe Liste im Dart-Zwilling und in `fixtures/receipt-due-errors.json`.
|
|
88
|
+
*/
|
|
89
|
+
export declare const RECEIPT_DUE_ERROR_REASONS: readonly ["unknown_receipt_type", "tip_not_allowed", "invalid_item", "invalid_voucher", "invalid_tip", "unknown_payment_method", "tip_conflict", "tip_recipient_missing", "tip_without_goods"];
|
|
90
|
+
export type ReceiptDueErrorReason = (typeof RECEIPT_DUE_ERROR_REASONS)[number];
|
|
91
|
+
/**
|
|
92
|
+
* Der Zahlbetrag laesst sich aus dieser Eingabe nicht rechnen, etwa
|
|
93
|
+
* Trinkgeld mit Betrag ohne Ware (`reason: 'tip_without_goods'`).
|
|
94
|
+
*
|
|
95
|
+
* Eindeutig **nicht gesendet**: die Rechnung laeuft ganz im Paket, vor jedem
|
|
96
|
+
* Aufruf. `outcome` ist darum immer `'rejected'`; nichts ist geschehen, keine
|
|
97
|
+
* Belegnummer verbraucht, keine Karte belastet. Die Kasse sagt das dem
|
|
98
|
+
* Kassier, bevor sie ein Terminal anspricht. Entscheiden am `code` bzw.
|
|
99
|
+
* `reason`, nie an `message` (die ist fuers Protokoll).
|
|
100
|
+
*
|
|
101
|
+
* Bis 1.0.0-rc.4 warf die Rechnung hier einen `RangeError` ohne Code.
|
|
102
|
+
*/
|
|
103
|
+
export declare class ReceiptDueError extends Error {
|
|
104
|
+
readonly name = "ReceiptDueError";
|
|
105
|
+
/** Stabiler Code fuer jede Ursache. */
|
|
106
|
+
readonly code: "receipt_due_unavailable";
|
|
107
|
+
/** Die Ursache im Einzelnen. */
|
|
108
|
+
readonly reason: ReceiptDueErrorReason;
|
|
109
|
+
/** Immer `'rejected'`: es ging nichts hinaus. */
|
|
110
|
+
readonly outcome: "rejected";
|
|
111
|
+
constructor(reason: ReceiptDueErrorReason, message: string);
|
|
112
|
+
}
|
|
113
|
+
export declare function isReceiptDueError(error: unknown): error is ReceiptDueError;
|
|
81
114
|
type BelegArt = ReceiptType | ReceiptTypeKey | string;
|
|
82
115
|
/** Zahlbetrag in Cent, siehe Modulkommentar. */
|
|
83
116
|
export declare function receiptDueCents(items: readonly ReceiptItem[], vouchers: readonly Voucher[], receiptType: BelegArt, options?: ReceiptDueOptions): number;
|
package/dist/esm/receipt/due.js
CHANGED
|
@@ -1,4 +1,47 @@
|
|
|
1
1
|
import { ReceiptType, VoucherAction, VoucherType } from '../enums/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Warum sich der Zahlbetrag nicht rechnen laesst. Fest wie ein Backend-Code;
|
|
4
|
+
* dieselbe Liste im Dart-Zwilling und in `fixtures/receipt-due-errors.json`.
|
|
5
|
+
*/
|
|
6
|
+
export const RECEIPT_DUE_ERROR_REASONS = Object.freeze([
|
|
7
|
+
'unknown_receipt_type',
|
|
8
|
+
'tip_not_allowed',
|
|
9
|
+
'invalid_item',
|
|
10
|
+
'invalid_voucher',
|
|
11
|
+
'invalid_tip',
|
|
12
|
+
'unknown_payment_method',
|
|
13
|
+
'tip_conflict',
|
|
14
|
+
'tip_recipient_missing',
|
|
15
|
+
'tip_without_goods',
|
|
16
|
+
]);
|
|
17
|
+
/**
|
|
18
|
+
* Der Zahlbetrag laesst sich aus dieser Eingabe nicht rechnen, etwa
|
|
19
|
+
* Trinkgeld mit Betrag ohne Ware (`reason: 'tip_without_goods'`).
|
|
20
|
+
*
|
|
21
|
+
* Eindeutig **nicht gesendet**: die Rechnung laeuft ganz im Paket, vor jedem
|
|
22
|
+
* Aufruf. `outcome` ist darum immer `'rejected'`; nichts ist geschehen, keine
|
|
23
|
+
* Belegnummer verbraucht, keine Karte belastet. Die Kasse sagt das dem
|
|
24
|
+
* Kassier, bevor sie ein Terminal anspricht. Entscheiden am `code` bzw.
|
|
25
|
+
* `reason`, nie an `message` (die ist fuers Protokoll).
|
|
26
|
+
*
|
|
27
|
+
* Bis 1.0.0-rc.4 warf die Rechnung hier einen `RangeError` ohne Code.
|
|
28
|
+
*/
|
|
29
|
+
export class ReceiptDueError extends Error {
|
|
30
|
+
name = 'ReceiptDueError';
|
|
31
|
+
/** Stabiler Code fuer jede Ursache. */
|
|
32
|
+
code = 'receipt_due_unavailable';
|
|
33
|
+
/** Die Ursache im Einzelnen. */
|
|
34
|
+
reason;
|
|
35
|
+
/** Immer `'rejected'`: es ging nichts hinaus. */
|
|
36
|
+
outcome = 'rejected';
|
|
37
|
+
constructor(reason, message) {
|
|
38
|
+
super(`Zahlbetrag: ${message}`);
|
|
39
|
+
this.reason = reason;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function isReceiptDueError(error) {
|
|
43
|
+
return error instanceof ReceiptDueError;
|
|
44
|
+
}
|
|
2
45
|
const UMSATZ = new Set(['standard', 'cancellation', 'training']);
|
|
3
46
|
const TRINKGELD_ERLAUBT = new Set(['standard', 'training']);
|
|
4
47
|
const ZAHLARTEN = new Set(['cash', 'creditCard', 'online', 'uberApp', 'uberCard', 'uberCash', 'boltApp', 'boltCard', 'boltCash']);
|
|
@@ -11,7 +54,7 @@ export function receiptDueBreakdown(items, vouchers, receiptType, options = {})
|
|
|
11
54
|
const art = belegArt(receiptType);
|
|
12
55
|
const trinkgelder = trinkgeldAuftraege(options);
|
|
13
56
|
if (trinkgelder.length > 0 && !TRINKGELD_ERLAUBT.has(art)) {
|
|
14
|
-
throw new
|
|
57
|
+
throw new ReceiptDueError('tip_not_allowed', `Trinkgeld gibt es nur bei standard und training, nicht bei "${art}".`);
|
|
15
58
|
}
|
|
16
59
|
if (!UMSATZ.has(art)) {
|
|
17
60
|
return { dueCents: 0, counterDeltaCents: 0, valueVoucherFlowCents: 0, bucketsCents: null };
|
|
@@ -41,7 +84,7 @@ export function receiptDueBreakdown(items, vouchers, receiptType, options = {})
|
|
|
41
84
|
function belegArt(wert) {
|
|
42
85
|
const text = typeof wert === 'object' && wert !== null ? wert.value : wert;
|
|
43
86
|
if (typeof text !== 'string' || !Object.prototype.hasOwnProperty.call(ReceiptType, text)) {
|
|
44
|
-
throw new
|
|
87
|
+
throw new ReceiptDueError('unknown_receipt_type', `unbekannter Belegtyp "${String(text)}".`);
|
|
45
88
|
}
|
|
46
89
|
return text;
|
|
47
90
|
}
|
|
@@ -53,17 +96,17 @@ function euroToCent(euro) {
|
|
|
53
96
|
function innen(item, i) {
|
|
54
97
|
const satz = typeof item.vat === 'number' ? item.vat : item.vat?.rate;
|
|
55
98
|
if (typeof satz !== 'number' || !Number.isFinite(satz))
|
|
56
|
-
throw new
|
|
99
|
+
throw new ReceiptDueError('invalid_item', `Position ${i + 1} ohne Steuersatz.`);
|
|
57
100
|
if (!Number.isSafeInteger(item.priceCents))
|
|
58
|
-
throw new
|
|
101
|
+
throw new ReceiptDueError('invalid_item', `Position ${i + 1} hat keinen ganzen Cent-Preis.`);
|
|
59
102
|
if (typeof item.quantity !== 'number' || !Number.isFinite(item.quantity))
|
|
60
|
-
throw new
|
|
103
|
+
throw new ReceiptDueError('invalid_item', `Position ${i + 1} hat keine gueltige Menge.`);
|
|
61
104
|
const tip = item.kind === 'tip' ? (item.recipient?.owner === true ? 'owner' : 'staff') : null;
|
|
62
105
|
return { amount: item.quantity, priceOne: Math.round(item.priceCents) / 100, vat: satz, tip };
|
|
63
106
|
}
|
|
64
107
|
function gutscheinInnen(v, i) {
|
|
65
108
|
if (!Number.isSafeInteger(v.valueCents))
|
|
66
|
-
throw new
|
|
109
|
+
throw new ReceiptDueError('invalid_voucher', `Gutschein ${i + 1} hat keinen ganzen Cent-Wert.`);
|
|
67
110
|
return { action: String(v.action), type: String(v.type), value: Math.round(v.valueCents) / 100 };
|
|
68
111
|
}
|
|
69
112
|
/** Trinkgeld-Auftraege in der Reihenfolge des Servers: `tip`, dann je Zahlart die Summe der `tipCents`. */
|
|
@@ -71,7 +114,7 @@ function trinkgeldAuftraege(options) {
|
|
|
71
114
|
const raus = [];
|
|
72
115
|
const tipCents = (options.payments ?? []).some((z) => z != null && z.tipCents !== undefined);
|
|
73
116
|
if (options.tip != null && tipCents) {
|
|
74
|
-
throw new
|
|
117
|
+
throw new ReceiptDueError('tip_conflict', 'tip und payments[].tipCents gehen nicht zugleich (tip_conflict).');
|
|
75
118
|
}
|
|
76
119
|
if (options.tip != null) {
|
|
77
120
|
const tip = options.tip;
|
|
@@ -80,14 +123,14 @@ function trinkgeldAuftraege(options) {
|
|
|
80
123
|
const recipients = typeof tip === 'number' ? undefined : tip.recipients;
|
|
81
124
|
if (recipients != null) {
|
|
82
125
|
if (recipients.length === 0)
|
|
83
|
-
throw new
|
|
126
|
+
throw new ReceiptDueError('invalid_tip', 'recipients darf nicht leer sein.');
|
|
84
127
|
for (const r of recipients) {
|
|
85
128
|
pruefeCent(r.cents, 'Trinkgeld-Anteil');
|
|
86
129
|
if (typeof r.owner !== 'boolean')
|
|
87
|
-
throw new
|
|
130
|
+
throw new ReceiptDueError('invalid_tip', 'recipients[].owner muss true oder false sein.');
|
|
88
131
|
}
|
|
89
132
|
if (recipients.reduce((s, r) => s + r.cents, 0) !== cents) {
|
|
90
|
-
throw new
|
|
133
|
+
throw new ReceiptDueError('invalid_tip', 'die Anteile des Trinkgelds ergeben nicht den Betrag.');
|
|
91
134
|
}
|
|
92
135
|
raus.push({ empfaenger: recipients.map((r) => ({ cents: r.cents, owner: r.owner })) });
|
|
93
136
|
}
|
|
@@ -103,7 +146,7 @@ function trinkgeldAuftraege(options) {
|
|
|
103
146
|
pruefeCent(z.tipCents, 'tipCents');
|
|
104
147
|
const methode = typeof z.method === 'object' && z.method !== null ? z.method.value : z.method;
|
|
105
148
|
if (typeof methode !== 'string' || !ZAHLARTEN.has(methode))
|
|
106
|
-
throw new
|
|
149
|
+
throw new ReceiptDueError('unknown_payment_method', `unbekannte Zahlart "${String(methode)}".`);
|
|
107
150
|
je.set(methode, (je.get(methode) ?? 0) + z.tipCents);
|
|
108
151
|
}
|
|
109
152
|
for (const cents of je.values())
|
|
@@ -112,7 +155,7 @@ function trinkgeldAuftraege(options) {
|
|
|
112
155
|
}
|
|
113
156
|
function pruefeCent(wert, was) {
|
|
114
157
|
if (typeof wert !== 'number' || !Number.isInteger(wert) || wert <= 0) {
|
|
115
|
-
throw new
|
|
158
|
+
throw new ReceiptDueError('invalid_tip', `${was} muss eine ganze Zahl in Cent > 0 sein.`);
|
|
116
159
|
}
|
|
117
160
|
}
|
|
118
161
|
/** `tip-core.buildTipItems`: Personal in den Null-%-Satz, Inhaber anteilig auf die Saetze der Ware. */
|
|
@@ -120,13 +163,13 @@ function trinkgeldPositionen(auftrag, positionen, standard) {
|
|
|
120
163
|
const basis = warenCentsJeSatz(positionen);
|
|
121
164
|
const summe = Object.values(basis).reduce((s, c) => s + c, 0);
|
|
122
165
|
if (summe <= 0)
|
|
123
|
-
throw new
|
|
166
|
+
throw new ReceiptDueError('tip_without_goods', 'Trinkgeld braucht mindestens eine Position mit Betrag.');
|
|
124
167
|
const raus = [];
|
|
125
168
|
for (const e of auftrag.empfaenger) {
|
|
126
169
|
let owner = e.owner;
|
|
127
170
|
if (owner == null) {
|
|
128
171
|
if (standard !== 'owner' && standard !== 'staff') {
|
|
129
|
-
throw new
|
|
172
|
+
throw new ReceiptDueError('tip_recipient_missing', "tipRecipient fehlt ('owner' oder 'staff', wie der angemeldete Kassen-Benutzer).");
|
|
130
173
|
}
|
|
131
174
|
owner = standard === 'owner';
|
|
132
175
|
}
|
|
@@ -136,7 +179,7 @@ function trinkgeldPositionen(auftrag, positionen, standard) {
|
|
|
136
179
|
}
|
|
137
180
|
const teile = splitOwnerTip(e.cents, basis);
|
|
138
181
|
if (teile.length === 0)
|
|
139
|
-
throw new
|
|
182
|
+
throw new ReceiptDueError('tip_without_goods', 'Inhaber-Trinkgeld braucht mindestens eine Position mit Betrag.');
|
|
140
183
|
for (const teil of teile) {
|
|
141
184
|
if (teil.cents !== 0)
|
|
142
185
|
raus.push({ amount: 1, priceOne: teil.cents / 100, vat: teil.vat, tip: 'owner' });
|
|
@@ -6,4 +6,4 @@ export { type SheetLogoSize, type SheetLogo, type LogoDimensions, type SheetBloc
|
|
|
6
6
|
export { rasterizeLogo } from './bild.js';
|
|
7
7
|
export { type BrandMarkRaster, BRAND_MARK_RASTERS, BRAND_MARK_PATHS } from './marke-daten.js';
|
|
8
8
|
export { brandMarkImage } from './marke.js';
|
|
9
|
-
export { type ReceiptDueTip, type ReceiptDueTipRecipient, type ReceiptDueOptions, type ReceiptDueBuckets, type ReceiptDueBreakdown, receiptDueCents, receiptDueBreakdown, } from './due.js';
|
|
9
|
+
export { type ReceiptDueTip, type ReceiptDueTipRecipient, type ReceiptDueOptions, type ReceiptDueBuckets, type ReceiptDueBreakdown, type ReceiptDueErrorReason, receiptDueCents, receiptDueBreakdown, ReceiptDueError, RECEIPT_DUE_ERROR_REASONS, isReceiptDueError, } from './due.js';
|
|
@@ -6,4 +6,4 @@ export { SHEET_LOGO_SIZES, LOGO_MAX_PIXELS, DOTS_PER_CHAR, DOTS_PER_LINE, paperS
|
|
|
6
6
|
export { rasterizeLogo } from './bild.js';
|
|
7
7
|
export { BRAND_MARK_RASTERS, BRAND_MARK_PATHS } from './marke-daten.js';
|
|
8
8
|
export { brandMarkImage } from './marke.js';
|
|
9
|
-
export { receiptDueCents, receiptDueBreakdown, } from './due.js';
|
|
9
|
+
export { receiptDueCents, receiptDueBreakdown, ReceiptDueError, RECEIPT_DUE_ERROR_REASONS, isReceiptDueError, } from './due.js';
|
|
@@ -107,3 +107,4 @@ export declare function invalidStoredPosSettings(stored: {
|
|
|
107
107
|
* weg.
|
|
108
108
|
*/
|
|
109
109
|
export declare function fromStoredArticle(id: string, stored: unknown): PosArticle;
|
|
110
|
+
export { fromStoredLayout, toStoredLayout, fromStoredPrintLogo, toStoredPrintLogo } from './layout.js';
|
package/dist/esm/stored/index.js
CHANGED
|
@@ -215,3 +215,5 @@ export function invalidStoredPosSettings(stored) {
|
|
|
215
215
|
export function fromStoredArticle(id, stored) {
|
|
216
216
|
return fromPosArticlePayload(_storedArticleToWire(id, stored));
|
|
217
217
|
}
|
|
218
|
+
// ---- Zeilenmodell und Drucklogo ----------------------------------------------
|
|
219
|
+
export { fromStoredLayout, toStoredLayout, fromStoredPrintLogo, toStoredPrintLogo } from './layout.js';
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zeilenmodell und Drucklogo in der inneren Form (deutsch) <-> Form 1.0.
|
|
3
|
+
*
|
|
4
|
+
* Die innere Form ist die, die das Backend speichert und am Draht `/api`
|
|
5
|
+
* (Kanal intern) spricht, und die, in der 0.x das Zeilenmodell im Browser
|
|
6
|
+
* zwischengespeichert hat:
|
|
7
|
+
*
|
|
8
|
+
* - Zeilenmodell: `regelwerk` statt `ruleset`, Bannerzeilen mit `ton`
|
|
9
|
+
* (`belegart`, `warnung`) statt `tone` (`receipt_type`, `warning`);
|
|
10
|
+
* - Drucklogo (`createPrintJob`, Parameter `logo`): `stufe`, `pxBreite`,
|
|
11
|
+
* `pxHoehe`, `breite`, `hoehe`, `zeilen` (Base64, MSB zuerst) statt
|
|
12
|
+
* [PrintLogo] (`size`, `pixelWidth`, `pixelHeight`, `raster`).
|
|
13
|
+
*
|
|
14
|
+
* Dieselben Namen wie der Rand des Backends unter `/v3` (Vokabular
|
|
15
|
+
* `createPrintJob`/`getReceiptWithCompany`: `layout.ruleset` <-> `regelwerk`,
|
|
16
|
+
* `lines[].tone` <-> `ton`, Katalog `LAYOUT_TON`, `logo.scale` <-> `stufe` ...).
|
|
17
|
+
* Ein unbekannter Ton geht woertlich durch, wie ihn auch der Rand durchreicht.
|
|
18
|
+
*/
|
|
19
|
+
import type { ReceiptLayout } from '../receipt/layout.js';
|
|
20
|
+
import type { PrintLogo } from '../receipt/layout-escpos.js';
|
|
21
|
+
/**
|
|
22
|
+
* Ein Zeilenmodell in der inneren Form (0.x-Zwischenspeicher, Antwort unter
|
|
23
|
+
* `/api`) als [ReceiptLayout]. Nimmt auch die Form 1.0 an und laesst sie
|
|
24
|
+
* unveraendert; so liest eine Funktion alte und neue Eintraege. Die
|
|
25
|
+
* Reihenfolge der Schluessel bleibt.
|
|
26
|
+
*
|
|
27
|
+
* `null`, wenn es kein ganzes Zeilenmodell ist: fehlt, kein Objekt, keine
|
|
28
|
+
* oder leere `lines`, eine Zeile, die kein Objekt ist, oder kein `paperSize`.
|
|
29
|
+
* Dann baut der Aufrufer neu (`receiptLayoutFromResult` aus Beleg, Firma und
|
|
30
|
+
* `testCashregister`/`testSignature`), und TESTKASSE und die Warnzeilen
|
|
31
|
+
* stehen wieder da. Ein halb lesbares Zeilenmodell gewaenne sonst immer.
|
|
32
|
+
*/
|
|
33
|
+
export declare function fromStoredLayout(stored: unknown): ReceiptLayout | null;
|
|
34
|
+
/**
|
|
35
|
+
* Ein [ReceiptLayout] in der inneren Form, fuer einen Aufruf unter `/api`
|
|
36
|
+
* (`createPrintJob`, `renderBelegPdfAdmin`) oder einen Leser der 0.x-Form,
|
|
37
|
+
* in der Reihenfolge der Schluessel wie 0.31 (`lines`, `paperSize`,
|
|
38
|
+
* `regelwerk`, sofern die Eingabe sie so hat). Ohne diese Uebersetzung liest
|
|
39
|
+
* das Backend den Ton nicht, und der Rahmen der Warnzeilen (TESTKASSE,
|
|
40
|
+
* Sicherheitseinrichtung ausgefallen) und des Belegart-Aufdrucks fiele am
|
|
41
|
+
* Bon und im PDF weg.
|
|
42
|
+
*/
|
|
43
|
+
export declare function toStoredLayout(layout: ReceiptLayout): Record<string, unknown>;
|
|
44
|
+
/**
|
|
45
|
+
* Ein [PrintLogo] in der inneren Form des Druckjobs (`createPrintJob` unter
|
|
46
|
+
* `/api`): dieselben Punkte, die das Paket unter `/v3` als `logo` schickt.
|
|
47
|
+
*/
|
|
48
|
+
export declare function toStoredPrintLogo(logo: PrintLogo): Record<string, unknown>;
|
|
49
|
+
/**
|
|
50
|
+
* Ein Drucklogo in der inneren Form als [PrintLogo]. Die Zeilen sind die
|
|
51
|
+
* Rasterzeilen als Base64 (MSB zuerst, jede Zeile auf volle Bytes
|
|
52
|
+
* aufgefuellt). Wirft `KasseneckValidationError`, wenn Stufe, Masse oder
|
|
53
|
+
* Zeilen nicht zusammenpassen.
|
|
54
|
+
*/
|
|
55
|
+
export declare function fromStoredPrintLogo(stored: unknown): PrintLogo;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { rasterRowsBase64 } from '../printing/escpos.js';
|
|
2
|
+
import { KasseneckValidationError } from '../client/errors.js';
|
|
3
|
+
const istObjekt = (w) => w !== null && typeof w === 'object' && !Array.isArray(w);
|
|
4
|
+
const TON_NACH_1_0 = { belegart: 'receipt_type', warnung: 'warning' };
|
|
5
|
+
const TON_NACH_INNEN = { receipt_type: 'belegart', warning: 'warnung' };
|
|
6
|
+
/**
|
|
7
|
+
* Ein Objekt mit umbenannten Schluesseln, an derselben Stelle: die
|
|
8
|
+
* Reihenfolge bleibt, damit ein gespeicherter Eintrag byte-gleich
|
|
9
|
+
* zurueckkommt (Hash- und Cache-Vergleiche). `werte` ersetzt den Wert eines
|
|
10
|
+
* (alten) Schluessels.
|
|
11
|
+
*/
|
|
12
|
+
function umbenannt(o, namen, werte = {}) {
|
|
13
|
+
const raus = {};
|
|
14
|
+
for (const [k, w] of Object.entries(o)) {
|
|
15
|
+
const neu = namen[k] ?? k;
|
|
16
|
+
// Traegt die Eingabe beide Namen, gewinnt der schon neue.
|
|
17
|
+
if (neu !== k && Object.prototype.hasOwnProperty.call(o, neu))
|
|
18
|
+
continue;
|
|
19
|
+
raus[neu] = werte[k] ? werte[k](w) : w;
|
|
20
|
+
}
|
|
21
|
+
return raus;
|
|
22
|
+
}
|
|
23
|
+
const tonNach = (tabelle) => (w) => (typeof w === 'string' ? tabelle[w] ?? w : w);
|
|
24
|
+
/**
|
|
25
|
+
* Ein Zeilenmodell in der inneren Form (0.x-Zwischenspeicher, Antwort unter
|
|
26
|
+
* `/api`) als [ReceiptLayout]. Nimmt auch die Form 1.0 an und laesst sie
|
|
27
|
+
* unveraendert; so liest eine Funktion alte und neue Eintraege. Die
|
|
28
|
+
* Reihenfolge der Schluessel bleibt.
|
|
29
|
+
*
|
|
30
|
+
* `null`, wenn es kein ganzes Zeilenmodell ist: fehlt, kein Objekt, keine
|
|
31
|
+
* oder leere `lines`, eine Zeile, die kein Objekt ist, oder kein `paperSize`.
|
|
32
|
+
* Dann baut der Aufrufer neu (`receiptLayoutFromResult` aus Beleg, Firma und
|
|
33
|
+
* `testCashregister`/`testSignature`), und TESTKASSE und die Warnzeilen
|
|
34
|
+
* stehen wieder da. Ein halb lesbares Zeilenmodell gewaenne sonst immer.
|
|
35
|
+
*/
|
|
36
|
+
export function fromStoredLayout(stored) {
|
|
37
|
+
if (!istObjekt(stored) || !Array.isArray(stored.lines) || stored.lines.length === 0)
|
|
38
|
+
return null;
|
|
39
|
+
if (!stored.lines.every(istObjekt))
|
|
40
|
+
return null;
|
|
41
|
+
if (typeof stored.paperSize !== 'string' || stored.paperSize === '')
|
|
42
|
+
return null;
|
|
43
|
+
return umbenannt(stored, { regelwerk: 'ruleset' }, {
|
|
44
|
+
lines: (zeilen) => zeilen.map((z) => umbenannt(z, { ton: 'tone' }, { ton: tonNach(TON_NACH_1_0) })),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Ein [ReceiptLayout] in der inneren Form, fuer einen Aufruf unter `/api`
|
|
49
|
+
* (`createPrintJob`, `renderBelegPdfAdmin`) oder einen Leser der 0.x-Form,
|
|
50
|
+
* in der Reihenfolge der Schluessel wie 0.31 (`lines`, `paperSize`,
|
|
51
|
+
* `regelwerk`, sofern die Eingabe sie so hat). Ohne diese Uebersetzung liest
|
|
52
|
+
* das Backend den Ton nicht, und der Rahmen der Warnzeilen (TESTKASSE,
|
|
53
|
+
* Sicherheitseinrichtung ausgefallen) und des Belegart-Aufdrucks fiele am
|
|
54
|
+
* Bon und im PDF weg.
|
|
55
|
+
*/
|
|
56
|
+
export function toStoredLayout(layout) {
|
|
57
|
+
if (!istObjekt(layout) || !Array.isArray(layout.lines)) {
|
|
58
|
+
throw new KasseneckValidationError('toStoredLayout', 'layout ohne lines', 'request');
|
|
59
|
+
}
|
|
60
|
+
return umbenannt(layout, { ruleset: 'regelwerk' }, {
|
|
61
|
+
lines: (zeilen) => zeilen.map((z) => (istObjekt(z) ? umbenannt(z, { tone: 'ton' }, { tone: tonNach(TON_NACH_INNEN) }) : z)),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Ein [PrintLogo] in der inneren Form des Druckjobs (`createPrintJob` unter
|
|
66
|
+
* `/api`): dieselben Punkte, die das Paket unter `/v3` als `logo` schickt.
|
|
67
|
+
*/
|
|
68
|
+
export function toStoredPrintLogo(logo) {
|
|
69
|
+
return {
|
|
70
|
+
stufe: logo.size, pxBreite: logo.pixelWidth, pxHoehe: logo.pixelHeight,
|
|
71
|
+
breite: logo.raster.width, hoehe: logo.raster.height, zeilen: rasterRowsBase64(logo.raster),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
const STUFEN = new Set(['S', 'M', 'L', 'XL']);
|
|
75
|
+
const ganz = (w) => typeof w === 'number' && Number.isInteger(w) && w > 0;
|
|
76
|
+
/**
|
|
77
|
+
* Ein Drucklogo in der inneren Form als [PrintLogo]. Die Zeilen sind die
|
|
78
|
+
* Rasterzeilen als Base64 (MSB zuerst, jede Zeile auf volle Bytes
|
|
79
|
+
* aufgefuellt). Wirft `KasseneckValidationError`, wenn Stufe, Masse oder
|
|
80
|
+
* Zeilen nicht zusammenpassen.
|
|
81
|
+
*/
|
|
82
|
+
export function fromStoredPrintLogo(stored) {
|
|
83
|
+
const name = 'fromStoredPrintLogo';
|
|
84
|
+
if (!istObjekt(stored))
|
|
85
|
+
throw new KasseneckValidationError(name, 'kein Logo-Objekt', 'request');
|
|
86
|
+
const { stufe, pxBreite, pxHoehe, breite, hoehe, zeilen } = stored;
|
|
87
|
+
if (typeof stufe !== 'string' || !STUFEN.has(stufe))
|
|
88
|
+
throw new KasseneckValidationError(name, 'stufe ist nicht S, M, L oder XL', 'request');
|
|
89
|
+
if (!ganz(pxBreite) || !ganz(pxHoehe) || !ganz(breite) || !ganz(hoehe)) {
|
|
90
|
+
throw new KasseneckValidationError(name, 'pxBreite, pxHoehe, breite und hoehe muessen ganze Zahlen > 0 sein', 'request');
|
|
91
|
+
}
|
|
92
|
+
if (typeof zeilen !== 'string')
|
|
93
|
+
throw new KasseneckValidationError(name, 'zeilen fehlt', 'request');
|
|
94
|
+
let binaer;
|
|
95
|
+
try {
|
|
96
|
+
binaer = atob(zeilen);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new KasseneckValidationError(name, 'zeilen ist kein Base64', 'request');
|
|
100
|
+
}
|
|
101
|
+
const byteJeZeile = Math.ceil(breite / 8);
|
|
102
|
+
if (binaer.length !== byteJeZeile * hoehe)
|
|
103
|
+
throw new KasseneckValidationError(name, 'zeilen passen nicht zu breite und hoehe', 'request');
|
|
104
|
+
const dots = new Uint8Array(breite * hoehe);
|
|
105
|
+
for (let y = 0; y < hoehe; y++) {
|
|
106
|
+
for (let x = 0; x < breite; x++) {
|
|
107
|
+
if (binaer.charCodeAt(y * byteJeZeile + (x >> 3)) & (0x80 >> (x & 7)))
|
|
108
|
+
dots[y * breite + x] = 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const raster = { width: breite, height: hoehe, dots };
|
|
112
|
+
return { size: stufe, pixelWidth: pxBreite, pixelHeight: pxHoehe, raster };
|
|
113
|
+
}
|
package/dist/esm/version.d.ts
CHANGED
package/dist/esm/version.js
CHANGED