@kreiseck/kasseneck-api 0.21.0 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +37 -2
  3. package/dist/cjs/client/errors.d.ts +0 -1
  4. package/dist/cjs/client/errors.js +24 -1
  5. package/dist/cjs/rechnung/api.d.ts +3 -1
  6. package/dist/cjs/rechnung/api.js +1 -0
  7. package/dist/cjs/rechnung/endpunkte.d.ts +12 -1
  8. package/dist/cjs/rechnung/endpunkte.js +56 -3
  9. package/dist/cjs/rechnung/index.d.ts +3 -2
  10. package/dist/cjs/rechnung/index.js +5 -1
  11. package/dist/cjs/rechnung/summen.d.ts +41 -0
  12. package/dist/cjs/rechnung/summen.js +81 -0
  13. package/dist/cjs/rechnung/texte.d.ts +4 -0
  14. package/dist/cjs/rechnung/texte.js +8 -0
  15. package/dist/cjs/rechnung/typen.d.ts +51 -7
  16. package/dist/cjs/rechnung/vertrag.js +6 -0
  17. package/dist/esm/client/errors.d.ts +0 -1
  18. package/dist/esm/client/errors.js +24 -1
  19. package/dist/esm/rechnung/api.d.ts +3 -1
  20. package/dist/esm/rechnung/api.js +2 -1
  21. package/dist/esm/rechnung/endpunkte.d.ts +12 -1
  22. package/dist/esm/rechnung/endpunkte.js +55 -3
  23. package/dist/esm/rechnung/index.d.ts +3 -2
  24. package/dist/esm/rechnung/index.js +2 -1
  25. package/dist/esm/rechnung/summen.d.ts +41 -0
  26. package/dist/esm/rechnung/summen.js +77 -0
  27. package/dist/esm/rechnung/texte.d.ts +4 -0
  28. package/dist/esm/rechnung/texte.js +8 -0
  29. package/dist/esm/rechnung/typen.d.ts +51 -7
  30. package/dist/esm/rechnung/vertrag.js +6 -0
  31. package/fixtures/hobex-hps-codes.json +1 -1
  32. package/fixtures/kasse-texte.json +1 -1
  33. package/fixtures/oberflaeche.json +9 -1
  34. package/fixtures/rechnung-api.schema.json +4 -1
  35. package/fixtures/rechnung-summen.json +154 -0
  36. package/fixtures/rechnung-texte.json +9 -1
  37. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,65 @@ Was vor 0.7.0 geschah, steht in der Commit-Historie (`git log`); ab hier wird
4
4
  es hier geführt. Ein Eintrag nennt die Änderung **und ihren Grund** —
5
5
  nur der Grund überlebt den nächsten Umbau.
6
6
 
7
+ ## 0.22.0
8
+
9
+ ### Rechnung: Brutto bleibt Brutto, Hinweise kommen an, Probelauf
10
+
11
+ **Anlass:** Rückmeldung eines Shops zur Rechnungs-API (16.09.2026).
12
+
13
+ - **`issueInvoice` gab `notice` nicht weiter.** Der Typ sah die Liste vor, der
14
+ Aufruf übernahm sie nie — eine ig. Lieferung meldete deshalb nie, dass eine
15
+ Zusammenfassende Meldung fällig ist. Behoben.
16
+ - **`recordInvoicePayment` liefert `notice` jetzt als Liste** wie
17
+ `issueInvoice` (bisher ein einzelnes Objekt). **Bricht die Form:** wer
18
+ `result.notice.code` las, liest jetzt `result.notice?.[0]?.code`. Ein Server,
19
+ der noch ein Objekt schickt, wird zur Liste gemacht.
20
+ - **Neu: `rechnungSummen(items, priceMode, taxScheme?)`** rechnet die Summen so,
21
+ wie der Server sie ausstellt. Im Brutto-Modus ist das Brutto je Satz der
22
+ vereinbarte Preis: Netto = round(B × 100 / (100 + Satz)), USt = B − Netto.
23
+ Der Server rechnete bis dahin Netto und USt getrennt und setzte das Brutto neu
24
+ zusammen — 14,79 € + 15,00 € zu 20 % wurden 29,80 €. Die Prüffälle stehen in
25
+ `fixtures/rechnung-summen.json`; Server und Dart-Zwilling prüfen dagegen.
26
+ - **Neu: `previewInvoice(anfrage)`** — Probelauf von `issueInvoice`
27
+ (`dryRun: true` im Vertrag): prüft und rechnet wie das Ausstellen, schreibt
28
+ nichts fest und verbraucht den `idempotencyKey` nicht. Antwort `preview` mit
29
+ Summen, Steuerfall samt Grund, Sprache, Marke, E-Rechnung — dazu die Hinweise.
30
+ - **`totals.byRate[]` trägt `grossCents`.** Die Summen sind auch bei
31
+ Gutschriften positiv; das steht jetzt am Typ.
32
+ - **Texte:** `pdf.tabelle.einzelBrutto`, `pdf.tabelle.betragBrutto`,
33
+ `pdf.summe.darinUst` — das PDF einer Brutto-Rechnung zeigt Brutto-Spalten,
34
+ deren Zeilen die Summe ergeben. Dazu `pdf.position.rabatt`: ein Zeilenrabatt
35
+ stand bisher nirgends auf dem Blatt, „2 × 24,90 = 44,82" ging für den Leser
36
+ nicht auf.
37
+ - **Kennung als Text:** `getInvoice('…')` und `getCustomer('…')` werfen vor dem
38
+ Senden `KasseneckValidationError` (`scope: 'request'`). Bisher wurde der Text
39
+ Zeichen für Zeichen zu Feldern `0`, `1`, … und der Server konnte nur
40
+ „Bitte Eingaben prüfen." antworten.
41
+ - **Feldfehler in der Meldung:** `KasseneckApiError.message` hängt bis zu fünf
42
+ Einträge aus `errors[]` an (`[items[0].vatRate: …]`); `serverMessage` und
43
+ `details` bleiben unverändert.
44
+
45
+ ## 0.21.0
46
+
47
+ ### Rechnung: Kataloge für den abgeleiteten Steuerfall
48
+
49
+ **Anlass:** Der Server leitet den Steuerfall jetzt selbst ab, statt ihn zu
50
+ glauben (Kundenland, Kundenart, UID, Ware oder Leistung).
51
+
52
+ - `taxScheme` ist in `issueInvoice` **optional**; eine Angabe prüft der Server
53
+ gegen die Ableitung (`tax_scheme_mismatch`).
54
+ - Neue Fälle `domesticReverseCharge`, `oss` und `outsideScope`; Katalog der elf
55
+ Gründe für den Übergang der Steuerschuld im Inland
56
+ (`REVERSE_CHARGE_REASONS`, mit Fundstelle und Schwelle).
57
+ `oss` steht im Katalog, **kann aber noch nicht ausgestellt werden** —
58
+ die Ländersätze folgen.
59
+ - Positionen tragen `kind` (`goods`/`service`), weil ig. Lieferung und Reverse
60
+ Charge sich genau daran unterscheiden.
61
+ - `notice` ist an `issueInvoice` eine **Liste**: eine bar bezahlte ig. Lieferung
62
+ trägt zwei Hinweise zugleich.
63
+ - Die Rechnungssicht trägt `reverseChargeReason` und `taxCountry`; neuer
64
+ Befreiungstext für den nicht steuerbaren Umsatz (E-Rechnung, BR-O-10).
65
+
7
66
  ## 0.20.0
