@kreiseck/kasseneck-api 0.21.0 → 0.23.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 (46) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +69 -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 +4 -2
  10. package/dist/cjs/rechnung/index.js +16 -1
  11. package/dist/cjs/rechnung/rechnen.d.ts +123 -0
  12. package/dist/cjs/rechnung/rechnen.js +327 -0
  13. package/dist/cjs/rechnung/summen.d.ts +42 -0
  14. package/dist/cjs/rechnung/summen.js +74 -0
  15. package/dist/cjs/rechnung/texte.d.ts +4 -0
  16. package/dist/cjs/rechnung/texte.js +8 -0
  17. package/dist/cjs/rechnung/typen.d.ts +51 -7
  18. package/dist/cjs/rechnung/vertrag.d.ts +1 -1
  19. package/dist/cjs/rechnung/vertrag.js +8 -0
  20. package/dist/esm/client/errors.d.ts +0 -1
  21. package/dist/esm/client/errors.js +24 -1
  22. package/dist/esm/rechnung/api.d.ts +3 -1
  23. package/dist/esm/rechnung/api.js +2 -1
  24. package/dist/esm/rechnung/endpunkte.d.ts +12 -1
  25. package/dist/esm/rechnung/endpunkte.js +55 -3
  26. package/dist/esm/rechnung/index.d.ts +4 -2
  27. package/dist/esm/rechnung/index.js +3 -1
  28. package/dist/esm/rechnung/rechnen.d.ts +123 -0
  29. package/dist/esm/rechnung/rechnen.js +316 -0
  30. package/dist/esm/rechnung/summen.d.ts +42 -0
  31. package/dist/esm/rechnung/summen.js +71 -0
  32. package/dist/esm/rechnung/texte.d.ts +4 -0
  33. package/dist/esm/rechnung/texte.js +8 -0
  34. package/dist/esm/rechnung/typen.d.ts +51 -7
  35. package/dist/esm/rechnung/vertrag.d.ts +1 -1
  36. package/dist/esm/rechnung/vertrag.js +8 -0
  37. package/fixtures/hobex-hps-codes.json +1 -1
  38. package/fixtures/kasse-texte.json +1 -1
  39. package/fixtures/oberflaeche.json +12 -2
  40. package/fixtures/position-aus-euro.json +107 -0
  41. package/fixtures/rechnung-api.schema.json +7 -2
  42. package/fixtures/rechnung-rechnen-zufall.json +8451 -0
  43. package/fixtures/rechnung-rechnen.json +304 -0
  44. package/fixtures/rechnung-summen.json +154 -0
  45. package/fixtures/rechnung-texte.json +9 -1
  46. package/package.json +13 -2