8
67
 
9
68
  ### Rechnung: richtige Steuerhinweise, Barumsatz auch mit Karte
package/README.md CHANGED
@@ -547,6 +547,41 @@ await rechnungen.recordInvoicePayment({
547
547
  `reference` wird gespeichert, aber **nicht gedruckt**; Kartendaten gehören
548
548
  ohnehin nicht auf eine Rechnung.
549
549
 
550
+ **Vorab rechnen.** Wer kassiert, bevor die Rechnung entsteht, braucht den
551
+ Betrag, den die Rechnung später ausweist. `rechnungSummen` rechnet ihn genau
552
+ wie der Server; `previewInvoice` fragt den Server selbst — ein Probelauf, der
553
+ prüft wie das Ausstellen, aber nichts festschreibt und den `idempotencyKey`
554
+ nicht verbraucht:
555
+
556
+ ```ts
557
+ import { rechnungSummen } from '@kreiseck/kasseneck-api/rechnung';
558
+
559
+ const posten = [
560
+ { description: 'Maniküre', quantity: 1, unitPriceCents: 1479, vatRate: 20 as const },
561
+ { description: 'Lack', quantity: 1, unitPriceCents: 1500, vatRate: 20 as const },
562
+ ];
563
+ rechnungSummen(posten, 'gross');
564
+ // { netCents: 2483, vatCents: 496, grossCents: 2979, byRate: [{ rate: 20, … }] }
565
+
566
+ const anfrage = { idempotencyKey: `bestellung-${bestellnummer}`, customerId: kunde.id,
567
+ priceMode: 'gross' as const, serviceStart: '2026-09-16', items: posten };
568
+ const { preview, notice } = await rechnungen.previewInvoice(anfrage);
569
+ // preview.totals, preview.taxScheme, preview.taxSchemeReason — dann:
570
+ await rechnungen.issueInvoice(anfrage);
571
+ ```
572
+
573
+ Im **Brutto-Modus** ist das Brutto je Satz der vereinbarte Preis: Netto =
574
+ round(B × 100 / (100 + Satz)), USt = B − Netto. Im **Netto-Modus** wird die USt
575
+ je Satz aus der Nettosumme gerundet. Gerundet wird kaufmännisch (halber Cent
576
+ aufwärts), je Satz, dann summiert. Die Prüffälle liegen in
577
+ `fixtures/rechnung-summen.json`. Die Summen sind auch bei Gutschriften positiv —
578
+ das Vorzeichen steht im Belegtyp (`docType: 'GU'`).
579
+
580
+ **Hinweise** (`notice`) sind immer eine Liste — bei `issueInvoice`,
581
+ `previewInvoice` und `recordInvoicePayment`. Eine ig. Lieferung trägt
582
+ `recapitulative_statement_due` (Zusammenfassende Meldung), eine bar bezahlte
583
+ Rechnung zusätzlich `cash_receipt_required`.
584
+
550
585
  **Barumsatz ist nicht nur Bargeld.** Als Barzahlung gilt auch die Karte **vor
551
586
  Ort** an der Kasse (§ 131b Abs. 1 Z 3 UStG) — dieselbe Karte im Internet
552
587
  dagegen nicht. Weil `card` und `online` beides sein können, sagt es das
@@ -557,8 +592,8 @@ payment: { method: 'card', onSite: true } // Terminal an der Kasse
557
592
  payment: { method: 'card' } // Kartenzahlung im Shop
558
593
  ```
559
594
 
560
- Bei `cash` (immer) und bei `onSite: true` trägt die Antwort
561
- `notice.code = 'cash_receipt_required'`: ein Barumsatz braucht einen Beleg
595
+ Bei `cash` (immer) und bei `onSite: true` trägt die Antwort in der Liste
596
+ `notice` den Eintrag `cash_receipt_required`: ein Barumsatz braucht einen Beleg
562
597
  (§ 132a BAO), bei Registrierkassenpflicht über die Registrierkasse — der
563
598
  Vermerk an der Rechnung ersetzt ihn nicht. `transfer` mit `onSite` ist ein
564
599
  Feldfehler, eine Überweisung erfolgt nicht vor Ort.
@@ -87,7 +87,6 @@ export declare function causeDigest(ursache: unknown, geheimnisse: readonly stri
87
87
  * demselben Grund **keinen** Vorgabewert.
88
88
  */
89
89
  export declare function fehlerDetails(daten: unknown, geheimnisse: readonly string[]): Record<string, unknown>;
90
- /** Fachlicher Fehler: HTTP 200, aber `status: 'error'` im Rumpf. */
91
90
  export declare class KasseneckApiError extends Error {
92
91
  readonly name = "KasseneckApiError";
93
92
  /** Aufgerufene Backend-Funktion, z. B. `createReceipt`. */
@@ -174,6 +174,29 @@ function sieben(wert, geheimnisse, tiefe) {
174
174
  return raus;
175
175
  }
176
176
  /** Fachlicher Fehler: HTTP 200, aber `status: 'error'` im Rumpf. */
177
+ /**
178
+ * Die Feldfehler einer Pruefantwort (`errors: [{ field, message }]`) als
179
+ * Anhang der Fehlermeldung — sonst stuende im Log nur „Bitte Eingaben
180
+ * pruefen." und niemand wuesste, welches Feld gemeint ist. Hoechstens fuenf;
181
+ * vollstaendig bleiben sie in `details.errors`.
182
+ */
183
+ function feldHinweis(details) {
184
+ const roh = details['errors'];
185
+ if (!Array.isArray(roh))
186
+ return '';
187
+ const teile = [];
188
+ for (const e of roh) {
189
+ if (e === null || typeof e !== 'object')
190
+ continue;
191
+ const { field, message } = e;
192
+ if (typeof field === 'string' && typeof message === 'string')
193
+ teile.push(`${field}: ${message}`);
194
+ }
195
+ if (!teile.length)
196
+ return '';
197
+ const mehr = teile.length > 5 ? ` (+${teile.length - 5} weitere)` : '';
198
+ return ` [${teile.slice(0, 5).join('; ')}${mehr}]`;
199
+ }
177
200
  class KasseneckApiError extends Error {
178
201
  name = 'KasseneckApiError';
179
202
  /** Aufgerufene Backend-Funktion, z. B. `createReceipt`. */
@@ -197,7 +220,7 @@ class KasseneckApiError extends Error {
197
220
  */
198
221
  details;
199
222
  constructor(functionName, serverMessage, details = {}, code) {
200
- super(`${functionName} fehlgeschlagen: ${serverMessage}`);
223
+ super(`${functionName} fehlgeschlagen: ${serverMessage}${feldHinweis(details)}`);
201
224
  this.functionName = functionName;
202
225
  this.serverMessage = serverMessage;
203
226
  this.details = details;
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { type FetchLike } from '../client/transport.js';
8
8
  import type { EInvoiceFormat, InvoiceLanguage } from './vertrag.js';
9
- import type { Brand, CancelInvoiceRequest, CancelResult, CreditNoteRequest, CreditNoteResult, Customer, CustomerInput, CustomerPage, CustomerSearch, InvoiceDetail, InvoiceListQuery, InvoicePage, InvoiceSetupStatus, IssueInvoiceRequest, IssueResult, RecordPaymentRequest, RecordPaymentResult } from './typen.js';
9
+ import type { Brand, CancelInvoiceRequest, CancelResult, CreditNoteRequest, CreditNoteResult, Customer, CustomerInput, CustomerPage, CustomerSearch, InvoiceDetail, InvoiceListQuery, InvoicePage, InvoiceSetupStatus, IssueInvoiceRequest, IssueResult, PreviewResult, RecordPaymentRequest, RecordPaymentResult } from './typen.js';
10
10
  export interface RechnungApiOptions {
11
11
  /** `api_key` des Kontos (`kr_live_…` / `kr_test_…`). Gehoert auf einen Server. */
12
12
  apiKey: string;
@@ -29,6 +29,8 @@ export interface RechnungApi {
29
29
  updateCustomer(customerId: string, patch: Partial<CustomerInput>): Promise<Customer>;
30
30
  searchCustomers(suche: CustomerSearch): Promise<CustomerPage>;
31
31
  issueInvoice(anfrage: IssueInvoiceRequest): Promise<IssueResult>;
32
+ /** Probelauf: pruefen und rechnen wie `issueInvoice`, ohne auszustellen. */
33
+ previewInvoice(anfrage: IssueInvoiceRequest): Promise<PreviewResult>;
32
34
  cancelInvoice(anfrage: CancelInvoiceRequest): Promise<CancelResult>;
33
35
  createCreditNote(anfrage: CreditNoteRequest): Promise<CreditNoteResult>;
34
36
  getInvoice(kennung: {
@@ -25,6 +25,7 @@ function createRechnungApi(optionen) {
25
25
  updateCustomer: (id, patch) => (0, endpunkte_js_1.updateCustomer)(rufen, id, patch),
26
26
  searchCustomers: (suche) => (0, endpunkte_js_1.searchCustomers)(rufen, suche),
27
27
  issueInvoice: (anfrage) => (0, endpunkte_js_1.issueInvoice)(rufen, anfrage),
28
+ previewInvoice: (anfrage) => (0, endpunkte_js_1.previewInvoice)(rufen, anfrage),
28
29
  cancelInvoice: (anfrage) => (0, endpunkte_js_1.cancelInvoice)(rufen, anfrage),
29
30
  createCreditNote: (anfrage) => (0, endpunkte_js_1.createCreditNote)(rufen, anfrage),
30
31
  getInvoice: (kennung) => (0, endpunkte_js_1.getInvoice)(rufen, kennung),
@@ -18,7 +18,7 @@
18
18
  */
19
19
  import type { InternerBinaerTransport, InternerTransport } from '../client/aufrufe.js';
20
20
  import type { EInvoiceFormat, InvoiceLanguage } from './vertrag.js';
21
- import type { Brand, CancelInvoiceRequest, CancelResult, CreditNoteRequest, CreditNoteResult, Customer, CustomerInput, CustomerPage, CustomerSearch, InvoiceDetail, InvoiceListQuery, InvoicePage, InvoiceSetupStatus, IssueInvoiceRequest, IssueResult, RecordPaymentRequest, RecordPaymentResult } from './typen.js';
21
+ import type { Brand, CancelInvoiceRequest, CancelResult, CreditNoteRequest, CreditNoteResult, Customer, CustomerInput, CustomerPage, CustomerSearch, InvoiceDetail, InvoiceListQuery, InvoicePage, InvoiceSetupStatus, IssueInvoiceRequest, IssueResult, PreviewResult, RecordPaymentRequest, RecordPaymentResult } from './typen.js';
22
22
  export declare function createCustomer(rufen: InternerTransport, customer: CustomerInput, optionen?: {
23
23
  idempotencyKey?: string;
24
24
  }): Promise<Customer>;
@@ -30,6 +30,17 @@ export declare function getCustomer(rufen: InternerTransport, kennung: {
30
30
  export declare function updateCustomer(rufen: InternerTransport, customerId: string, patch: Partial<CustomerInput>): Promise<Customer>;
31
31
  export declare function searchCustomers(rufen: InternerTransport, suche: CustomerSearch): Promise<CustomerPage>;
32
32
  export declare function issueInvoice(rufen: InternerTransport, anfrage: IssueInvoiceRequest): Promise<IssueResult>;
33
+ /**
34
+ * Probelauf von `issueInvoice`: dieselbe Anfrage wird geprueft und gerechnet
35
+ * wie beim Ausstellen, aber nichts festgeschrieben. Die Antwort nennt Summen,
36
+ * Steuerfall, Sprache, Marke und die Hinweise — oder scheitert mit demselben
37
+ * Fehlercode, mit dem das Ausstellen scheitern wuerde.
38
+ *
39
+ * Der `idempotencyKey` wird nicht verbraucht: dieselbe Anfrage laesst sich
40
+ * danach unveraendert ausstellen. Verbindlich ist das Ausstellen — zwischen
41
+ * Probelauf und Ausstellen kann sich der Kunde oder das Konto aendern.
42
+ */
43
+ export declare function previewInvoice(rufen: InternerTransport, anfrage: IssueInvoiceRequest): Promise<PreviewResult>;
33
44
  export declare function cancelInvoice(rufen: InternerTransport, anfrage: CancelInvoiceRequest): Promise<CancelResult>;
34
45
  export declare function createCreditNote(rufen: InternerTransport, anfrage: CreditNoteRequest): Promise<CreditNoteResult>;
35
46
  export declare function getInvoice(rufen: InternerTransport, kennung: {
@@ -23,6 +23,7 @@ exports.getCustomer = getCustomer;
23
23
  exports.updateCustomer = updateCustomer;
24
24
  exports.searchCustomers = searchCustomers;
25
25
  exports.issueInvoice = issueInvoice;
26
+ exports.previewInvoice = previewInvoice;
26
27
  exports.cancelInvoice = cancelInvoice;
27
28
  exports.createCreditNote = createCreditNote;
28
29
  exports.getInvoice = getInvoice;
@@ -55,6 +56,33 @@ function seite(aufruf, daten, feld) {
55
56
  return { eintraege: o[feld], nextCursor: typeof o['nextCursor'] === 'string' ? o['nextCursor'] : null };
56
57
  }
57
58
  const zahl = (wert) => (typeof wert === 'number' && Number.isFinite(wert) ? wert : 0);
59
+ /**
60
+ * Die Hinweise einer Antwort (`data.notice`) — immer eine Liste, `undefined`
61
+ * ohne Hinweis. Ein einzelnes Objekt (Server vor der Vereinheitlichung) wird
62
+ * zur Liste.
63
+ *
64
+ * Ein unbrauchbarer Eintrag wird uebergangen, nicht geworfen: der Aufruf hat
65
+ * schon gewirkt (die Rechnung ist ausgestellt, die Zahlung gebucht). Ein
66
+ * Fehler an dieser Stelle liesse den Aufrufer glauben, es sei nichts
67
+ * entstanden — und eine Wiederholung liefert die Hinweise nicht noch einmal.
68
+ */
69
+ function hinweise(daten) {
70
+ const roh = objekt(daten)['notice'];
71
+ if (roh === undefined || roh === null)
72
+ return undefined;
73
+ const liste = (Array.isArray(roh) ? roh : [roh]).filter((h) => typeof objekt(h)['code'] === 'string' && typeof objekt(h)['message'] === 'string');
74
+ return liste.length ? liste : undefined;
75
+ }
76
+ /**
77
+ * Eine Kennung muss ein Objekt sein (`{ invoiceId }`, `{ customerId }` …). Ein
78
+ * blosser Text wuerde sonst Zeichen fuer Zeichen zu Feldern `0`, `1`, …
79
+ * zerlegt, und der Server koennte nur „Bitte Eingaben pruefen" antworten.
80
+ */
81
+ function kennungVerlangen(aufruf, kennung, felder) {
82
+ if (kennung === null || typeof kennung !== 'object' || Array.isArray(kennung)) {
83
+ throw new errors_js_1.KasseneckValidationError(aufruf, `Kennung als Objekt erwartet: ${felder}`, 'request');
84
+ }
85
+ }
58
86
  /** Anfrageobjekt als Nutzlast — flache Kopie, damit der Aufrufer sein Objekt behaelt. */
59
87
  const nutzlast = (anfrage) => ({ ...anfrage });
60
88
  // ---- Kunden -----------------------------------------------------------------
@@ -66,6 +94,7 @@ async function createCustomer(rufen, customer, optionen = {}) {
66
94
  return pflichtObjekt('createCustomer', daten, 'customer');
67
95
  }
68
96
  async function getCustomer(rufen, kennung) {
97
+ kennungVerlangen('getCustomer', kennung, '{ customerId } oder { externalId }');
69
98
  const daten = await rufen('getCustomer', nutzlast(kennung));
70
99
  return pflichtObjekt('getCustomer', daten, 'customer');
71
100
  }
@@ -81,10 +110,32 @@ async function searchCustomers(rufen, suche) {
81
110
  // ---- Rechnungen -------------------------------------------------------------
82
111
  async function issueInvoice(rufen, anfrage) {
83
112
  const daten = await rufen('issueInvoice', nutzlast(anfrage));
84
- return {
113
+ const ergebnis = {
85
114
  invoice: pflichtObjekt('issueInvoice', daten, 'invoice'),
86
115
  replayed: objekt(daten)['replayed'] === true,
87
116
  };
117
+ const notice = hinweise(daten);
118
+ if (notice)
119
+ ergebnis.notice = notice;
120
+ return ergebnis;
121
+ }
122
+ /**
123
+ * Probelauf von `issueInvoice`: dieselbe Anfrage wird geprueft und gerechnet
124
+ * wie beim Ausstellen, aber nichts festgeschrieben. Die Antwort nennt Summen,
125
+ * Steuerfall, Sprache, Marke und die Hinweise — oder scheitert mit demselben
126
+ * Fehlercode, mit dem das Ausstellen scheitern wuerde.
127
+ *
128
+ * Der `idempotencyKey` wird nicht verbraucht: dieselbe Anfrage laesst sich
129
+ * danach unveraendert ausstellen. Verbindlich ist das Ausstellen — zwischen
130
+ * Probelauf und Ausstellen kann sich der Kunde oder das Konto aendern.
131
+ */
132
+ async function previewInvoice(rufen, anfrage) {
133
+ const daten = await rufen('issueInvoice', { ...nutzlast(anfrage), dryRun: true });
134
+ const ergebnis = { preview: pflichtObjekt('issueInvoice', daten, 'preview') };
135
+ const notice = hinweise(daten);
136
+ if (notice)
137
+ ergebnis.notice = notice;
138
+ return ergebnis;
88
139
  }
89
140
  async function cancelInvoice(rufen, anfrage) {
90
141
  const daten = await rufen('cancelInvoice', nutzlast(anfrage));
@@ -104,6 +155,7 @@ async function createCreditNote(rufen, anfrage) {
104
155
  };
105
156
  }
106
157
  async function getInvoice(rufen, kennung) {
158
+ kennungVerlangen('getInvoice', kennung, '{ invoiceId } oder { number }');
107
159
  const daten = await rufen('getInvoice', nutzlast(kennung));
108
160
  return pflichtObjekt('getInvoice', daten, 'invoice');
109
161
  }
@@ -159,8 +211,9 @@ async function recordInvoicePayment(rufen, anfrage) {
159
211
  payment: pflichtObjekt('recordInvoicePayment', daten, 'payment'),
160
212
  replayed: roh['replayed'] === true,
161
213
  };
162
- if (roh['notice'])
163
- ergebnis.notice = roh['notice'];
214
+ const notice = hinweise(daten);
215
+ if (notice)
216
+ ergebnis.notice = notice;
164
217
  return ergebnis;
165
218
  }
166
219
  /** Die Marken des Kontos — die Kennung geht als `brandId` in `issueInvoice`. */
@@ -13,7 +13,8 @@
13
13
  export { createRechnungApi, type RechnungApi, type RechnungApiOptions } from './api.js';
14
14
  export { rechnungKeyAuth, type RechnungKeyAuthOptions } from './auth.js';
15
15
  export { istRechnungFehler, rechnungFehlerCode, rechnungFeldFehler, type RechnungFeldFehler, } from './fehler.js';
16
- export { cancelInvoice, createCreditNote, createCustomer, getCustomer, getInvoice, getInvoicePdf, getInvoiceSetupStatus, getInvoiceXml, issueInvoice, listBrands, listInvoices, recordInvoicePayment, searchCustomers, updateCustomer, } from './endpunkte.js';
17
- export type { Brand, CancelInvoiceRequest, CancelResult, CreditNoteRequest, CreditNoteResult, Customer, CustomerInput, CustomerPage, CustomerSearch, EInvoiceStatus, Invoice, InvoiceDetail, InvoiceItem, InvoiceItemInput, InvoiceListQuery, InvoicePage, InvoiceRecipient, InvoiceSetupGap, InvoiceSetupStatus, InvoiceTotals, InvoiceNotice, InvoicePayment, IssueInvoiceRequest, IssueResult, PaymentInput, RecordPaymentRequest, RecordPaymentResult, } from './typen.js';
16
+ export { cancelInvoice, createCreditNote, createCustomer, getCustomer, getInvoice, getInvoicePdf, getInvoiceSetupStatus, getInvoiceXml, issueInvoice, listBrands, listInvoices, previewInvoice, recordInvoicePayment, searchCustomers, updateCustomer, } from './endpunkte.js';
17
+ export type { Brand, CancelInvoiceRequest, CancelResult, CreditNoteRequest, CreditNoteResult, Customer, CustomerInput, CustomerPage, CustomerSearch, EInvoiceStatus, Invoice, InvoiceDetail, InvoiceItem, InvoiceItemInput, InvoiceListQuery, InvoicePage, InvoiceRecipient, InvoiceSetupGap, InvoiceSetupStatus, InvoiceTotals, InvoiceNotice, InvoicePayment, InvoicePreview, InvoiceRateTotals, IssueInvoiceRequest, IssueResult, PaymentInput, PreviewResult, RecordPaymentRequest, RecordPaymentResult, } from './typen.js';
18
18
  export { RECHNUNG_TEXTE, rechnungText, type RechnungTextSchluessel } from './texte.js';
19
+ export { rechnungSummen, STEUERFREIE_FAELLE, type SummenPosition } from './summen.js';
19
20
  export { RECHNUNG_VERTRAG_VERSION, RECHNUNG_AUFRUFE, INVOICE_ERROR_CODES, CREDIT_NOTE_REASONS, TAX_SCHEMES, PRICE_MODES, VAT_RATES, CUSTOMER_TYPES, INVOICE_LIST_STATUS, DOC_TYPES, EINVOICE_FORMATS, INVOICE_LANGUAGES, INVOICE_PAYMENT_METHODS, ITEM_KINDS, REVERSE_CHARGE_REASONS, INVOICE_NOTICE_CODES, INVOICE_UNITS, RECHNUNG_EINHEITEN_CODES, INVOICE_SETUP_REQUIREMENTS, KUNDE_FELDER, POSITION_FELDER, RECHNUNG_ANFRAGEN, RECHNUNG_GENAU_EINS, RECHNUNG_MINDESTENS_EINS, type RechnungAufruf, type InvoiceErrorCode, type CreditNoteReason, type TaxScheme, type PriceMode, type VatRatePercent, type CustomerType, type InvoiceListStatus, type DocType, type EInvoiceFormat, type InvoiceLanguage, type InvoicePaymentMethod, type ItemKind, type ReverseChargeReason, type InvoiceNoticeCode, type InvoiceUnit, type InvoiceSetupRequirement, type Format, type Feld, } from './vertrag.js';
@@ -12,7 +12,7 @@
12
12
  * Benutzung des Clients.
13
13
  */
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.RECHNUNG_MINDESTENS_EINS = exports.RECHNUNG_GENAU_EINS = exports.RECHNUNG_ANFRAGEN = exports.POSITION_FELDER = exports.KUNDE_FELDER = exports.INVOICE_SETUP_REQUIREMENTS = exports.RECHNUNG_EINHEITEN_CODES = exports.INVOICE_UNITS = exports.INVOICE_NOTICE_CODES = exports.REVERSE_CHARGE_REASONS = exports.ITEM_KINDS = exports.INVOICE_PAYMENT_METHODS = exports.INVOICE_LANGUAGES = exports.EINVOICE_FORMATS = exports.DOC_TYPES = exports.INVOICE_LIST_STATUS = exports.CUSTOMER_TYPES = exports.VAT_RATES = exports.PRICE_MODES = exports.TAX_SCHEMES = exports.CREDIT_NOTE_REASONS = exports.INVOICE_ERROR_CODES = exports.RECHNUNG_AUFRUFE = exports.RECHNUNG_VERTRAG_VERSION = exports.rechnungText = exports.RECHNUNG_TEXTE = exports.updateCustomer = exports.searchCustomers = exports.recordInvoicePayment = exports.listInvoices = exports.listBrands = exports.issueInvoice = exports.getInvoiceXml = exports.getInvoiceSetupStatus = exports.getInvoicePdf = exports.getInvoice = exports.getCustomer = exports.createCustomer = exports.createCreditNote = exports.cancelInvoice = exports.rechnungFeldFehler = exports.rechnungFehlerCode = exports.istRechnungFehler = exports.rechnungKeyAuth = exports.createRechnungApi = void 0;
15
+ exports.RECHNUNG_MINDESTENS_EINS = exports.RECHNUNG_GENAU_EINS = exports.RECHNUNG_ANFRAGEN = exports.POSITION_FELDER = exports.KUNDE_FELDER = exports.INVOICE_SETUP_REQUIREMENTS = exports.RECHNUNG_EINHEITEN_CODES = exports.INVOICE_UNITS = exports.INVOICE_NOTICE_CODES = exports.REVERSE_CHARGE_REASONS = exports.ITEM_KINDS = exports.INVOICE_PAYMENT_METHODS = exports.INVOICE_LANGUAGES = exports.EINVOICE_FORMATS = exports.DOC_TYPES = exports.INVOICE_LIST_STATUS = exports.CUSTOMER_TYPES = exports.VAT_RATES = exports.PRICE_MODES = exports.TAX_SCHEMES = exports.CREDIT_NOTE_REASONS = exports.INVOICE_ERROR_CODES = exports.RECHNUNG_AUFRUFE = exports.RECHNUNG_VERTRAG_VERSION = exports.STEUERFREIE_FAELLE = exports.rechnungSummen = exports.rechnungText = exports.RECHNUNG_TEXTE = exports.updateCustomer = exports.searchCustomers = exports.recordInvoicePayment = exports.previewInvoice = exports.listInvoices = exports.listBrands = exports.issueInvoice = exports.getInvoiceXml = exports.getInvoiceSetupStatus = exports.getInvoicePdf = exports.getInvoice = exports.getCustomer = exports.createCustomer = exports.createCreditNote = exports.cancelInvoice = exports.rechnungFeldFehler = exports.rechnungFehlerCode = exports.istRechnungFehler = exports.rechnungKeyAuth = exports.createRechnungApi = void 0;
16
16
  var api_js_1 = require("./api.js");
17
17
  Object.defineProperty(exports, "createRechnungApi", { enumerable: true, get: function () { return api_js_1.createRechnungApi; } });
18
18
  var auth_js_1 = require("./auth.js");
@@ -33,12 +33,16 @@ Object.defineProperty(exports, "getInvoiceXml", { enumerable: true, get: functio
33
33
  Object.defineProperty(exports, "issueInvoice", { enumerable: true, get: function () { return endpunkte_js_1.issueInvoice; } });
34
34
  Object.defineProperty(exports, "listBrands", { enumerable: true, get: function () { return endpunkte_js_1.listBrands; } });
35
35
  Object.defineProperty(exports, "listInvoices", { enumerable: true, get: function () { return endpunkte_js_1.listInvoices; } });
36
+ Object.defineProperty(exports, "previewInvoice", { enumerable: true, get: function () { return endpunkte_js_1.previewInvoice; } });
36
37
  Object.defineProperty(exports, "recordInvoicePayment", { enumerable: true, get: function () { return endpunkte_js_1.recordInvoicePayment; } });
37
38
  Object.defineProperty(exports, "searchCustomers", { enumerable: true, get: function () { return endpunkte_js_1.searchCustomers; } });
38
39
  Object.defineProperty(exports, "updateCustomer", { enumerable: true, get: function () { return endpunkte_js_1.updateCustomer; } });
39
40
  var texte_js_1 = require("./texte.js");
40
41
  Object.defineProperty(exports, "RECHNUNG_TEXTE", { enumerable: true, get: function () { return texte_js_1.RECHNUNG_TEXTE; } });
41
42
  Object.defineProperty(exports, "rechnungText", { enumerable: true, get: function () { return texte_js_1.rechnungText; } });
43
+ var summen_js_1 = require("./summen.js");
44
+ Object.defineProperty(exports, "rechnungSummen", { enumerable: true, get: function () { return summen_js_1.rechnungSummen; } });
45
+ Object.defineProperty(exports, "STEUERFREIE_FAELLE", { enumerable: true, get: function () { return summen_js_1.STEUERFREIE_FAELLE; } });
42
46
  var vertrag_js_1 = require("./vertrag.js");
43
47
  Object.defineProperty(exports, "RECHNUNG_VERTRAG_VERSION", { enumerable: true, get: function () { return vertrag_js_1.RECHNUNG_VERTRAG_VERSION; } });
44
48
  Object.defineProperty(exports, "RECHNUNG_AUFRUFE", { enumerable: true, get: function () { return vertrag_js_1.RECHNUNG_AUFRUFE; } });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Summen einer Rechnung vorab rechnen — genau so, wie der Server sie beim
3
+ * Ausstellen rechnet. Fuer Shops, die kassieren, bevor die Rechnung entsteht,
4
+ * und denselben Betrag brauchen, den die Rechnung spaeter ausweist.
5
+ *
6
+ * Die Regel (je USt-Satz, Betraege in Cent, kaufmaennisch gerundet):
7
+ *
8
+ * | Modus | Netto | USt | Brutto |
9
+ * |---------|--------------------------------|----------------------------|--------------------|
10
+ * | `net` | round(Σ Zeilen) | round(Σ Zeilen × Satz/100) | Netto + USt |
11
+ * | `gross` | round(B × 100 / (100 + Satz)) | B − Netto | B = round(Σ Zeilen)|
12
+ *
13
+ * Zeile = Einzelpreis × Menge × (1 − Rabatt/100), ungerundet. Im Brutto-Modus
14
+ * ist das Brutto der vereinbarte Preis und bleibt, wie die Zeilen es ergeben.
15
+ * Bei einem steuerfreien Steuerfall zaehlt jede Zeile zu 0 %.
16
+ *
17
+ * `fixtures/rechnung-summen.json` haelt die Prueffaelle; Backend und
18
+ * Dart-Zwilling pruefen gegen dieselbe Datei. Verbindlich bleibt, was der
19
+ * Server rechnet — mit `previewInvoice` laesst sich das vorab abfragen.
20
+ */
21
+ import type { InvoiceTotals } from './typen.js';
22
+ import type { PriceMode, TaxScheme } from './vertrag.js';
23
+ /** Was fuer die Summe zaehlt — `InvoiceItemInput` passt unveraendert. */
24
+ export interface SummenPosition {
25
+ quantity: number;
26
+ unitPriceCents: number;
27
+ vatRate: number;
28
+ discountPct?: number;
29
+ }
30
+ /** Steuerfaelle, in denen die Rechnung keine Steuer ausweist: jede Zeile zaehlt zu 0 %. */
31
+ export declare const STEUERFREIE_FAELLE: readonly TaxScheme[];
32
+ /**
33
+ * Netto, USt und Brutto einer Rechnung in Cent, je Satz absteigend — dieselbe
34
+ * Form wie `invoice.totals` in den Antworten.
35
+ *
36
+ * `taxScheme` ist der Steuerfall der Rechnung; ohne Angabe `normal`. Leitet der
37
+ * Server einen steuerfreien Fall ab (etwa eine ig. Lieferung), gehoert dieser
38
+ * Fall hierher — sonst rechnet die Funktion Steuer, die die Rechnung nicht
39
+ * ausweist.
40
+ */
41
+ export declare function rechnungSummen(items: readonly SummenPosition[], priceMode: PriceMode, taxScheme?: TaxScheme): InvoiceTotals;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * Summen einer Rechnung vorab rechnen — genau so, wie der Server sie beim
4
+ * Ausstellen rechnet. Fuer Shops, die kassieren, bevor die Rechnung entsteht,
5
+ * und denselben Betrag brauchen, den die Rechnung spaeter ausweist.
6
+ *
7
+ * Die Regel (je USt-Satz, Betraege in Cent, kaufmaennisch gerundet):
8
+ *
9
+ * | Modus | Netto | USt | Brutto |
10
+ * |---------|--------------------------------|----------------------------|--------------------|
11
+ * | `net` | round(Σ Zeilen) | round(Σ Zeilen × Satz/100) | Netto + USt |
12
+ * | `gross` | round(B × 100 / (100 + Satz)) | B − Netto | B = round(Σ Zeilen)|
13
+ *
14
+ * Zeile = Einzelpreis × Menge × (1 − Rabatt/100), ungerundet. Im Brutto-Modus
15
+ * ist das Brutto der vereinbarte Preis und bleibt, wie die Zeilen es ergeben.
16
+ * Bei einem steuerfreien Steuerfall zaehlt jede Zeile zu 0 %.
17
+ *
18
+ * `fixtures/rechnung-summen.json` haelt die Prueffaelle; Backend und
19
+ * Dart-Zwilling pruefen gegen dieselbe Datei. Verbindlich bleibt, was der
20
+ * Server rechnet — mit `previewInvoice` laesst sich das vorab abfragen.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.STEUERFREIE_FAELLE = void 0;
24
+ exports.rechnungSummen = rechnungSummen;
25
+ /** Steuerfaelle, in denen die Rechnung keine Steuer ausweist: jede Zeile zaehlt zu 0 %. */
26
+ exports.STEUERFREIE_FAELLE = Object.freeze([
27
+ 'smallBusiness',
28
+ 'reverseCharge',
29
+ 'igLieferung',
30
+ 'exportThirdCountry',
31
+ 'domesticReverseCharge',
32
+ 'outsideScope',
33
+ ]);
34
+ /** Auf Cent gerundete Euro — dieselbe Rundung wie am Server (halber Cent vom Nullpunkt weg). */
35
+ function euroRund(n) {
36
+ return ((n < 0 ? -1 : 1) * Math.round((Math.abs(n) + Number.EPSILON) * 100)) / 100;
37
+ }
38
+ const euroZuCent = (euro) => Math.round(euroRund(euro) * 100) || 0;
39
+ /** Ganze Cent kaufmaennisch; ein Gleitkomma-Rest wie x,4999999… zaehlt als halber Cent. */
40
+ function centRund(x) {
41
+ const betrag = Math.round(Math.abs(x) * 1e6) / 1e6;
42
+ return (x < 0 ? -1 : 1) * Math.round(betrag) || 0;
43
+ }
44
+ /**
45
+ * Netto, USt und Brutto einer Rechnung in Cent, je Satz absteigend — dieselbe
46
+ * Form wie `invoice.totals` in den Antworten.
47
+ *
48
+ * `taxScheme` ist der Steuerfall der Rechnung; ohne Angabe `normal`. Leitet der
49
+ * Server einen steuerfreien Fall ab (etwa eine ig. Lieferung), gehoert dieser
50
+ * Fall hierher — sonst rechnet die Funktion Steuer, die die Rechnung nicht
51
+ * ausweist.
52
+ */
53
+ function rechnungSummen(items, priceMode, taxScheme = 'normal') {
54
+ const steuerfrei = exports.STEUERFREIE_FAELLE.includes(taxScheme);
55
+ const bruttoPreise = priceMode === 'gross' && !steuerfrei;
56
+ // Ungerundete Zeilen je Satz, in Euro wie am Server.
57
+ const jeSatz = new Map();
58
+ for (const p of items) {
59
+ const satz = steuerfrei ? 0 : p.vatRate;
60
+ const zeile = (p.unitPriceCents / 100) * p.quantity * (1 - (p.discountPct ?? 0) / 100);
61
+ jeSatz.set(satz, (jeSatz.get(satz) ?? 0) + zeile);
62
+ }
63
+ const byRate = [];
64
+ for (const [rate, roh] of jeSatz) {
65
+ let netCents;
66
+ let vatCents;
67
+ if (bruttoPreise) {
68
+ const grossCents = euroZuCent(roh);
69
+ netCents = centRund((grossCents * 100) / (100 + rate));
70
+ vatCents = grossCents - netCents;
71
+ }
72
+ else {
73
+ netCents = euroZuCent(roh);
74
+ vatCents = euroZuCent((roh * rate) / 100);
75
+ }
76
+ byRate.push({ rate, netCents, vatCents, grossCents: netCents + vatCents });
77
+ }
78
+ byRate.sort((a, b) => b.rate - a.rate);
79
+ const summe = (feld) => byRate.reduce((s, r) => s + r[feld], 0);
80
+ return { netCents: summe('netCents'), vatCents: summe('vatCents'), grossCents: summe('grossCents'), byRate };
81
+ }
@@ -35,8 +35,12 @@ declare const DE: {
35
35
  readonly 'pdf.tabelle.ust': "UST";
36
36
  readonly 'pdf.tabelle.einzelNetto': "EINZEL NETTO";
37
37
  readonly 'pdf.tabelle.betragNetto': "BETRAG NETTO";
38
+ readonly 'pdf.tabelle.einzelBrutto': "EINZEL BRUTTO";
39
+ readonly 'pdf.tabelle.betragBrutto': "BETRAG BRUTTO";
40
+ readonly 'pdf.position.rabatt': "abzüglich {prozent} % Rabatt";
38
41
  readonly 'pdf.summe.zwischensumme': "Zwischensumme netto";
39
42
  readonly 'pdf.summe.umsatzsteuer': "Umsatzsteuer {satz}%";
43
+ readonly 'pdf.summe.darinUst': "darin {satz}% USt von {netto} netto";
40
44
  readonly 'pdf.summe.gesamt.rechnung': "Gesamtbetrag";
41
45
  readonly 'pdf.summe.gesamt.gutschrift': "Gutschriftsbetrag";
42
46
  readonly 'steuer.kleinunternehmer': "Gemäß § 6 Abs. 1 Z 27 UStG wird keine Umsatzsteuer verrechnet (Kleinunternehmer).";
@@ -24,8 +24,12 @@ const DE = {
24
24
  'pdf.tabelle.ust': 'UST',
25
25
  'pdf.tabelle.einzelNetto': 'EINZEL NETTO',
26
26
  'pdf.tabelle.betragNetto': 'BETRAG NETTO',
27
+ 'pdf.tabelle.einzelBrutto': 'EINZEL BRUTTO',
28
+ 'pdf.tabelle.betragBrutto': 'BETRAG BRUTTO',
29
+ 'pdf.position.rabatt': 'abzüglich {prozent} % Rabatt',
27
30
  'pdf.summe.zwischensumme': 'Zwischensumme netto',
28
31
  'pdf.summe.umsatzsteuer': 'Umsatzsteuer {satz}%',
32
+ 'pdf.summe.darinUst': 'darin {satz}% USt von {netto} netto',
29
33
  'pdf.summe.gesamt.rechnung': 'Gesamtbetrag',
30
34
  'pdf.summe.gesamt.gutschrift': 'Gutschriftsbetrag',
31
35
  'steuer.kleinunternehmer': 'Gemäß § 6 Abs. 1 Z 27 UStG wird keine Umsatzsteuer verrechnet (Kleinunternehmer).',
@@ -230,8 +234,12 @@ const EN = {
230
234
  'pdf.tabelle.ust': 'VAT',
231
235
  'pdf.tabelle.einzelNetto': 'UNIT NET',
232
236
  'pdf.tabelle.betragNetto': 'AMOUNT NET',
237
+ 'pdf.tabelle.einzelBrutto': 'UNIT GROSS',
238
+ 'pdf.tabelle.betragBrutto': 'AMOUNT GROSS',
239
+ 'pdf.position.rabatt': 'less {prozent}% discount',
233
240
  'pdf.summe.zwischensumme': 'Subtotal net',
234
241
  'pdf.summe.umsatzsteuer': 'VAT {satz}%',
242
+ 'pdf.summe.darinUst': 'incl. {satz}% VAT on {netto} net',
235
243
  'pdf.summe.gesamt.rechnung': 'Total',
236
244
  'pdf.summe.gesamt.gutschrift': 'Credit total',
237
245
  'steuer.kleinunternehmer': 'No VAT charged pursuant to § 6 (1) no. 27 UStG (small business exemption).',
@@ -146,7 +146,7 @@ export interface InvoicePayment {
146
146
  }
147
147
  /**
148
148
  * Ein Hinweis zu einer erfolgreichen Antwort — kein Fehler, sondern etwas,
149
- * das der Aufrufer wissen sollte (heute nur `cash_receipt_required`).
149
+ * das der Aufrufer wissen sollte. Die Codes stehen in `INVOICE_NOTICE_CODES`.
150
150
  */
151
151
  export interface InvoiceNotice {
152
152
  code: InvoiceNoticeCode;
@@ -156,7 +156,11 @@ export interface RecordPaymentResult {
156
156
  invoice: Invoice;
157
157
  payment: InvoicePayment;
158
158
  replayed: boolean;
159
- notice?: InvoiceNotice;
159
+ /**
160
+ * Hinweise zu dieser Zahlung — eine **Liste** wie bei `issueInvoice`
161
+ * (bis 0.21.0 ein einzelnes Objekt). Fehlt, wenn es keinen gibt.
162
+ */
163
+ notice?: InvoiceNotice[];
160
164
  }
161
165
  export interface CancelInvoiceRequest {
162
166
  idempotencyKey: string;
@@ -167,15 +171,23 @@ export interface CancelInvoiceRequest {
167
171
  export interface CreditNoteRequest extends CancelInvoiceRequest {
168
172
  items: InvoiceItemInput[];
169
173
  }
174
+ /**
175
+ * Summen einer Rechnung in Cent, **immer positiv** — auch bei einer Gutschrift
176
+ * (`docType: 'GU'`); das Vorzeichen steht im Belegtyp, nicht im Betrag.
177
+ * Gerechnet wird je Satz wie in [rechnungSummen].
178
+ */
170
179
  export interface InvoiceTotals {
171
180
  netCents: number;
172
181
  vatCents: number;
173
182
  grossCents: number;
174
- byRate: {
175
- rate: number;
176
- netCents: number;
177
- vatCents: number;
178
- }[];
183
+ /** Je USt-Satz, absteigend. `grossCents` ist `netCents + vatCents` (seit 0.22.0 mitgeliefert). */
184
+ byRate: InvoiceRateTotals[];
185
+ }
186
+ export interface InvoiceRateTotals {
187
+ rate: number;
188
+ netCents: number;
189
+ vatCents: number;
190
+ grossCents: number;
179
191
  }
180
192
  export interface EInvoiceStatus {
181
193
  level: string;
@@ -299,6 +311,38 @@ export interface IssueResult {
299
311
  */
300
312
  notice?: InvoiceNotice[];
301
313
  }
314
+ /**
315
+ * Was `issueInvoice` mit dieser Anfrage ausstellen wuerde — ohne Nummer, ohne
316
+ * Dokument, ohne Zahlung. Der Probelauf prueft wie das Ausstellen (Einrichtung,
317
+ * Kunde, Steuerfall, Pflichtangaben, Marke, Zahlung) und scheitert mit
318
+ * denselben Fehlercodes.
319
+ */
320
+ export interface InvoicePreview {
321
+ docType: 'RE';
322
+ /** Heutiger Wiener Tag — der Tag, den eine sofort ausgestellte Rechnung truege. */
323
+ invoiceDate: string;
324
+ dueDate: string | null;
325
+ customerId: string | null;
326
+ /** Der abgeleitete Steuerfall. */
327
+ taxScheme: TaxScheme;
328
+ /** Warum dieser Fall gilt (z. B. `customer_country_eu_with_vat_id`). */
329
+ taxSchemeReason: string;
330
+ reverseChargeReason: ReverseChargeReason | null;
331
+ taxCountry: string;
332
+ priceMode: PriceMode;
333
+ totals: InvoiceTotals;
334
+ language: InvoiceLanguage;
335
+ brand: {
336
+ id: string | null;
337
+ name: string | null;
338
+ } | null;
339
+ einvoice: EInvoiceStatus;
340
+ }
341
+ export interface PreviewResult {
342
+ preview: InvoicePreview;
343
+ /** Dieselben Hinweise, die das Ausstellen liefern wuerde. */
344
+ notice?: InvoiceNotice[];
345
+ }
302
346
  export interface CancelResult {
303
347
  creditNote: Invoice;
304
348
  original: {
@@ -420,6 +420,12 @@ exports.RECHNUNG_ANFRAGEN = Object.freeze({
420
420
  * und ein sofort geholtes PDF Zahlungsinformationen traegt.
421
421
  */
422
422
  payment: { typ: 'object', pflicht: false, felder: exports.PAYMENT_FELDER },
423
+ /**
424
+ * Probelauf: alles pruefen und rechnen wie beim Ausstellen, aber nichts
425
+ * festschreiben — keine Nummer, kein Dokument, keine Zahlung, kein
426
+ * Idempotenz-Eintrag. Die Antwort traegt `preview` statt `invoice`.
427
+ */
428
+ dryRun: { typ: 'boolean', pflicht: false },
423
429
  },
424
430
  cancelInvoice: {
425
431
  idempotencyKey: idempotencyKey(true),
@@ -87,7 +87,6 @@ export declare function causeDigest(ursache: unknown, geheimnisse: readonly stri
87
87
  * demselben Grund **keinen** Vorgabewert.
88
88
  */
89
89
  export declare function fehlerDetails(daten: unknown, geheimnisse: readonly string[]): Record<string, unknown>;
90
- /** Fachlicher Fehler: HTTP 200, aber `status: 'error'` im Rumpf. */
91
90
  export declare class KasseneckApiError extends Error {
92
91
  readonly name = "KasseneckApiError";
93
92
  /** Aufgerufene Backend-Funktion, z. B. `createReceipt`. */