@@ -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: {
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import { createBinaryTransport, createTransport } from '../client/transport.js';
8
8
  import { rechnungKeyAuth } from './auth.js';
9
- import { cancelInvoice, createCreditNote, createCustomer, getCustomer, getInvoice, getInvoicePdf, getInvoiceSetupStatus, getInvoiceXml, issueInvoice, listBrands, listInvoices, recordInvoicePayment, searchCustomers, updateCustomer, } from './endpunkte.js';
9
+ import { cancelInvoice, createCreditNote, createCustomer, getCustomer, getInvoice, getInvoicePdf, getInvoiceSetupStatus, getInvoiceXml, issueInvoice, listBrands, previewInvoice, listInvoices, recordInvoicePayment, searchCustomers, updateCustomer, } from './endpunkte.js';
10
10
  export function createRechnungApi(optionen) {
11
11
  const transportOptionen = {
12
12
  auth: rechnungKeyAuth({ apiKey: optionen.apiKey }),
@@ -22,6 +22,7 @@ export function createRechnungApi(optionen) {
22
22
  updateCustomer: (id, patch) => updateCustomer(rufen, id, patch),
23
23
  searchCustomers: (suche) => searchCustomers(rufen, suche),
24
24
  issueInvoice: (anfrage) => issueInvoice(rufen, anfrage),
25
+ previewInvoice: (anfrage) => previewInvoice(rufen, anfrage),
25
26
  cancelInvoice: (anfrage) => cancelInvoice(rufen, anfrage),
26
27
  createCreditNote: (anfrage) => createCreditNote(rufen, anfrage),
27
28
  getInvoice: (kennung) => 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: {
@@ -39,6 +39,33 @@ function seite(aufruf, daten, feld) {
39
39
  return { eintraege: o[feld], nextCursor: typeof o['nextCursor'] === 'string' ? o['nextCursor'] : null };
40
40
  }
41
41
  const zahl = (wert) => (typeof wert === 'number' && Number.isFinite(wert) ? wert : 0);
42
+ /**
43
+ * Die Hinweise einer Antwort (`data.notice`) — immer eine Liste, `undefined`
44
+ * ohne Hinweis. Ein einzelnes Objekt (Server vor der Vereinheitlichung) wird
45
+ * zur Liste.
46
+ *
47
+ * Ein unbrauchbarer Eintrag wird uebergangen, nicht geworfen: der Aufruf hat
48
+ * schon gewirkt (die Rechnung ist ausgestellt, die Zahlung gebucht). Ein
49
+ * Fehler an dieser Stelle liesse den Aufrufer glauben, es sei nichts
50
+ * entstanden — und eine Wiederholung liefert die Hinweise nicht noch einmal.
51
+ */
52
+ function hinweise(daten) {
53
+ const roh = objekt(daten)['notice'];
54
+ if (roh === undefined || roh === null)
55
+ return undefined;
56
+ const liste = (Array.isArray(roh) ? roh : [roh]).filter((h) => typeof objekt(h)['code'] === 'string' && typeof objekt(h)['message'] === 'string');
57
+ return liste.length ? liste : undefined;
58
+ }
59
+ /**
60
+ * Eine Kennung muss ein Objekt sein (`{ invoiceId }`, `{ customerId }` …). Ein
61
+ * blosser Text wuerde sonst Zeichen fuer Zeichen zu Feldern `0`, `1`, …
62
+ * zerlegt, und der Server koennte nur „Bitte Eingaben pruefen" antworten.
63
+ */
64
+ function kennungVerlangen(aufruf, kennung, felder) {
65
+ if (kennung === null || typeof kennung !== 'object' || Array.isArray(kennung)) {
66
+ throw new KasseneckValidationError(aufruf, `Kennung als Objekt erwartet: ${felder}`, 'request');
67
+ }
68
+ }
42
69
  /** Anfrageobjekt als Nutzlast — flache Kopie, damit der Aufrufer sein Objekt behaelt. */
43
70
  const nutzlast = (anfrage) => ({ ...anfrage });
44
71
  // ---- Kunden -----------------------------------------------------------------
@@ -50,6 +77,7 @@ export async function createCustomer(rufen, customer, optionen = {}) {
50
77
  return pflichtObjekt('createCustomer', daten, 'customer');
51
78
  }
52
79
  export async function getCustomer(rufen, kennung) {
80
+ kennungVerlangen('getCustomer', kennung, '{ customerId } oder { externalId }');
53
81
  const daten = await rufen('getCustomer', nutzlast(kennung));
54
82
  return pflichtObjekt('getCustomer', daten, 'customer');
55
83
  }
@@ -65,10 +93,32 @@ export async function searchCustomers(rufen, suche) {
65
93
  // ---- Rechnungen -------------------------------------------------------------
66
94
  export async function issueInvoice(rufen, anfrage) {
67
95
  const daten = await rufen('issueInvoice', nutzlast(anfrage));
68
- return {
96
+ const ergebnis = {
69
97
  invoice: pflichtObjekt('issueInvoice', daten, 'invoice'),
70
98
  replayed: objekt(daten)['replayed'] === true,
71
99
  };
100
+ const notice = hinweise(daten);
101
+ if (notice)
102
+ ergebnis.notice = notice;
103
+ return ergebnis;
104
+ }
105
+ /**
106
+ * Probelauf von `issueInvoice`: dieselbe Anfrage wird geprueft und gerechnet
107
+ * wie beim Ausstellen, aber nichts festgeschrieben. Die Antwort nennt Summen,
108
+ * Steuerfall, Sprache, Marke und die Hinweise — oder scheitert mit demselben
109
+ * Fehlercode, mit dem das Ausstellen scheitern wuerde.
110
+ *
111
+ * Der `idempotencyKey` wird nicht verbraucht: dieselbe Anfrage laesst sich
112
+ * danach unveraendert ausstellen. Verbindlich ist das Ausstellen — zwischen
113
+ * Probelauf und Ausstellen kann sich der Kunde oder das Konto aendern.
114
+ */
115
+ export async function previewInvoice(rufen, anfrage) {
116
+ const daten = await rufen('issueInvoice', { ...nutzlast(anfrage), dryRun: true });
117
+ const ergebnis = { preview: pflichtObjekt('issueInvoice', daten, 'preview') };
118
+ const notice = hinweise(daten);
119
+ if (notice)
120
+ ergebnis.notice = notice;
121
+ return ergebnis;
72
122
  }
73
123
  export async function cancelInvoice(rufen, anfrage) {
74
124
  const daten = await rufen('cancelInvoice', nutzlast(anfrage));
@@ -88,6 +138,7 @@ export async function createCreditNote(rufen, anfrage) {
88
138
  };
89
139
  }
90
140
  export async function getInvoice(rufen, kennung) {
141
+ kennungVerlangen('getInvoice', kennung, '{ invoiceId } oder { number }');
91
142
  const daten = await rufen('getInvoice', nutzlast(kennung));
92
143
  return pflichtObjekt('getInvoice', daten, 'invoice');
93
144
  }
@@ -143,8 +194,9 @@ export async function recordInvoicePayment(rufen, anfrage) {
143
194
  payment: pflichtObjekt('recordInvoicePayment', daten, 'payment'),
144
195
  replayed: roh['replayed'] === true,
145
196
  };
146
- if (roh['notice'])
147
- ergebnis.notice = roh['notice'];
197
+ const notice = hinweise(daten);
198
+ if (notice)
199
+ ergebnis.notice = notice;
148
200
  return ergebnis;
149
201
  }
150
202
  /** Die Marken des Kontos — die Kennung geht als `brandId` in `issueInvoice`. */
@@ -13,7 +13,9 @@
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';
20
+ export { anteiligerPreis, BETRAG_GRENZE_CENTS, positionAusEuro, preisText, RechenFehler, rechnungRechnen, rund, satzSchluessel, satzText, type RechenErgebnis, type RechenFehlerCode, type RechenOptionen, type RechenPosition, type SatzSumme, type Umwandlung, type UmwandlungsGrund, type ZeilenBetrag, } from './rechnen.js';
19
21
  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';
@@ -13,6 +13,8 @@
13
13
  export { createRechnungApi } from './api.js';
14
14
  export { rechnungKeyAuth } from './auth.js';
15
15
  export { istRechnungFehler, rechnungFehlerCode, rechnungFeldFehler, } from './fehler.js';
16
- export { cancelInvoice, createCreditNote, createCustomer, getCustomer, getInvoice, getInvoicePdf, getInvoiceSetupStatus, getInvoiceXml, issueInvoice, listBrands, listInvoices, recordInvoicePayment, searchCustomers, updateCustomer, } from './endpunkte.js';
16
+ export { cancelInvoice, createCreditNote, createCustomer, getCustomer, getInvoice, getInvoicePdf, getInvoiceSetupStatus, getInvoiceXml, issueInvoice, listBrands, listInvoices, previewInvoice, recordInvoicePayment, searchCustomers, updateCustomer, } from './endpunkte.js';
17
17
  export { RECHNUNG_TEXTE, rechnungText } from './texte.js';
18
+ export { rechnungSummen, STEUERFREIE_FAELLE } from './summen.js';
19
+ export { anteiligerPreis, BETRAG_GRENZE_CENTS, positionAusEuro, preisText, RechenFehler, rechnungRechnen, rund, satzSchluessel, satzText, } from './rechnen.js';
18
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, } from './vertrag.js';
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Der Rechenkern der Rechnung — exakt, in ganzen Zahlen.
3
+ *
4
+ * Eine Zeile ist ein Bruch: Preis (µ€) × Menge (Tausendstel) × Rabattanteil
5
+ * (Hundertstel-Prozent). Multipliziert man die Skalen, ist die kleinste Einheit
6
+ * 10⁻¹⁷ Cent — das passt in keine Gleitkommazahl und in keine 53-Bit-Ganzzahl,
7
+ * also rechnet der Kern mit BigInt. Gerundet wird genau EINMAL je USt-Satz, auf
8
+ * dem Bruch, kaufmaennisch (halbe Einheit vom Nullpunkt weg).
9
+ *
10
+ * Warum das so streng ist: Vier Umsetzungen (Server, dieses Paket, Dart, Panel)
11
+ * mussten bisher dieselbe Reihenfolge von Gleitkomma-Schritten nachbauen, damit
12
+ * Grenzfaelle gleich runden. Mit Ganzzahlen gibt es keine Reihenfolge mehr, an
13
+ * der etwas auseinanderlaufen koennte.
14
+ *
15
+ * Dieser Unterpfad ist rein: kein Transport, kein Zugangsschluessel, keine
16
+ * Abhaengigkeit ausser Typen. Er wird im Browser gebuendelt (Panel) und im
17
+ * Server geladen.
18
+ *
19
+ * Spec: docs/specs/2026-09-17-rechnung-ganzzahlen-design.md § 5.
20
+ */
21
+ import type { PriceMode, TaxScheme } from './vertrag.js';
22
+ /** Hoechster Betrag je Zeile und je Rechnung: 999.999.999,99 €. */
23
+ export declare const BETRAG_GRENZE_CENTS = 99999999999;
24
+ /** Steuerfaelle, in denen die Rechnung keine Steuer ausweist: jede Zeile zaehlt zu 0 %. */
25
+ export declare const STEUERFREIE_FAELLE: readonly TaxScheme[];
26
+ /** Eine Position in gespeicherter Form — alle Werte ganzzahlig. */
27
+ export interface RechenPosition {
28
+ /** Einzelpreis in Millionstel Euro, 0 … 10¹². */
29
+ unitPriceMicros: number;
30
+ /** Menge in Tausendstel; negativ = Abzugszeile, 0 = Textzeile. */
31
+ quantityMilli: number;
32
+ /** Rabatt in Hundertstel-Prozent, 0 … 10000 (ohne Angabe 0). */
33
+ discountBp?: number;
34
+ /** USt-Satz in Hundertstel-Prozent, 0 … 10000 (ohne Angabe 0). */
35
+ vatRateBp?: number;
36
+ }
37
+ export interface RechenOptionen {
38
+ priceMode: PriceMode;
39
+ /** Ohne Angabe `normal`. */
40
+ taxScheme?: TaxScheme;
41
+ }
42
+ export interface SatzSumme {
43
+ rateBp: number;
44
+ netCents: number;
45
+ vatCents: number;
46
+ grossCents: number;
47
+ }
48
+ export interface ZeilenBetrag {
49
+ netCents: number;
50
+ grossCents: number;
51
+ /** Der Satz, mit dem die Zeile gerechnet wurde (bei steuerfreiem Fall 0). */
52
+ rateBp: number;
53
+ }
54
+ export interface RechenErgebnis {
55
+ netCents: number;
56
+ vatCents: number;
57
+ grossCents: number;
58
+ /** Absteigend nach `rateBp`. */
59
+ byRate: SatzSumme[];
60
+ /** Je Position, in der Reihenfolge der Eingabe. */
61
+ lines: ZeilenBetrag[];
62
+ }
63
+ export type RechenFehlerCode = 'amount_too_large' | 'kein_ganzzahlwert' | 'ausserhalb';
64
+ /** Fehler des Kerns — mit Code, Feld und Index, damit die API daraus einen Feldfehler machen kann. */
65
+ export declare class RechenFehler extends Error {
66
+ readonly code: RechenFehlerCode;
67
+ readonly feld?: string;
68
+ readonly index?: number;
69
+ constructor(code: RechenFehlerCode, nachricht: string, feld?: string, index?: number);
70
+ }
71
+ /**
72
+ * `a / b` kaufmaennisch gerundet, halbe Einheit vom Nullpunkt weg; `b` > 0.
73
+ *
74
+ * Ohne Gleitkomma: `(2·|a| + b) / (2·b)` ist genau dann eins groesser, wenn der
75
+ * Rest mindestens die halbe Einheit betraegt.
76
+ */
77
+ export declare function rund(a: bigint, b: bigint): bigint;
78
+ export declare function rechnungRechnen(positionen: readonly RechenPosition[], optionen: RechenOptionen): RechenErgebnis;
79
+ export type UmwandlungsGrund = 'kein_zahlwert' | 'nachkommastellen' | 'ausserhalb';
80
+ export type Umwandlung = {
81
+ ok: true;
82
+ position: RechenPosition & Record<string, unknown>;
83
+ } | {
84
+ ok: false;
85
+ feld: 'unitPrice' | 'quantity' | 'vatRate' | 'discountPct';
86
+ grund: UmwandlungsGrund;
87
+ };
88
+ /**
89
+ * Euro-Position → Ganzzahl-Form, verlustfrei oder mit Grund abgelehnt. Die
90
+ * EINE Stelle, die das tut: Panel (beim Lesen), SEPA-Lauf, Umrechnungsskript
91
+ * und das Festschreiben benutzen alle diese Funktion, damit niemand eine
92
+ * zweite Auslegung baut.
93
+ *
94
+ * Eine Position, die schon `unitPriceMicros` traegt, kommt unveraendert
95
+ * zurueck — die Funktion ist die Formweiche, nicht eine blinde Umrechnung.
96
+ *
97
+ * Fehlende Menge und fehlender Satz gelten als 0, weil der Server bisher so
98
+ * abgerechnet hat. Ein fehlender Preis dagegen ist NIE 0: sonst wuerde eine
99
+ * Position, der nur der Preis fehlt, still zur 0-€-Zeile.
100
+ */
101
+ export declare function positionAusEuro(item: Record<string, unknown>): Umwandlung;
102
+ /**
103
+ * Anteiliger Preis fuer einen Teilzeitraum (erste Rechnung eines Abos).
104
+ * War der Ursprungspreis centgenau, bleibt auch der Anteil centgenau — sonst
105
+ * stuende auf der ersten Rechnung ein Preis mit sechs Stellen, wo bisher zwei
106
+ * standen.
107
+ */
108
+ export declare function anteiligerPreis(unitPriceMicros: number, monate: number, intervall: number): number;
109
+ /**
110
+ * Schluessel eines USt-Satzes in den gespeicherten Maps (`creditedCents.byRate`,
111
+ * `invoice_stats.vatByRate`): der Prozenttext mit Punkt, genau wie
112
+ * `String(satz)` ihn bisher gebildet hat. NICHT fuer die Anzeige — dafuer gibt
113
+ * es `satzText`.
114
+ */
115
+ export declare function satzSchluessel(rateBp: number): string;
116
+ /** Ein USt-Satz fuer die Anzeige: oesterreichisch mit Komma, ohne nachlaufende Nullen. */
117
+ export declare function satzText(rateBp: number): string;
118
+ /**
119
+ * Ein Einzelpreis fuer die Anzeige: mindestens zwei, hoechstens sechs
120
+ * Nachkommastellen, Tausenderpunkt, Komma als Trennzeichen — in jeder Sprache
121
+ * oesterreichisch, wie die Betraege auf dem Blatt.
122
+ */
123
+ export declare function preisText(unitPriceMicros: number): string;
@@ -0,0 +1,316 @@
1
+ /**
2
+ * Der Rechenkern der Rechnung — exakt, in ganzen Zahlen.
3
+ *
4
+ * Eine Zeile ist ein Bruch: Preis (µ€) × Menge (Tausendstel) × Rabattanteil
5
+ * (Hundertstel-Prozent). Multipliziert man die Skalen, ist die kleinste Einheit
6
+ * 10⁻¹⁷ Cent — das passt in keine Gleitkommazahl und in keine 53-Bit-Ganzzahl,
7
+ * also rechnet der Kern mit BigInt. Gerundet wird genau EINMAL je USt-Satz, auf
8
+ * dem Bruch, kaufmaennisch (halbe Einheit vom Nullpunkt weg).
9
+ *
10
+ * Warum das so streng ist: Vier Umsetzungen (Server, dieses Paket, Dart, Panel)
11
+ * mussten bisher dieselbe Reihenfolge von Gleitkomma-Schritten nachbauen, damit
12
+ * Grenzfaelle gleich runden. Mit Ganzzahlen gibt es keine Reihenfolge mehr, an
13
+ * der etwas auseinanderlaufen koennte.
14
+ *
15
+ * Dieser Unterpfad ist rein: kein Transport, kein Zugangsschluessel, keine
16
+ * Abhaengigkeit ausser Typen. Er wird im Browser gebuendelt (Panel) und im
17
+ * Server geladen.
18
+ *
19
+ * Spec: docs/specs/2026-09-17-rechnung-ganzzahlen-design.md § 5.
20
+ */
21
+ /** Skala des Zeilenbruchs: 10⁻¹⁷ Cent. */
22
+ const E = 10n ** 17n;
23
+ /** Hoechster Betrag je Zeile und je Rechnung: 999.999.999,99 €. */
24
+ export const BETRAG_GRENZE_CENTS = 99_999_999_999;
25
+ /** Steuerfaelle, in denen die Rechnung keine Steuer ausweist: jede Zeile zaehlt zu 0 %. */
26
+ export const STEUERFREIE_FAELLE = Object.freeze([
27
+ 'smallBusiness',
28
+ 'reverseCharge',
29
+ 'igLieferung',
30
+ 'exportThirdCountry',
31
+ 'domesticReverseCharge',
32
+ 'outsideScope',
33
+ ]);
34
+ /** Fehler des Kerns — mit Code, Feld und Index, damit die API daraus einen Feldfehler machen kann. */
35
+ export class RechenFehler extends Error {
36
+ code;
37
+ feld;
38
+ index;
39
+ constructor(code, nachricht, feld, index) {
40
+ super(nachricht);
41
+ this.name = 'RechenFehler';
42
+ this.code = code;
43
+ this.feld = feld;
44
+ this.index = index;
45
+ }
46
+ }
47
+ /**
48
+ * `a / b` kaufmaennisch gerundet, halbe Einheit vom Nullpunkt weg; `b` > 0.
49
+ *
50
+ * Ohne Gleitkomma: `(2·|a| + b) / (2·b)` ist genau dann eins groesser, wenn der
51
+ * Rest mindestens die halbe Einheit betraegt.
52
+ */
53
+ export function rund(a, b) {
54
+ const negativ = a < 0n;
55
+ const betrag = negativ ? -a : a;
56
+ const ganz = (2n * betrag + b) / (2n * b);
57
+ return negativ ? -ganz : ganz;
58
+ }
59
+ const GRENZEN = {
60
+ unitPriceMicros: [0, 1_000_000_000_000],
61
+ quantityMilli: [-1_000_000_000_000, 1_000_000_000_000],
62
+ discountBp: [0, 10_000],
63
+ vatRateBp: [0, 10_000],
64
+ };
65
+ function ganzzahl(wert, feld, index) {
66
+ if (typeof wert !== 'number' || !Number.isInteger(wert)) {
67
+ throw new RechenFehler('kein_ganzzahlwert', `items[${index}].${feld} muss eine ganze Zahl sein`, feld, index);
68
+ }
69
+ const [min, max] = GRENZEN[feld];
70
+ if (wert < min || wert > max) {
71
+ throw new RechenFehler('ausserhalb', `items[${index}].${feld} liegt ausserhalb von ${min} … ${max}`, feld, index);
72
+ }
73
+ return wert;
74
+ }
75
+ /**
76
+ * Verteilt die Zeilenbetraege eines Satzes so, dass sie genau die Satzsumme
77
+ * ergeben. Startwert ist die kaufmaennisch gerundete Zeile; der Rest geht
78
+ * einzeln an die Zeilen, bei denen das Runden am meisten weggenommen (bzw.
79
+ * zugegeben) hat. Verglichen wird auf dem Bruch, nie in Gleitkomma; bei
80
+ * Gleichstand bekommt die fruehere Zeile den Cent. Eine Zeile ueber 0 bekommt
81
+ * nie einen Cent — sonst stuende auf dem Blatt ein Betrag, den die Position
82
+ * nicht hat.
83
+ */
84
+ function verteile(gruppe, feld, faktor, nenner, ziel) {
85
+ const werte = gruppe.map((z) => {
86
+ const zaehler = z.L * faktor;
87
+ return { z, zaehler, wert: rund(zaehler, nenner) };
88
+ });
89
+ let rest = ziel - werte.reduce((s, w) => s + w.wert, 0n);
90
+ if (rest !== 0n) {
91
+ const schritt = rest > 0n ? 1n : -1n;
92
+ const kandidaten = werte
93
+ .filter((w) => w.zaehler !== 0n)
94
+ .sort((a, b) => {
95
+ const da = (a.zaehler - a.wert * nenner) * schritt;
96
+ const db = (b.zaehler - b.wert * nenner) * schritt;
97
+ if (da !== db)
98
+ return db > da ? 1 : -1;
99
+ return a.z.index - b.z.index;
100
+ });
101
+ for (let k = 0; rest !== 0n && kandidaten.length > 0; k = (k + 1) % kandidaten.length) {
102
+ kandidaten[k].wert += schritt;
103
+ rest -= schritt;
104
+ }
105
+ }
106
+ for (const w of werte)
107
+ w.z[feld] = Number(w.wert);
108
+ }
109
+ export function rechnungRechnen(positionen, optionen) {
110
+ const steuerfrei = STEUERFREIE_FAELLE.includes(optionen.taxScheme ?? 'normal');
111
+ const bruttoPreise = optionen.priceMode === 'gross' && !steuerfrei;
112
+ const grenze = BigInt(BETRAG_GRENZE_CENTS) * E;
113
+ const zeilen = positionen.map((p, index) => {
114
+ const preis = BigInt(ganzzahl(p.unitPriceMicros, 'unitPriceMicros', index));
115
+ const menge = BigInt(ganzzahl(p.quantityMilli, 'quantityMilli', index));
116
+ const rabatt = BigInt(ganzzahl(p.discountBp ?? 0, 'discountBp', index));
117
+ const satz = ganzzahl(p.vatRateBp ?? 0, 'vatRateBp', index);
118
+ const L = preis * menge * (10000n - rabatt) * 1000000n;
119
+ if ((L < 0n ? -L : L) > grenze) {
120
+ throw new RechenFehler('amount_too_large', `items[${index}] uebersteigt ${BETRAG_GRENZE_CENTS} Cent`, 'unitPriceMicros', index);
121
+ }
122
+ return { index, rateBp: steuerfrei ? 0 : satz, L, netCents: 0, grossCents: 0 };
123
+ });
124
+ const jeSatz = new Map();
125
+ for (const z of zeilen) {
126
+ const gruppe = jeSatz.get(z.rateBp);
127
+ if (gruppe)
128
+ gruppe.push(z);
129
+ else
130
+ jeSatz.set(z.rateBp, [z]);
131
+ }
132
+ const byRate = [];
133
+ for (const [rateBp, gruppe] of jeSatz) {
134
+ const r = BigInt(rateBp);
135
+ const S = gruppe.reduce((s, z) => s + z.L, 0n);
136
+ let netCents;
137
+ let vatCents;
138
+ let grossCents;
139
+ if (bruttoPreise) {
140
+ grossCents = rund(S, E);
141
+ netCents = rund(grossCents * 10000n, 10000n + r);
142
+ vatCents = grossCents - netCents;
143
+ }
144
+ else {
145
+ netCents = rund(S, E);
146
+ vatCents = rund(S * r, E * 10000n);
147
+ grossCents = netCents + vatCents;
148
+ }
149
+ if (bruttoPreise) {
150
+ verteile(gruppe, 'netCents', 10000n, E * (10000n + r), netCents);
151
+ verteile(gruppe, 'grossCents', 1n, E, grossCents);
152
+ }
153
+ else {
154
+ verteile(gruppe, 'netCents', 1n, E, netCents);
155
+ verteile(gruppe, 'grossCents', 10000n + r, E * 10000n, grossCents);
156
+ }
157
+ byRate.push({
158
+ rateBp,
159
+ netCents: Number(netCents),
160
+ vatCents: Number(vatCents),
161
+ grossCents: Number(grossCents),
162
+ });
163
+ }
164
+ byRate.sort((a, b) => b.rateBp - a.rateBp);
165
+ const summe = (feld) => byRate.reduce((s, r) => s + r[feld], 0);
166
+ const ergebnis = {
167
+ netCents: summe('netCents'),
168
+ vatCents: summe('vatCents'),
169
+ grossCents: summe('grossCents'),
170
+ byRate,
171
+ lines: zeilen.map((z) => ({ netCents: z.netCents, grossCents: z.grossCents, rateBp: z.rateBp })),
172
+ };
173
+ for (const feld of ['netCents', 'vatCents', 'grossCents']) {
174
+ if (Math.abs(ergebnis[feld]) > BETRAG_GRENZE_CENTS) {
175
+ throw new RechenFehler('amount_too_large', `Die Rechnung uebersteigt ${BETRAG_GRENZE_CENTS} Cent (${feld})`);
176
+ }
177
+ }
178
+ return ergebnis;
179
+ }
180
+ /**
181
+ * Wandelt eine Zahl ueber ihren kuerzesten Dezimaltext in eine Ganzzahl mit
182
+ * `stellen` Nachkommastellen. Kein Gleitkomma-Rechnen: `0.1 * 1000` ist
183
+ * 100.00000000000001, `String(0.1)` dagegen genau "0.1". Mehr Stellen als
184
+ * erlaubt ergeben `null` — auch Gleitkomma-Rauschen wie 0.30000000000000004,
185
+ * das mit 17 Stellen dasteht.
186
+ */
187
+ function ganzAusDezimaltext(wert, stellen) {
188
+ if (typeof wert !== 'number' || !Number.isFinite(wert))
189
+ return null;
190
+ const treffer = /^(-?)(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(String(wert));
191
+ if (!treffer)
192
+ return null;
193
+ const [, vorzeichen = '', ganzTeil = '0', bruchTeil = '', hoch = '0'] = treffer;
194
+ const verschiebung = Number(hoch);
195
+ let ziffern = `${ganzTeil}${bruchTeil}`;
196
+ let nachkomma = bruchTeil.length - verschiebung;
197
+ if (nachkomma < 0) {
198
+ ziffern += '0'.repeat(-nachkomma);
199
+ nachkomma = 0;
200
+ }
201
+ if (nachkomma > stellen) {
202
+ // Nur echte Nullen am Ende duerfen wegfallen (1.20 bei 1 Stelle).
203
+ const zuviel = nachkomma - stellen;
204
+ if (!/^0+$/.test(ziffern.slice(-zuviel)))
205
+ return null;
206
+ ziffern = ziffern.slice(0, -zuviel);
207
+ nachkomma = stellen;
208
+ }
209
+ const skaliert = `${ziffern}${'0'.repeat(stellen - nachkomma)}`;
210
+ const zahl = Number(`${vorzeichen}${skaliert}`);
211
+ return Number.isSafeInteger(zahl) ? zahl : null;
212
+ }
213
+ const EURO_FELDER = ['unitPrice', 'quantity', 'vatRate', 'discountPct'];
214
+ /**
215
+ * Euro-Position → Ganzzahl-Form, verlustfrei oder mit Grund abgelehnt. Die
216
+ * EINE Stelle, die das tut: Panel (beim Lesen), SEPA-Lauf, Umrechnungsskript
217
+ * und das Festschreiben benutzen alle diese Funktion, damit niemand eine
218
+ * zweite Auslegung baut.
219
+ *
220
+ * Eine Position, die schon `unitPriceMicros` traegt, kommt unveraendert
221
+ * zurueck — die Funktion ist die Formweiche, nicht eine blinde Umrechnung.
222
+ *
223
+ * Fehlende Menge und fehlender Satz gelten als 0, weil der Server bisher so
224
+ * abgerechnet hat. Ein fehlender Preis dagegen ist NIE 0: sonst wuerde eine
225
+ * Position, der nur der Preis fehlt, still zur 0-€-Zeile.
226
+ */
227
+ export function positionAusEuro(item) {
228
+ if (typeof item.unitPriceMicros === 'number') {
229
+ return { ok: true, position: item };
230
+ }
231
+ const roh = item.unitPrice;
232
+ if (typeof roh !== 'number' || !Number.isFinite(roh)) {
233
+ return { ok: false, feld: 'unitPrice', grund: 'kein_zahlwert' };
234
+ }
235
+ const micros = ganzAusDezimaltext(Math.abs(roh), 6);
236
+ if (micros === null)
237
+ return { ok: false, feld: 'unitPrice', grund: 'nachkommastellen' };
238
+ if (micros > GRENZEN.unitPriceMicros[1])
239
+ return { ok: false, feld: 'unitPrice', grund: 'ausserhalb' };
240
+ const zahl = (feld, stellen, max) => {
241
+ const w = item[feld];
242
+ if (w === undefined || w === null)
243
+ return { ok: true, wert: 0 };
244
+ if (typeof w !== 'number' || !Number.isFinite(w))
245
+ return { ok: false, grund: 'kein_zahlwert' };
246
+ const ganz = ganzAusDezimaltext(w, stellen);
247
+ if (ganz === null)
248
+ return { ok: false, grund: 'nachkommastellen' };
249
+ if (ganz < (feld === 'quantity' ? -max : 0) || ganz > max)
250
+ return { ok: false, grund: 'ausserhalb' };
251
+ return { ok: true, wert: ganz };
252
+ };
253
+ const menge = zahl('quantity', 3, GRENZEN.quantityMilli[1]);
254
+ if (!menge.ok)
255
+ return { ok: false, feld: 'quantity', grund: menge.grund };
256
+ const satz = zahl('vatRate', 2, 10_000);
257
+ if (!satz.ok)
258
+ return { ok: false, feld: 'vatRate', grund: satz.grund };
259
+ const rabatt = zahl('discountPct', 2, 10_000);
260
+ if (!rabatt.ok)
261
+ return { ok: false, feld: 'discountPct', grund: rabatt.grund };
262
+ const rest = { ...item };
263
+ for (const feld of EURO_FELDER)
264
+ delete rest[feld];
265
+ return {
266
+ ok: true,
267
+ position: {
268
+ ...rest,
269
+ unitPriceMicros: micros,
270
+ quantityMilli: roh < 0 ? -menge.wert : menge.wert,
271
+ discountBp: rabatt.wert,
272
+ vatRateBp: satz.wert,
273
+ },
274
+ };
275
+ }
276
+ /**
277
+ * Anteiliger Preis fuer einen Teilzeitraum (erste Rechnung eines Abos).
278
+ * War der Ursprungspreis centgenau, bleibt auch der Anteil centgenau — sonst
279
+ * stuende auf der ersten Rechnung ein Preis mit sechs Stellen, wo bisher zwei
280
+ * standen.
281
+ */
282
+ export function anteiligerPreis(unitPriceMicros, monate, intervall) {
283
+ const micros = BigInt(unitPriceMicros);
284
+ const anteil = rund(micros * BigInt(monate), BigInt(intervall));
285
+ if (unitPriceMicros % 10_000 !== 0)
286
+ return Number(anteil);
287
+ return Number(rund(anteil, 10000n) * 10000n);
288
+ }
289
+ /**
290
+ * Schluessel eines USt-Satzes in den gespeicherten Maps (`creditedCents.byRate`,
291
+ * `invoice_stats.vatByRate`): der Prozenttext mit Punkt, genau wie
292
+ * `String(satz)` ihn bisher gebildet hat. NICHT fuer die Anzeige — dafuer gibt
293
+ * es `satzText`.
294
+ */
295
+ export function satzSchluessel(rateBp) {
296
+ return String(rateBp / 100);
297
+ }
298
+ /** Ein USt-Satz fuer die Anzeige: oesterreichisch mit Komma, ohne nachlaufende Nullen. */
299
+ export function satzText(rateBp) {
300
+ return satzSchluessel(rateBp).replace('.', ',');
301
+ }
302
+ /**
303
+ * Ein Einzelpreis fuer die Anzeige: mindestens zwei, hoechstens sechs
304
+ * Nachkommastellen, Tausenderpunkt, Komma als Trennzeichen — in jeder Sprache
305
+ * oesterreichisch, wie die Betraege auf dem Blatt.
306
+ */
307
+ export function preisText(unitPriceMicros) {
308
+ const negativ = unitPriceMicros < 0;
309
+ const betrag = Math.abs(unitPriceMicros);
310
+ const ganz = Math.trunc(betrag / 1_000_000);
311
+ let bruch = String(betrag % 1_000_000).padStart(6, '0');
312
+ while (bruch.length > 2 && bruch.endsWith('0'))
313
+ bruch = bruch.slice(0, -1);
314
+ const mitPunkten = String(ganz).replace(/\B(?=(\d{3})+(?!\d))/g, '.');
315
+ return `${negativ ? '-' : ''}${mitPunkten},${bruch}`;
316
+ }
@@ -0,0 +1,42 @@
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
+ import { STEUERFREIE_FAELLE } from './rechnen.js';
24
+ /** Aus dem Kern uebernommen, damit Kern und Huelle nie auseinanderlaufen. */
25
+ export { STEUERFREIE_FAELLE };
26
+ /** Was fuer die Summe zaehlt — `InvoiceItemInput` passt unveraendert. */
27
+ export interface SummenPosition {
28
+ quantity: number;
29
+ unitPriceCents: number;
30
+ vatRate: number;
31
+ discountPct?: number;
32
+ }
33
+ /**
34
+ * Netto, USt und Brutto einer Rechnung in Cent, je Satz absteigend — dieselbe
35
+ * Form wie `invoice.totals` in den Antworten.
36
+ *
37
+ * `taxScheme` ist der Steuerfall der Rechnung; ohne Angabe `normal`. Leitet der
38
+ * Server einen steuerfreien Fall ab (etwa eine ig. Lieferung), gehoert dieser
39
+ * Fall hierher — sonst rechnet die Funktion Steuer, die die Rechnung nicht
40
+ * ausweist.
41
+ */
42
+ export declare function rechnungSummen(items: readonly SummenPosition[], priceMode: PriceMode, taxScheme?: TaxScheme): InvoiceTotals;