@stacksjs/commerce 0.75.169 → 0.75.171

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.
@@ -0,0 +1,172 @@
1
+ import { GDPDU_DTD, GDPDU_DTD_FILE } from './gdpdu-dtd';
2
+ import { type DeVatKey } from './kassensichv';
3
+ export declare function dsfinvkTable(name: DsfinvkTableName): DsfinvkTable;
4
+ /** A numeric value with its column's decimals and "," as the decimal symbol. */
5
+ export declare function dsfinvkNumber(value: number, accuracy: number): string;
6
+ /** A cent amount as a DSFinV-K money value ("12,34", "-5,00"), at `accuracy` decimals (2 or 5). */
7
+ export declare function dsfinvkMoney(cents: number, accuracy?: number): string;
8
+ /** Text as the CSV writes it: in quotes (with quotes doubled) when it holds ";", a quote or a line break. */
9
+ export declare function dsfinvkText(value: string): string;
10
+ /** Every problem with one row of a table, as messages naming the column (empty when it is valid). */
11
+ export declare function dsfinvkRowProblems(table: DsfinvkTable, row: DsfinvkRow): Array<{ column: string, problem: string }>;
12
+ /** One table as its CSV file: a header row and one record per row, CRLF-separated. Throws on the first invalid row. */
13
+ export declare function dsfinvkCsv(table: DsfinvkTable, rows: readonly DsfinvkRow[]): string;
14
+ /** The index.xml describing the tables (all twenty unless given), in the shape of the official one. */
15
+ export declare function dsfinvkIndexXml(tables?: readonly DsfinvkTable[], supplier?: { name?: string, location?: string }): string;
16
+ /** Every file of an export of these closings: twenty CSVs, index.xml and the DTD. Throws DsfinvkError on invalid data. */
17
+ export declare function dsfinvkExport(closings: readonly DsfinvkClosing[], options?: { supplier?: { name?: string, location?: string } }): DsfinvkFile[];
18
+ /** Stamm_TSE with TSE_ZERTIFIKAT_III and on when a certificate is longer than 2000 characters (Anhang E). */
19
+ export declare function withCertificateParts(table: DsfinvkTable, parts: number): DsfinvkTable;
20
+ /** The VAT key's rate and DSFinV-K description. */
21
+ export declare function vatKeyInfo(key: DeVatKey): { rate: number, description: string };
22
+ export declare const DSFINVK_VERSION: '2.4';
23
+ /** The twenty tables, in the official index.xml's order. */
24
+ export declare const DSFINVK_TABLES: readonly DsfinvkTable[];
25
+ /**
26
+ * The columns that must be filled (Anhang G's "Mussfelder"), beyond the
27
+ * closing's key (Z_KASSE_ID, Z_ERSTELLUNG, Z_NR) every table has. A TSE row
28
+ * that records a failure (TSE_TA_FEHLER) has no signature to fill in.
29
+ */
30
+ export declare const DSFINVK_MANDATORY: Readonly<Record<DsfinvkTableName, readonly string[]>>;
31
+ /** The values DSFinV-K allows in its closed lists. */
32
+ export declare const DSFINVK_ENUMS: {
33
+ BON_TYP: readonly ['Beleg', 'AVTransfer', 'AVBestellung', 'AVTraining', 'AVBelegstorno', 'AVBelegabbruch', 'AVSachbezug', 'AVRechnung', 'AVSonstige'];
34
+ GV_TYP: readonly ['Umsatz', 'Pfand', 'PfandRueckzahlung', 'Rabatt', 'Aufschlag', 'ZuschussEcht', 'ZuschussUnecht', 'TrinkgeldAG', 'TrinkgeldAN', 'EinzweckgutscheinKauf', 'EinzweckgutscheinEinloesung', 'MehrzweckgutscheinKauf', 'MehrzweckgutscheinEinloesung', 'Forderungsentstehung', 'Forderungsaufloesung', 'Anzahlungseinstellung', 'Anzahlungsaufloesung', 'Anfangsbestand', 'Privatentnahme', 'Privateinlage', 'Geldtransit', 'Lohnzahlung', 'Einzahlung', 'Auszahlung', 'DifferenzSollIst'];
35
+ ZAHLART_TYP: readonly ['Bar', 'Unbar', 'Keine', 'ECKarte', 'Kreditkarte', 'ElZahlungsdienstleister', 'Guthabenkarte'];
36
+ REF_TYP: readonly ['ExterneRechnung', 'ExternerLieferschein', 'Transaktion', 'ExterneSonstige'];
37
+ TSE_TA_VORGANGSART: readonly ['Kassenbeleg-V1', 'Bestellung-V1', 'SonstigerVorgang'];
38
+ TSE_ZEITFORMAT: readonly ['unixTime', 'utcTime', 'utcTimeWithSeconds', 'generalizedTime', 'generalizedTimeWithMilliseconds'];
39
+ TSE_PD_ENCODING: readonly ['UTF-8', 'ASCII'];
40
+ FLAG: readonly ['0', '1']
41
+ };
42
+ /** The DFKA taxonomy version the closing data follows (TAXONOMIE_VERSION). */
43
+ export declare const DFKA_TAXONOMY_VERSION: '2.4';
44
+ export declare interface DsfinvkColumn {
45
+ name: string
46
+ description: string
47
+ type: 'alphanumeric' | 'numeric'
48
+ maxLength?: number
49
+ accuracy?: number
50
+ }
51
+ export declare interface DsfinvkTable {
52
+ file: string
53
+ name: string
54
+ columns: DsfinvkColumn[]
55
+ }
56
+ // ---------------------------------------------------------------------------
57
+ // From a till's closings to the files
58
+ // ---------------------------------------------------------------------------
59
+ export declare interface DsfinvkCompany {
60
+ name: string
61
+ street: string
62
+ postalCode: string
63
+ city: string
64
+ country: string
65
+ taxNumber?: string | null
66
+ vatId?: string | null
67
+ }
68
+ export declare interface DsfinvkRegister {
69
+ id: string
70
+ serialNumber: string
71
+ brand?: string
72
+ model?: string
73
+ softwareBrand?: string
74
+ softwareVersion?: string
75
+ currency: string
76
+ }
77
+ export declare interface DsfinvkTse {
78
+ id: number
79
+ serial: string
80
+ signatureAlgorithm: string
81
+ timeFormat: string
82
+ encoding?: 'UTF-8' | 'ASCII'
83
+ publicKey: string
84
+ certificate: string
85
+ }
86
+ export declare interface DsfinvkLine {
87
+ lineNumber: number
88
+ text: string
89
+ businessCase?: DsfinvkGvTyp
90
+ articleNumber?: string | null
91
+ gtin?: string | null
92
+ groupId?: string | null
93
+ group?: string | null
94
+ quantity: number
95
+ unitGrossCents: number
96
+ grossCents: number
97
+ vatKey: DeVatKey
98
+ voided?: boolean
99
+ }
100
+ export declare interface DsfinvkPayment {
101
+ type: DsfinvkZahlartTyp
102
+ name?: string | null
103
+ cents: number
104
+ currency?: string
105
+ foreignCents?: number | null
106
+ }
107
+ export declare interface DsfinvkReference {
108
+ type: 'Transaktion' | 'ExterneRechnung' | 'ExternerLieferschein' | 'ExterneSonstige'
109
+ name?: string | null
110
+ date?: string | null
111
+ registerId?: string | null
112
+ zNumber?: number | null
113
+ bonId: string
114
+ lineNumber?: number | null
115
+ }
116
+ export declare interface DsfinvkTseTransaction {
117
+ tseId: number
118
+ transactionNumber: number | null
119
+ start: string | number | Date | null
120
+ end: string | number | Date | null
121
+ processType: 'Kassenbeleg-V1' | 'Bestellung-V1' | 'SonstigerVorgang'
122
+ signatureCounter: number | null
123
+ signature: string | null
124
+ error?: string | null
125
+ processData?: string | null
126
+ }
127
+ export declare interface DsfinvkTransaction {
128
+ id: string
129
+ number: number
130
+ type?: DsfinvkBonTyp
131
+ name?: string | null
132
+ terminalId?: string | null
133
+ voided?: boolean
134
+ start: string
135
+ end: string
136
+ operatorId: string
137
+ operatorName?: string | null
138
+ customer?: { name?: string | null, id?: string | null, type?: string | null } | null
139
+ note?: string | null
140
+ lines: DsfinvkLine[]
141
+ payments: DsfinvkPayment[]
142
+ references?: DsfinvkReference[]
143
+ tse?: DsfinvkTseTransaction | null
144
+ allocationGroup?: string | null
145
+ }
146
+ export declare interface DsfinvkClosing {
147
+ register: DsfinvkRegister
148
+ number: number
149
+ createdAt: string
150
+ businessDate?: string | null
151
+ company: DsfinvkCompany
152
+ location: { name: string, street: string, postalCode: string, city: string, country: string, vatId?: string | null }
153
+ terminals?: Array<{ id: string, serialNumber: string, brand?: string, model?: string, softwareBrand?: string, softwareVersion?: string }>
154
+ tses: DsfinvkTse[]
155
+ transactions: DsfinvkTransaction[]
156
+ }
157
+ export declare interface DsfinvkFile { name: string, content: string }
158
+ export type DsfinvkTableName = 'Stamm_Abschluss' | 'Stamm_Orte' | 'Stamm_Kassen' | 'Stamm_Terminals' | 'Stamm_Agenturen' | 'Stamm_TSE' | 'Stamm_USt'
159
+ | 'Z_GV_Typ' | 'Z_Zahlart' | 'Z_Waehrungen'
160
+ | 'Bonkopf' | 'Bonkopf_Zahlarten' | 'Bonpos' | 'Bonpos_Preisfindung' | 'Bonpos_Zusatzinfo' | 'TSE_Transaktionen' | 'Bonkopf_USt' | 'Bonpos_USt' | 'Bonkopf_AbrKreis' | 'Bon_Referenzen';
161
+ export type DsfinvkZahlartTyp = typeof DSFINVK_ENUMS.ZAHLART_TYP[number];
162
+ export type DsfinvkGvTyp = typeof DSFINVK_ENUMS.GV_TYP[number];
163
+ export type DsfinvkBonTyp = typeof DSFINVK_ENUMS.BON_TYP[number];
164
+ export type DsfinvkValue = string | number | null | undefined;
165
+ export type DsfinvkRow = Record<string, DsfinvkValue>;
166
+ export declare class DsfinvkError extends Error {
167
+ readonly table: string;
168
+ readonly row: number;
169
+ readonly column: string;
170
+ constructor(table: string, row: number, column: string, problem: string);
171
+ }
172
+ export { GDPDU_DTD, GDPDU_DTD_FILE };
@@ -0,0 +1,6 @@
1
+ import{DE_VAT_KEYS,splitGross,tseTime}from"./kassensichv";import{GDPDU_DTD,GDPDU_DTD_FILE}from"./gdpdu-dtd";export{GDPDU_DTD,GDPDU_DTD_FILE};export const DSFINVK_VERSION="2.4";const alpha=(name,maxLength,description)=>({name,description,type:"alphanumeric",maxLength}),num=(name,accuracy,description)=>({name,description,type:"numeric",accuracy});export const DSFINVK_TABLES=[{file:"cashpointclosing.csv",name:"Stamm_Abschluss",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("Z_BUCHUNGSTAG",25,"Vom Erstellungsdatum abweichender Verbuchungstag"),alpha("TAXONOMIE_VERSION",10,"Version der DFKA-Taxonomie-Kasse"),alpha("Z_START_ID",40,"Erste BON_ID im Abschluss"),alpha("Z_ENDE_ID",40,"Letzte BON_ID im Abschluss"),alpha("NAME",60,"Name des Unternehmens"),alpha("STRASSE",60,"Stra\xDFe"),alpha("PLZ",10,"Postleitzahl"),alpha("ORT",62,"Ort"),alpha("LAND",3,"Land"),alpha("STNR",20,"Steuernummer des Unternehmens"),alpha("USTID",15,"USTID"),num("Z_SE_ZAHLUNGEN",2,"Summe aller Zahlungen"),num("Z_SE_BARZAHLUNGEN",2,"Summe aller Barzahlungen")]},{file:"location.csv",name:"Stamm_Orte",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("LOC_NAME",60,"Name des Standortes"),alpha("LOC_STRASSE",60,"Stra\xDFe"),alpha("LOC_PLZ",10,"Postleitzahl"),alpha("LOC_ORT",62,"Ort"),alpha("LOC_LAND",3,"Land"),alpha("LOC_USTID",15,"USTID")]},{file:"cashregister.csv",name:"Stamm_Kassen",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("KASSE_BRAND",50,"Marke der Kasse"),alpha("KASSE_MODELL",50,"Modellbezeichnung"),alpha("KASSE_SERIENNR",70,"Seriennummer der Kasse"),alpha("KASSE_SW_BRAND",50,"Markenbezeichnung der Software"),alpha("KASSE_SW_VERSION",50,"Version der Software"),alpha("KASSE_BASISWAEH_CODE",3,"Basisw\xE4hrung der Kasse"),alpha("KEINE_UST_ZUORDNUNG",1,"UmsatzsteuerNichtErmittelbar (bei sp\xE4terem Zahlungseingang)")]},{file:"slaves.csv",name:"Stamm_Terminals",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("TERMINAL_ID",50,"ID des Terminals"),alpha("TERMINAL_BRAND",50,"Marke der Terminals"),alpha("TERMINAL_MODELL",50,"Modellbezeichnung des Terminals"),alpha("TERMINAL_SERIENNR",70,"Seriennummer des Terminals"),alpha("TERMINAL_SW_BRAND",50,"Markenbezeichnung der Software"),alpha("TERMINAL_SW_VERSION",50,"Version der Software")]},{file:"pa.csv",name:"Stamm_Agenturen",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),num("AGENTUR_ID",0,"ID der Agentur"),alpha("AGENTUR_NAME",60,"Name des Auftraggebers"),alpha("AGENTUR_STRASSE",60,"Stra\xDFe"),alpha("AGENTUR_PLZ",10,"Postleitzahl"),alpha("AGENTUR_ORT",62,"Ort"),alpha("AGENTUR_LAND",3,"Land"),alpha("AGENTUR_STNR",20,"Steuernummer des Auftraggebers"),alpha("AGENTUR_USTID",15,"USTID des Auftraggebers")]},{file:"tse.csv",name:"Stamm_TSE",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),num("TSE_ID",0,"ID der TSE - wird nur zur Referenzierung innerhalb eines Kassenabschlusses verwendet"),alpha("TSE_SERIAL",68,"Seriennummer der TSE (Entspricht laut TR-03153 Abschnitt 7.5. dem Hashwert des im Zertifikat enthaltenen Schl\xFCssels in Octet-String-Darstellung)"),alpha("TSE_SIG_ALGO",21,"Der von der TSE verwendete Signaturalgo-rithmus"),alpha("TSE_ZEITFORMAT",31,"Das von der TSE verwendete Format f\xFCr die Log-Time - 'unixTime', 'utcTime' = YYMMDDhhmmZ, 'utcTimeWithSeconds' = YYMMDDhhmmssZ, 'generalizedTime' = YYYYMMDDhhmmssZ, 'generalizedTimeWithMilliseconds' = YYYYMMDDhhmmss.fffZ"),alpha("TSE_PD_ENCODING",5,"Das Text-Encoding der ProcessData (UTF-8 oder ASCII)"),alpha("TSE_PUBLIC_KEY",512,"\xD6ffentlicher Schl\xFCssel - extrahiert aus dem Zertifikat der TSE - in base64-Codierung"),alpha("TSE_ZERTIFIKAT_I",1000,"Erste 1000 Zeichen des base64-codierten Zertifikats der TSE (in base64-Codierung)"),alpha("TSE_ZERTIFIKAT_II",1000,"Ggf. Rest des base64-codierten Zertifikats der TSE (in base64-Codierung)")]},{file:"vat.csv",name:"Stamm_USt",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),num("UST_SCHLUESSEL",0,"ID des Umsatzsteuersatzes"),num("UST_SATZ",2,"Prozentsatz"),alpha("UST_BESCHR",55,"Beschreibung")]},{file:"businesscases.csv",name:"Z_GV_Typ",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("GV_TYP",30,"Gesch\xE4ftsvorfall-Art"),alpha("GV_NAME",40,"Name des Gesch\xE4ftsvorfalls"),num("AGENTUR_ID",0,"ID der Agentur"),num("UST_SCHLUESSEL",0,"ID des USt-Satzes"),num("Z_UMS_BRUTTO",5,"Bruttoumsatz"),num("Z_UMS_NETTO",5,"Nettoumsatz"),num("Z_UST",5,"Umsatzsteuer")]},{file:"payment.csv",name:"Z_Zahlart",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("ZAHLART_TYP",25,"Typ der Zahlart"),alpha("ZAHLART_NAME",60,"Name der Zahlart"),num("Z_ZAHLART_BETRAG",2,"Betrag")]},{file:"cash_per_currency.csv",name:"Z_Waehrungen",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("ZAHLART_WAEH",3,"W\xE4hrung"),num("ZAHLART_BETRAG_WAEH",2,"Betrag")]},{file:"transactions.csv",name:"Bonkopf",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("BON_ID",40,"Vorgangs-ID"),num("BON_NR",0,"Bonnummer"),alpha("BON_TYP",30,"Bontyp"),alpha("BON_NAME",60,"Zusatz-Beschreibung zum Bontyp"),alpha("TERMINAL_ID",50,"ID des Erfassungsterminals"),alpha("BON_STORNO",1,"Storno-Kennzeichen"),alpha("BON_START",30,"Zeitpunkt des Vorgangsstarts"),alpha("BON_ENDE",30,"Zeitpunkt der Vorgangsbeendigung"),alpha("BEDIENER_ID",50,"Bediener-ID"),alpha("BEDIENER_NAME",50,"Bediener-Name"),num("UMS_BRUTTO",2,"Brutto-Gesamtumsatz"),alpha("KUNDE_NAME",50,"Name des Leistungsempf\xE4ngers"),alpha("KUNDE_ID",50,"Kundennummer des Leistungsempf\xE4ngers"),alpha("KUNDE_TYP",50,"Art des Leistungsempf\xE4ngers (z. B. Mitarbeiter)"),alpha("KUNDE_STRASSE",60,"Stra\xDFe und Hausnummer des Leistungsempf\xE4ngers"),alpha("KUNDE_PLZ",10,"PLZ des Leistungsempf\xE4ngers"),alpha("KUNDE_ORT",62,"Ort des Leistungsempf\xE4ngers"),alpha("KUNDE_LAND",3,"Land des Leistungsempf\xE4ngers"),alpha("KUNDE_USTID",15,"UStID des Leistungsempf\xE4ngers"),alpha("BON_NOTIZ",255,"Zus\xE4tzliche Informationen zum Bonkopf")]},{file:"datapayment.csv",name:"Bonkopf_Zahlarten",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("BON_ID",40,"Vorgangs-ID"),alpha("ZAHLART_TYP",25,"Typ der Zahlart"),alpha("ZAHLART_NAME",60,"Name der Zahlart"),alpha("ZAHLWAEH_CODE",3,"W\xE4hrungscode"),num("ZAHLWAEH_BETRAG",2,"Betrag in Fremdw\xE4hrung"),num("BASISWAEH_BETRAG",2,"Betrag in Basisw\xE4hrung (i.d.R. EUR)")]},{file:"lines.csv",name:"Bonpos",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("BON_ID",40,"Vorgangs-ID"),alpha("POS_ZEILE",50,"Zeilennummer"),alpha("GUTSCHEIN_NR",50,"Gutschein-Nr."),alpha("ARTIKELTEXT",255,"Artikeltext"),alpha("POS_TERMINAL_ID",50,"ID des POS-Terminals"),alpha("GV_TYP",30,"Gesch\xE4ftsvorfall-Art"),alpha("GV_NAME",40,"Name des Gesch\xE4ftsvorfalls"),alpha("INHAUS",1,"Verzehr an Ort und Stelle"),alpha("P_STORNO",1,"Positionsstorno-Kennzeichnung"),num("AGENTUR_ID",0,"ID der Agentur"),alpha("ART_NR",50,"Artikelnummer"),alpha("GTIN",50,"GTIN"),alpha("WARENGR_ID",40,"Warengruppen-ID"),alpha("WARENGR",50,"Bezeichnung Warengruppe"),num("MENGE",3,"Menge"),num("FAKTOR",3,"Faktor, z. B. Gebindegr\xF6\xDFen"),alpha("EINHEIT",50,"Ma\xDFeinheit, z. B. kg, Liter oder St\xFCck"),num("STK_BR",5,"Preis pro Einheit inkl. USt")]},{file:"itemamounts.csv",name:"Bonpos_Preisfindung",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("BON_ID",40,"Vorgangs-ID"),alpha("POS_ZEILE",50,"Zeilennummer"),alpha("TYP",20,"Basispreis, Rabatt oder Zuschlag"),num("UST_SCHLUESSEL",0,"ID des USt-Satzes"),num("PF_BRUTTO",5,"Bruttoumsatz"),num("PF_NETTO",5,"Nettoumsatz"),num("PF_UST",5,"Umsatzsteuer")]},{file:"subitems.csv",name:"Bonpos_Zusatzinfo",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("BON_ID",40,"Vorgangs-ID"),alpha("POS_ZEILE",50,"Zeilennummer"),alpha("ZI_ART_NR",50,"Artikelnummer"),alpha("ZI_GTIN",50,"GTIN"),alpha("ZI_NAME",60,"Artikelbezeichnung"),alpha("ZI_WARENGR_ID",40,"Warengruppen-ID"),alpha("ZI_WARENGR",50,"Bezeichnung Warengruppe"),num("ZI_MENGE",3,"Menge"),num("ZI_FAKTOR",3,"Faktor, z. B. Gebindegr\xF6\xDFen"),alpha("ZI_EINHEIT",50,"Ma\xDFeinheit, z. B. kg, Liter oder St\xFCck"),num("ZI_UST_SCHLUESSEL",0,"ID USt-Satz des Basispreises"),num("ZI_BASISPREIS_BRUTTO",5,"Basispreis brutto"),num("ZI_BASISPREIS_NETTO",5,"Basispreis netto"),num("ZI_BASISPREIS_UST",5,"Basispreis USt")]},{file:"transactions_tse.csv",name:"TSE_Transaktionen",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("BON_ID",40,"Vorgangs-ID"),num("TSE_ID",0,"Die Id der f\xFCr eine Transaktion verwendeten TSE"),num("TSE_TANR",0,"Die Transaktionsnummer der TSE-Transaktion"),alpha("TSE_TA_START",30,"Die Log-Time der StartTransaction-Operation"),alpha("TSE_TA_ENDE",30,"Die Log-Time der FinishTransaction-Operation"),alpha("TSE_TA_VORGANGSART",30,"Der processType der FinishTransaction-Operation"),num("TSE_TA_SIGZ",0,"Der Signaturz\xE4hler der FinishTransaction-Operation"),alpha("TSE_TA_SIG",512,"Die Signatur der FinishTransaction-Operation"),alpha("TSE_TA_FEHLER",200,"Beschreibung des TSE-Ausfalls oder Fehlers"),alpha("TSE_VORGANGSDATEN",1000,"An die zertifizierte technische Sicherheitseinrichtung \xFCbergebene Daten des Vorgangs (optional)")]},{file:"transactions_vat.csv",name:"Bonkopf_USt",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("BON_ID",40,"Vorgangs-ID"),num("UST_SCHLUESSEL",0,"ID des USt-Satzes"),num("BON_BRUTTO",5,"Bruttoumsatz"),num("BON_NETTO",5,"Nettoumsatz"),num("BON_UST",5,"Umsatzsteuer")]},{file:"lines_vat.csv",name:"Bonpos_USt",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("BON_ID",40,"Vorgangs-ID"),alpha("POS_ZEILE",50,"Zeilennummer"),num("UST_SCHLUESSEL",0,"ID des USt-Satzes"),num("POS_BRUTTO",5,"Bruttoumsatz"),num("POS_NETTO",5,"Nettoumsatz"),num("POS_UST",5,"Umsatzsteuer")]},{file:"allocation_groups.csv",name:"Bonkopf_AbrKreis",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("BON_ID",40,"Vorgangs-ID"),alpha("ABRECHNUNGSKREIS",50,"z. B. Tischnummer")]},{file:"references.csv",name:"Bon_Referenzen",columns:[alpha("Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),alpha("Z_ERSTELLUNG",30,"Zeitpunkt des Kassenabschlusses"),num("Z_NR",0,"Nr. des Kassenabschlusses"),alpha("BON_ID",40,"Vorgangs-ID"),alpha("POS_ZEILE",50,"Zeilennummer des referenzierenden Vorgangs (nicht bei Verweis aus einem Bonkopf heraus)"),alpha("REF_TYP",20,"Art der Referenz"),alpha("REF_NAME",40,"Beschreibung bei Art \u201CExterneSonstige\u201D"),alpha("REF_DATUM",30,"Datum des Kassenabschlusses"),alpha("REF_Z_KASSE_ID",50,"ID der (Abschluss-) Kasse"),num("REF_Z_NR",0,"Nr. des Kassenabschlusses"),alpha("REF_BON_ID",40,"Vorgangs-ID")]}];export function dsfinvkTable(name){const table=DSFINVK_TABLES.find((entry)=>entry.name===name);if(!table)throw Error(`Unknown DSFinV-K table ${name}`);return table}export const DSFINVK_MANDATORY={Stamm_Abschluss:["TAXONOMIE_VERSION","Z_START_ID","Z_ENDE_ID","NAME","STRASSE","PLZ","ORT","LAND","Z_SE_ZAHLUNGEN","Z_SE_BARZAHLUNGEN"],Stamm_Orte:["LOC_NAME","LOC_STRASSE","LOC_PLZ","LOC_ORT","LOC_LAND"],Stamm_Kassen:["KASSE_SERIENNR","KASSE_BASISWAEH_CODE"],Stamm_Terminals:["TERMINAL_ID","TERMINAL_SERIENNR"],Stamm_Agenturen:["AGENTUR_ID","AGENTUR_NAME","AGENTUR_STRASSE","AGENTUR_PLZ","AGENTUR_ORT","AGENTUR_LAND"],Stamm_TSE:["TSE_ID","TSE_SERIAL","TSE_SIG_ALGO","TSE_ZEITFORMAT","TSE_PD_ENCODING","TSE_PUBLIC_KEY","TSE_ZERTIFIKAT_I"],Stamm_USt:["UST_SCHLUESSEL","UST_SATZ","UST_BESCHR"],Z_GV_Typ:["GV_TYP","UST_SCHLUESSEL","Z_UMS_BRUTTO","Z_UMS_NETTO","Z_UST"],Z_Zahlart:["ZAHLART_TYP","Z_ZAHLART_BETRAG"],Z_Waehrungen:["ZAHLART_WAEH","ZAHLART_BETRAG_WAEH"],Bonkopf:["BON_ID","BON_NR","BON_TYP","BON_STORNO","BON_START","BON_ENDE","BEDIENER_ID","UMS_BRUTTO"],Bonkopf_Zahlarten:["BON_ID","ZAHLART_TYP","ZAHLWAEH_CODE","BASISWAEH_BETRAG"],Bonpos:["BON_ID","POS_ZEILE","ARTIKELTEXT","GV_TYP"],Bonpos_Preisfindung:["BON_ID","POS_ZEILE","UST_SCHLUESSEL"],Bonpos_Zusatzinfo:["BON_ID","POS_ZEILE","ZI_ART_NR","ZI_MENGE","ZI_UST_SCHLUESSEL"],TSE_Transaktionen:["BON_ID","TSE_ID","TSE_TANR","TSE_TA_START","TSE_TA_ENDE","TSE_TA_VORGANGSART","TSE_TA_SIGZ","TSE_TA_SIG"],Bonkopf_USt:["BON_ID","UST_SCHLUESSEL","BON_BRUTTO","BON_NETTO","BON_UST"],Bonpos_USt:["BON_ID","POS_ZEILE","UST_SCHLUESSEL"],Bonkopf_AbrKreis:["BON_ID"],Bon_Referenzen:["BON_ID","REF_TYP","REF_BON_ID"]},DSFINVK_ENUMS={BON_TYP:["Beleg","AVTransfer","AVBestellung","AVTraining","AVBelegstorno","AVBelegabbruch","AVSachbezug","AVRechnung","AVSonstige"],GV_TYP:["Umsatz","Pfand","PfandRueckzahlung","Rabatt","Aufschlag","ZuschussEcht","ZuschussUnecht","TrinkgeldAG","TrinkgeldAN","EinzweckgutscheinKauf","EinzweckgutscheinEinloesung","MehrzweckgutscheinKauf","MehrzweckgutscheinEinloesung","Forderungsentstehung","Forderungsaufloesung","Anzahlungseinstellung","Anzahlungsaufloesung","Anfangsbestand","Privatentnahme","Privateinlage","Geldtransit","Lohnzahlung","Einzahlung","Auszahlung","DifferenzSollIst"],ZAHLART_TYP:["Bar","Unbar","Keine","ECKarte","Kreditkarte","ElZahlungsdienstleister","Guthabenkarte"],REF_TYP:["ExterneRechnung","ExternerLieferschein","Transaktion","ExterneSonstige"],TSE_TA_VORGANGSART:["Kassenbeleg-V1","Bestellung-V1","SonstigerVorgang"],TSE_ZEITFORMAT:["unixTime","utcTime","utcTimeWithSeconds","generalizedTime","generalizedTimeWithMilliseconds"],TSE_PD_ENCODING:["UTF-8","ASCII"],FLAG:["0","1"]};const ENUM_COLUMNS={BON_TYP:DSFINVK_ENUMS.BON_TYP,GV_TYP:DSFINVK_ENUMS.GV_TYP,ZAHLART_TYP:DSFINVK_ENUMS.ZAHLART_TYP,REF_TYP:DSFINVK_ENUMS.REF_TYP,TSE_TA_VORGANGSART:DSFINVK_ENUMS.TSE_TA_VORGANGSART,TSE_ZEITFORMAT:DSFINVK_ENUMS.TSE_ZEITFORMAT,TSE_PD_ENCODING:DSFINVK_ENUMS.TSE_PD_ENCODING,BON_STORNO:DSFINVK_ENUMS.FLAG,P_STORNO:DSFINVK_ENUMS.FLAG,INHAUS:DSFINVK_ENUMS.FLAG,KEINE_UST_ZUORDNUNG:DSFINVK_ENUMS.FLAG},DATETIME=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})?$/,TSE_DATETIME=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,DATE=/^\d{4}-\d{2}-\d{2}$/;export class DsfinvkError extends Error{table;row;column;constructor(table,row,column,problem){super(`${table} row ${row}, ${column}: ${problem}`);this.table=table;this.row=row;this.column=column;this.name="DsfinvkError"}}export function dsfinvkNumber(value,accuracy){if(!Number.isFinite(value))throw Error("Not a number");return value.toFixed(accuracy).replace(".",",")}export function dsfinvkMoney(cents,accuracy=2){if(!Number.isSafeInteger(cents))throw Error("An amount must be whole cents");const sign=cents<0?"-":"",abs=Math.abs(cents),decimals=String(abs%100).padStart(2,"0")+"0".repeat(Math.max(0,accuracy-2));return`${sign}${Math.floor(abs/100)}${accuracy>0?`,${decimals}`:""}`}export function dsfinvkText(value){return/[;"\r\n]/.test(value)?`"${value.replace(/"/g,'""')}"`:value}export function dsfinvkRowProblems(table,row){const problems=[],known=new Set(table.columns.map((column)=>column.name));for(const key of Object.keys(row))if(!known.has(key))problems.push({column:key,problem:"is not a column of this table"});const filled=(name)=>row[name]!==void 0&&row[name]!==null&&row[name]!=="",failedTse=table.name==="TSE_Transaktionen"&&filled("TSE_TA_FEHLER"),mandatory=["Z_KASSE_ID","Z_ERSTELLUNG","Z_NR",...DSFINVK_MANDATORY[table.name]??[]];for(const name of mandatory){if(failedTse&&["TSE_TANR","TSE_TA_START","TSE_TA_ENDE","TSE_TA_SIGZ","TSE_TA_SIG"].includes(name))continue;if(!filled(name))problems.push({column:name,problem:"must be filled"})}if(table.name==="Stamm_Abschluss"&&!filled("STNR")&&!filled("USTID"))problems.push({column:"STNR",problem:"a tax number (STNR) or VAT id (USTID) is needed"});for(const column of table.columns){const value=row[column.name];if(value===void 0||value===null||value==="")continue;if(column.type==="numeric"){const number=typeof value==="number"?value:Number(String(value).replace(",","."));if(!Number.isFinite(number))problems.push({column:column.name,problem:`is not a number (${String(value)})`});else if((column.accuracy??0)===0&&!Number.isInteger(number))problems.push({column:column.name,problem:"must be a whole number"});continue}const text=String(value);if(column.maxLength&&[...text].length>column.maxLength)problems.push({column:column.name,problem:`is longer than ${column.maxLength} characters`});const allowed=ENUM_COLUMNS[column.name];if(allowed&&!allowed.includes(text))problems.push({column:column.name,problem:`"${text}" is not one of ${allowed.join(", ")}`});if(column.name==="KASSE_SERIENNR"&&/[/_]/.test(text))problems.push({column:column.name,problem:'may contain neither "/" nor "_"'});if(["Z_ERSTELLUNG","BON_START","BON_ENDE","REF_DATUM"].includes(column.name)&&!DATETIME.test(text))problems.push({column:column.name,problem:`is not an ISO 8601 time (${text})`});if(["TSE_TA_START","TSE_TA_ENDE"].includes(column.name)&&!TSE_DATETIME.test(text))problems.push({column:column.name,problem:`is not YYYY-MM-DDThh:mm:ss.fffZ (${text})`});if(column.name==="Z_BUCHUNGSTAG"&&!DATE.test(text))problems.push({column:column.name,problem:`is not a date (${text})`});if(column.name==="TSE_VORGANGSDATEN"&&text!==text.trim())problems.push({column:column.name,problem:"may not start or end with a space"})}return problems}export function dsfinvkCsv(table,rows){const lines=[table.columns.map((column)=>column.name).join(";")];rows.forEach((row,index)=>{const problem=dsfinvkRowProblems(table,row)[0];if(problem)throw new DsfinvkError(table.name,index+1,problem.column,problem.problem);lines.push(table.columns.map((column)=>{const value=row[column.name];if(value===void 0||value===null||value==="")return"";if(column.type==="numeric")return typeof value==="number"?dsfinvkNumber(value,column.accuracy??0):String(value);return dsfinvkText(String(value))}).join(";"))});return`${lines.join(`\r
2
+ `)}\r
3
+ `}function xmlText(value){return value.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}export function dsfinvkIndexXml(tables=DSFINVK_TABLES,supplier={}){const out=['<?xml version="1.0" encoding="utf-8"?>',`<!DOCTYPE DataSet SYSTEM "${GDPDU_DTD_FILE}">`,"<DataSet>"," <Version>1.0</Version>"," <DataSupplier>",supplier.name?` <Name>${xmlText(supplier.name)}</Name>`:" <Name />",supplier.location?` <Location>${xmlText(supplier.location)}</Location>`:" <Location />"," <Comment>Datentraegerueberlassung nach GDPdU vom 12.11.2010</Comment>"," </DataSupplier>"," <Media>"," <Name>CD Nummer 1</Name>"];for(const table of tables){out.push(" <Table>",` <URL>${table.file}</URL>`,` <Name>${table.name}</Name>`,` <Description>${table.file}</Description>`," <UTF8 />"," <DecimalSymbol>,</DecimalSymbol>"," <DigitGroupingSymbol>.</DigitGroupingSymbol>"," <Range>"," <From>2</From>"," </Range>"," <VariableLength>"," <ColumnDelimiter>;</ColumnDelimiter>"," <RecordDelimiter>&#xD;&#xA;</RecordDelimiter>",' <TextEncapsulator>"</TextEncapsulator>');for(const column of table.columns){out.push(" <VariableColumn>",` <Name>${column.name}</Name>`,` <Description>${xmlText(column.description)}</Description>`);if(column.type==="alphanumeric"){out.push(" <AlphaNumeric />");if(column.maxLength)out.push(` <MaxLength>${column.maxLength}</MaxLength>`)}else if(column.accuracy)out.push(" <Numeric>",` <Accuracy>${column.accuracy}</Accuracy>`," </Numeric>");else out.push(" <Numeric />");out.push(" </VariableColumn>")}out.push(" </VariableLength>"," </Table>")}out.push(" </Media>","</DataSet>");return`${out.join(`\r
4
+ `)}\r
5
+ `}export const DFKA_TAXONOMY_VERSION="2.4";export function dsfinvkExport(closings,options={}){const rows=new Map(DSFINVK_TABLES.map((table)=>[table.name,[]])),add=(table,row)=>rows.get(table).push(row);let certificateParts=2;for(const closing of closings){const key={Z_KASSE_ID:closing.register.id,Z_ERSTELLUNG:closing.createdAt,Z_NR:closing.number};let allPayments=0,cashPayments=0;const businessCases=new Map,paymentTotals=new Map,cashByCurrency=new Map,vatKeys=new Set;for(const transaction of closing.transactions){const lineNumbers=new Set,vatGross=new Map;for(const line of transaction.lines){if(lineNumbers.has(line.lineNumber))throw new DsfinvkError("Bonpos",0,"POS_ZEILE",`${line.lineNumber} appears twice in receipt ${transaction.id}`);lineNumbers.add(line.lineNumber);if(!(line.vatKey in DE_VAT_KEYS))throw new DsfinvkError("Bonpos_USt",0,"UST_SCHLUESSEL",`${line.vatKey} is not a VAT key this export knows`);vatKeys.add(line.vatKey);vatGross.set(line.vatKey,(vatGross.get(line.vatKey)??0)+line.grossCents);const split=splitGross(line.grossCents,DE_VAT_KEYS[line.vatKey].rate);add("Bonpos",{...key,BON_ID:transaction.id,POS_ZEILE:String(line.lineNumber),ARTIKELTEXT:line.text,GV_TYP:line.businessCase??"Umsatz",INHAUS:"0",P_STORNO:line.voided?"1":"0",AGENTUR_ID:0,ART_NR:line.articleNumber??"",GTIN:line.gtin??"",WARENGR_ID:line.groupId??"",WARENGR:line.group??"",MENGE:line.quantity,FAKTOR:1,STK_BR:line.unitGrossCents/100});add("Bonpos_USt",{...key,BON_ID:transaction.id,POS_ZEILE:String(line.lineNumber),UST_SCHLUESSEL:line.vatKey,POS_BRUTTO:dsfinvkMoney(line.grossCents,5),POS_NETTO:dsfinvkMoney(split.net,5),POS_UST:dsfinvkMoney(split.vat,5)});if((transaction.type??"Beleg")==="Beleg"){const caseKey=`${line.businessCase??"Umsatz"}:${line.vatKey}`,entry=businessCases.get(caseKey)??{type:line.businessCase??"Umsatz",vatKey:line.vatKey,gross:0};entry.gross+=line.grossCents;businessCases.set(caseKey,entry)}}const gross=[...vatGross.values()].reduce((sum,cents)=>sum+cents,0);add("Bonkopf",{...key,BON_ID:transaction.id,BON_NR:transaction.number,BON_TYP:transaction.type??"Beleg",BON_NAME:transaction.name??"",TERMINAL_ID:transaction.terminalId??"",BON_STORNO:transaction.voided?"1":"0",BON_START:transaction.start,BON_ENDE:transaction.end,BEDIENER_ID:transaction.operatorId,BEDIENER_NAME:transaction.operatorName??"",UMS_BRUTTO:dsfinvkMoney(gross),KUNDE_NAME:transaction.customer?.name??"",KUNDE_ID:transaction.customer?.id??"",KUNDE_TYP:transaction.customer?.type??"",BON_NOTIZ:transaction.note??""});for(const[vatKey,cents]of[...vatGross.entries()].sort(([a],[b])=>a-b)){const split=splitGross(cents,DE_VAT_KEYS[vatKey].rate);add("Bonkopf_USt",{...key,BON_ID:transaction.id,UST_SCHLUESSEL:vatKey,BON_BRUTTO:dsfinvkMoney(cents,5),BON_NETTO:dsfinvkMoney(split.net,5),BON_UST:dsfinvkMoney(split.vat,5)})}if(transaction.allocationGroup)add("Bonkopf_AbrKreis",{...key,BON_ID:transaction.id,ABRECHNUNGSKREIS:transaction.allocationGroup});const paid=transaction.payments.reduce((sum,payment)=>sum+payment.cents,0);if((transaction.type??"Beleg")==="Beleg"&&paid!==gross)throw new DsfinvkError("Bonkopf_Zahlarten",0,"BASISWAEH_BETRAG",`receipt ${transaction.id} was paid ${paid} cents for ${gross} cents of goods`);for(const payment of transaction.payments){const currency=(payment.currency||closing.register.currency).toUpperCase();add("Bonkopf_Zahlarten",{...key,BON_ID:transaction.id,ZAHLART_TYP:payment.type,ZAHLART_NAME:payment.name??"",ZAHLWAEH_CODE:currency,ZAHLWAEH_BETRAG:payment.foreignCents==null?"":dsfinvkMoney(payment.foreignCents),BASISWAEH_BETRAG:dsfinvkMoney(payment.cents)});if((transaction.type??"Beleg")!=="Beleg")continue;allPayments+=payment.cents;const totalKey=`${payment.type}:${payment.name??""}`,total=paymentTotals.get(totalKey)??{type:payment.type,name:payment.name??null,cents:0};total.cents+=payment.cents;paymentTotals.set(totalKey,total);if(payment.type==="Bar"){cashPayments+=payment.cents;cashByCurrency.set(currency,(cashByCurrency.get(currency)??0)+(payment.foreignCents??payment.cents))}}for(const reference of transaction.references??[])add("Bon_Referenzen",{...key,BON_ID:transaction.id,POS_ZEILE:reference.lineNumber==null?"":String(reference.lineNumber),REF_TYP:reference.type,REF_NAME:reference.name??"",REF_DATUM:reference.type==="Transaktion"?reference.date??"":"",REF_Z_KASSE_ID:reference.type==="Transaktion"?reference.registerId??"":"",REF_Z_NR:reference.type==="Transaktion"?reference.zNumber??"":"",REF_BON_ID:reference.bonId});if(transaction.tse){const tse=transaction.tse;add("TSE_Transaktionen",{...key,BON_ID:transaction.id,TSE_ID:tse.tseId,TSE_TANR:tse.transactionNumber??"",TSE_TA_START:tse.start==null?"":tseTime(tse.start),TSE_TA_ENDE:tse.end==null?"":tseTime(tse.end),TSE_TA_VORGANGSART:tse.processType,TSE_TA_SIGZ:tse.signatureCounter??"",TSE_TA_SIG:tse.signature??"",TSE_TA_FEHLER:tse.error??"",TSE_VORGANGSDATEN:tse.processData??""})}}const sorted=[...closing.transactions].sort((a,b)=>a.number-b.number);add("Stamm_Abschluss",{...key,Z_BUCHUNGSTAG:closing.businessDate??"",TAXONOMIE_VERSION:DFKA_TAXONOMY_VERSION,Z_START_ID:sorted[0]?.id??"",Z_ENDE_ID:sorted[sorted.length-1]?.id??"",NAME:closing.company.name,STRASSE:closing.company.street,PLZ:closing.company.postalCode,ORT:closing.company.city,LAND:closing.company.country,STNR:closing.company.taxNumber??"",USTID:closing.company.vatId??"",Z_SE_ZAHLUNGEN:dsfinvkMoney(allPayments),Z_SE_BARZAHLUNGEN:dsfinvkMoney(cashPayments)});add("Stamm_Orte",{...key,LOC_NAME:closing.location.name,LOC_STRASSE:closing.location.street,LOC_PLZ:closing.location.postalCode,LOC_ORT:closing.location.city,LOC_LAND:closing.location.country,LOC_USTID:closing.location.vatId??""});add("Stamm_Kassen",{...key,KASSE_BRAND:closing.register.brand??"",KASSE_MODELL:closing.register.model??"",KASSE_SERIENNR:closing.register.serialNumber,KASSE_SW_BRAND:closing.register.softwareBrand??"",KASSE_SW_VERSION:closing.register.softwareVersion??"",KASSE_BASISWAEH_CODE:closing.register.currency.toUpperCase(),KEINE_UST_ZUORDNUNG:vatKeys.has(7)?"1":""});for(const terminal of closing.terminals??[])add("Stamm_Terminals",{...key,TERMINAL_ID:terminal.id,TERMINAL_BRAND:terminal.brand??"",TERMINAL_MODELL:terminal.model??"",TERMINAL_SERIENNR:terminal.serialNumber,TERMINAL_SW_BRAND:terminal.softwareBrand??"",TERMINAL_SW_VERSION:terminal.softwareVersion??""});for(const tse of closing.tses){const parts=tse.certificate.match(/.{1,1000}/gs)??[""];certificateParts=Math.max(certificateParts,parts.length);const row={...key,TSE_ID:tse.id,TSE_SERIAL:tse.serial,TSE_SIG_ALGO:tse.signatureAlgorithm,TSE_ZEITFORMAT:tse.timeFormat,TSE_PD_ENCODING:tse.encoding??"UTF-8",TSE_PUBLIC_KEY:tse.publicKey};parts.forEach((part,index)=>{row[certificateColumn(index)]=part});add("Stamm_TSE",row)}for(const vatKey of[...vatKeys].sort((a,b)=>a-b)){const info=DE_VAT_KEYS[vatKey];add("Stamm_USt",{...key,UST_SCHLUESSEL:vatKey,UST_SATZ:info.rate,UST_BESCHR:info.description})}for(const entry of[...businessCases.values()].sort((a,b)=>a.type.localeCompare(b.type)||a.vatKey-b.vatKey)){const split=splitGross(entry.gross,DE_VAT_KEYS[entry.vatKey].rate);add("Z_GV_Typ",{...key,GV_TYP:entry.type,GV_NAME:"",AGENTUR_ID:0,UST_SCHLUESSEL:entry.vatKey,Z_UMS_BRUTTO:dsfinvkMoney(entry.gross,5),Z_UMS_NETTO:dsfinvkMoney(split.net,5),Z_UST:dsfinvkMoney(split.vat,5)})}for(const total of paymentTotals.values())add("Z_Zahlart",{...key,ZAHLART_TYP:total.type,ZAHLART_NAME:total.name??"",Z_ZAHLART_BETRAG:dsfinvkMoney(total.cents)});for(const[currency,cents]of[...cashByCurrency.entries()].sort(([a],[b])=>a.localeCompare(b)))add("Z_Waehrungen",{...key,ZAHLART_WAEH:currency,ZAHLART_BETRAG_WAEH:dsfinvkMoney(cents)})}const tables=DSFINVK_TABLES.map((table)=>table.name==="Stamm_TSE"?withCertificateParts(table,certificateParts):table),files=tables.map((table)=>({name:table.file,content:dsfinvkCsv(table,rows.get(table.name))}));files.push({name:"index.xml",content:dsfinvkIndexXml(tables,options.supplier)});files.push({name:GDPDU_DTD_FILE,content:GDPDU_DTD.replace(/\r?\n/g,`\r
6
+ `)});return files}const ROMAN=["I","II","III","IV","V","VI","VII","VIII","IX","X"];function certificateColumn(index){if(index>=ROMAN.length)throw Error("A TSE certificate longer than 10000 characters");return`TSE_ZERTIFIKAT_${ROMAN[index]}`}export function withCertificateParts(table,parts){if(parts<=2)return table;const extra=[];for(let index=2;index<parts;index++)extra.push(alpha(certificateColumn(index),1000,"Ggf. Rest des base64-codierten Zertifikats der TSE (in base64-Codierung)"));return{...table,columns:[...table.columns,...extra]}}export function vatKeyInfo(key){return{rate:DE_VAT_KEYS[key].rate,description:DE_VAT_KEYS[key].description}}
@@ -0,0 +1,142 @@
1
+ /** A fiskaly SIGN DE client for one API key. Tokens are cached and refreshed. */
2
+ export declare function fiskalySignDe(options: FiskalyOptions): {
3
+ status: unknown.status;
4
+ json: unknown
5
+ } | {
6
+ authenticate: () => Promise<{ organizationId: string | null, environment: string | null }>;
7
+ createTss: (tssId: string, metadata?: Record<string, string>) => boolean;
8
+ retrieveTss: (tssId: string) => boolean;
9
+ setTssState: (tssId: string, state: 'UNINITIALIZED' | 'INITIALIZED' | 'DISABLED') => boolean;
10
+ changeAdminPin: (tssId: string, adminPuk: string, newAdminPin: string) => unknown;
11
+ authenticateAdmin: (tssId: string, adminPin: string) => unknown;
12
+ logoutAdmin: (tssId: string) => unknown;
13
+ upsertClient: (tssId: string, clientId: string, serialNumber: string) => boolean;
14
+ retrieveClient: (tssId: string, clientId: string) => boolean;
15
+ startTransaction: (tssId: string, txId: string, clientId: string) => boolean;
16
+ finishTransaction: (tssId: string, txId: string, clientId: string, receipt: FiskalyReceipt, revision?: number) => boolean;
17
+ cancelTransaction: (tssId: string, txId: string, clientId: string, revision?: number) => boolean;
18
+ retrieveTransaction: (tssId: string, txId: string) => boolean
19
+ };
20
+ /**
21
+ * Takes a TSS from nothing (or from wherever an earlier attempt stopped) to
22
+ * INITIALIZED with this till registered as its client. Each step is safe to
23
+ * repeat, so a failed setup is finished by calling this again with the same
24
+ * ids, PIN and the PUK kept from the first answer.
25
+ */
26
+ export declare function provisionTss(api: FiskalySignDe, input: ProvisionInput): Promise<ProvisionResult>;
27
+ /** A random admin PIN of digits (fiskaly asks for at least 6 characters). */
28
+ export declare function newAdminPin(length?: number): string;
29
+ /**
30
+ * A client serial number fiskaly accepts (and DSFinV-K's KASSE_SERIENNR):
31
+ * letters, digits, spaces and ' ( ) + , - . : = ?, at most 70, no "_" or "/".
32
+ */
33
+ export declare function isClientSerialNumber(value: string): boolean;
34
+ /**
35
+ * fiskaly SIGN DE (API v2): a cloud TSE (technische Sicherheitseinrichtung)
36
+ * that signs every till transaction, as Germany's KassenSichV (§ 146a AO)
37
+ * requires of an electronic till. Also `@stacksjs/commerce/fiskaly`.
38
+ *
39
+ * The calls follow fiskaly's public API reference
40
+ * (https://developer.fiskaly.com/api/kassensichv/v2):
41
+ *
42
+ * - `POST /auth` trades an API key and secret for a bearer token (refreshed
43
+ * with its `refresh_token` before it runs out);
44
+ * - a TSS (one TSE) is created with `PUT /tss/{id}` (state CREATED, answering
45
+ * the admin PUK once), personalised (`PATCH` state UNINITIALIZED), given an
46
+ * admin PIN with the PUK (`PATCH /tss/{id}/admin`), and initialised
47
+ * (`PATCH` state INITIALIZED) while the admin is signed in
48
+ * (`POST /tss/{id}/admin/auth`);
49
+ * - a client (one till) is registered with `PUT /tss/{id}/client/{client_id}`;
50
+ * - a transaction is started (`PUT /tss/{id}/tx/{tx_id}?tx_revision=1`, state
51
+ * ACTIVE) and finished (revision 2, state FINISHED, with the receipt's
52
+ * amounts per VAT rate and per payment type), which answers the signature,
53
+ * its counter, the times and the receipt's QR code data;
54
+ * - an error is `{ status_code, error, code, message }`.
55
+ *
56
+ * Ids are UUIDs the caller chooses, so every call is a PUT that can be
57
+ * repeated after a lost answer. `provisionTss` walks a TSS from nothing (or
58
+ * from wherever an earlier attempt stopped) to INITIALIZED with a client.
59
+ *
60
+ * Nothing here is a certification: whether a till is compliant depends on
61
+ * the whole system and its operator. The pure receipt and export rules are
62
+ * in `kassensichv.ts` and `dsfinvk.ts`.
63
+ */
64
+ export declare const FISKALY_SIGN_DE_URL: 'https://kassensichv-middleware.fiskaly.com/api/v2';
65
+ export declare interface FiskalyCredentials {
66
+ apiKey: string
67
+ apiSecret: string
68
+ }
69
+ export declare interface FiskalyOptions extends FiskalyCredentials {
70
+ baseUrl?: string
71
+ fetch?: typeof fetch
72
+ timeoutMs?: number
73
+ }
74
+ export declare interface FiskalyTss {
75
+ _id: string
76
+ state: TssState
77
+ description?: string
78
+ serial_number?: string
79
+ public_key?: string
80
+ certificate?: string
81
+ signature_algorithm?: string
82
+ signature_timestamp_format?: string
83
+ transaction_data_encoding?: string
84
+ admin_puk?: string
85
+ max_number_registered_clients?: number
86
+ number_registered_clients?: number
87
+ metadata?: Record<string, string>
88
+ }
89
+ export declare interface FiskalyClient {
90
+ _id: string
91
+ serial_number: string
92
+ state: 'REGISTERED' | 'DEREGISTERED'
93
+ tss_id: string
94
+ }
95
+ export declare interface FiskalyReceipt {
96
+ receipt_type: FiskalyReceiptType
97
+ amounts_per_vat_rate: Array<{ vat_rate: FiskalyVatRate, amount: string }>
98
+ amounts_per_payment_type: Array<{ payment_type: FiskalyPaymentType, amount: string, currency_code?: string }>
99
+ }
100
+ export declare interface FiskalyTransaction {
101
+ _id: string
102
+ state: 'ACTIVE' | 'FINISHED' | 'CANCELLED'
103
+ number: number
104
+ client_id: string
105
+ client_serial_number: string
106
+ tss_id: string
107
+ tss_serial_number: string
108
+ time_start: number
109
+ time_end?: number
110
+ revision: number
111
+ latest_revision: number
112
+ qr_code_data?: string
113
+ signature?: { value: string, algorithm: string, counter: number | string, public_key: string }
114
+ log?: { operation: string, timestamp: number, timestamp_format: string }
115
+ schema?: { standard_v1?: { receipt?: FiskalyReceipt } }
116
+ }
117
+ export declare interface ProvisionInput {
118
+ tssId: string
119
+ clientId: string
120
+ clientSerialNumber: string
121
+ adminPin: string
122
+ adminPuk?: string | null
123
+ metadata?: Record<string, string>
124
+ }
125
+ export declare interface ProvisionResult {
126
+ tss: FiskalyTss
127
+ client: FiskalyClient
128
+ adminPuk: string | null
129
+ }
130
+ export type FiskalyFailure = 'rejected' | 'not_found' | 'conflict' | 'invalid' | 'rate_limited' | 'unreachable' | 'failed';
131
+ export type TssState = 'CREATED' | 'UNINITIALIZED' | 'INITIALIZED' | 'DISABLED' | 'DEFECTIVE' | 'DELETED' | 'EVICTED';
132
+ export type FiskalyVatRate = 'NORMAL' | 'REDUCED_1' | 'SPECIAL_RATE_1' | 'SPECIAL_RATE_2' | 'NULL';
133
+ export type FiskalyPaymentType = 'CASH' | 'NON_CASH';
134
+ export type FiskalyReceiptType = 'RECEIPT' | 'TRAINING' | 'TRANSFER' | 'ORDER' | 'CANCELLATION' | 'ABORT' | 'BENEFIT_IN_KIND' | 'INVOICE' | 'OTHER';
135
+ export type FiskalySignDe = ReturnType<typeof fiskalySignDe>;
136
+ export declare class FiskalyError extends Error {
137
+ readonly failure: FiskalyFailure;
138
+ readonly status: number;
139
+ readonly code: string | null;
140
+ constructor(message: string, failure: FiskalyFailure, status: number, code?: string | null);
141
+ get outage(): boolean;
142
+ }
@@ -0,0 +1 @@
1
+ export const FISKALY_SIGN_DE_URL="https://kassensichv-middleware.fiskaly.com/api/v2";export class FiskalyError extends Error{failure;status;code;constructor(message,failure,status,code=null){super(message);this.failure=failure;this.status=status;this.code=code;this.name="FiskalyError"}get outage(){return this.failure==="unreachable"||this.failure==="rate_limited"||this.failure==="failed"&&(this.status===0||this.status>=500)}}function failureFor(status){if(status===401||status===403)return"rejected";if(status===404)return"not_found";if(status===409)return"conflict";if(status===400||status===422)return"invalid";if(status===429)return"rate_limited";return"failed"}export function fiskalySignDe(options){const base=(options.baseUrl||FISKALY_SIGN_DE_URL).replace(/\/$/,""),doFetch=options.fetch??fetch,timeoutMs=options.timeoutMs??15000;let token=null;async function send(method,path,body,bearer,timeout=timeoutMs){let res;try{res=await doFetch(`${base}${path}`,{method,headers:{Accept:"application/json",...body===void 0?{}:{"Content-Type":"application/json"},...bearer?{Authorization:`Bearer ${bearer}`}:{}},body:body===void 0?void 0:JSON.stringify(body),signal:AbortSignal.timeout(timeout)})}catch(error){throw new FiskalyError(`Could not reach fiskaly: ${error instanceof Error?error.message:String(error)}`,"unreachable",0)}const text=await res.text();let json=null;try{json=text?JSON.parse(text):null}catch{json=null}if(!res.ok){const message=typeof json?.message==="string"&&json.message?json.message:`fiskaly answered ${res.status}`;throw new FiskalyError(message,failureFor(res.status),res.status,typeof json?.code==="string"?json.code:null)}return{status:res.status,json}}function keep(json){const expiresIn=Number(json?.access_token_expires_in),expiresAt=Number(json?.access_token_expires_at);token={accessToken:String(json?.access_token||""),refreshToken:typeof json?.refresh_token==="string"?json.refresh_token:null,expiresAt:Number.isFinite(expiresAt)&&expiresAt>0?expiresAt*1000:Date.now()+(Number.isFinite(expiresIn)&&expiresIn>0?expiresIn:300)*1000};if(!token.accessToken)throw new FiskalyError("fiskaly answered no access token","failed",200);return token}async function bearer(){if(token&&token.expiresAt-60000>Date.now())return token.accessToken;if(token?.refreshToken)try{return keep((await send("POST","/auth",{refresh_token:token.refreshToken},null)).json).accessToken}catch(error){if(!(error instanceof FiskalyError)||error.failure!=="rejected")throw error}return keep((await send("POST","/auth",{api_key:options.apiKey,api_secret:options.apiSecret},null)).json).accessToken}async function call(method,path,body,timeout){try{return(await send(method,path,body,await bearer(),timeout)).json}catch(error){if(error instanceof FiskalyError&&error.status===401&&token){token=null;return(await send(method,path,body,await bearer(),timeout)).json}throw error}}const id=(value)=>encodeURIComponent(value);return{async authenticate(){token=null;const json=(await send("POST","/auth",{api_key:options.apiKey,api_secret:options.apiSecret},null)).json;keep(json);const claims=json?.access_token_claims??{};return{organizationId:typeof claims.organization_id==="string"?claims.organization_id:null,environment:typeof claims.env==="string"?claims.env:null}},createTss:(tssId,metadata)=>call("PUT",`/tss/${id(tssId)}`,metadata?{metadata}:{}),retrieveTss:(tssId)=>call("GET",`/tss/${id(tssId)}`),setTssState:(tssId,state)=>call("PATCH",`/tss/${id(tssId)}`,{state},state==="UNINITIALIZED"?Math.max(timeoutMs,60000):void 0),changeAdminPin:(tssId,adminPuk,newAdminPin)=>call("PATCH",`/tss/${id(tssId)}/admin`,{admin_puk:adminPuk,new_admin_pin:newAdminPin}),authenticateAdmin:(tssId,adminPin)=>call("POST",`/tss/${id(tssId)}/admin/auth`,{admin_pin:adminPin}),logoutAdmin:(tssId)=>call("POST",`/tss/${id(tssId)}/admin/logout`,{}),upsertClient:(tssId,clientId,serialNumber)=>call("PUT",`/tss/${id(tssId)}/client/${id(clientId)}`,{serial_number:serialNumber}),retrieveClient:(tssId,clientId)=>call("GET",`/tss/${id(tssId)}/client/${id(clientId)}`),startTransaction:(tssId,txId,clientId)=>call("PUT",`/tss/${id(tssId)}/tx/${id(txId)}?tx_revision=1`,{state:"ACTIVE",client_id:clientId}),finishTransaction:(tssId,txId,clientId,receipt,revision=2)=>call("PUT",`/tss/${id(tssId)}/tx/${id(txId)}?tx_revision=${revision}`,{state:"FINISHED",client_id:clientId,schema:{standard_v1:{receipt}}}),cancelTransaction:(tssId,txId,clientId,revision=2)=>call("PUT",`/tss/${id(tssId)}/tx/${id(txId)}?tx_revision=${revision}`,{state:"CANCELLED",client_id:clientId}),retrieveTransaction:(tssId,txId)=>call("GET",`/tss/${id(tssId)}/tx/${id(txId)}`)}}export async function provisionTss(api,input){let tss=null,adminPuk=input.adminPuk??null;try{tss=await api.retrieveTss(input.tssId)}catch(error){if(!(error instanceof FiskalyError)||error.failure!=="not_found")throw error}if(!tss){tss=await api.createTss(input.tssId,input.metadata);if(tss.admin_puk)adminPuk=tss.admin_puk}if(tss.state==="DISABLED"||tss.state==="DEFECTIVE"||tss.state==="DELETED"||tss.state==="EVICTED")throw new FiskalyError(`The TSE is ${tss.state.toLowerCase()} and cannot sign`,"invalid",409,"E_TSS_STATE");if(tss.state==="CREATED"){tss=await api.setTssState(input.tssId,"UNINITIALIZED");if(!adminPuk)throw new FiskalyError("The admin PUK of this TSE was not kept, so its PIN cannot be set","invalid",409,"E_ADMIN_PUK_MISSING");await api.changeAdminPin(input.tssId,adminPuk,input.adminPin)}await api.authenticateAdmin(input.tssId,input.adminPin);try{if(tss.state==="UNINITIALIZED")tss=await api.setTssState(input.tssId,"INITIALIZED");const client=await api.upsertClient(input.tssId,input.clientId,input.clientSerialNumber);tss=await api.retrieveTss(input.tssId);return{tss,client,adminPuk}}finally{await api.logoutAdmin(input.tssId).catch(()=>{})}}export function newAdminPin(length=10){if(!Number.isSafeInteger(length)||length<6)throw Error("An admin PIN needs at least 6 characters");let pin="";while(pin.length<length){const bytes=new Uint8Array(length);crypto.getRandomValues(bytes);for(const byte of bytes)if(byte<250&&pin.length<length)pin+=String(byte%10)}return pin}export function isClientSerialNumber(value){return/^(?!\s)[A-Za-z0-9 '()+,\-.:=?]{1,70}(?<!\s)$/.test(value)}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The GDPdU document type definition (version 1.5, 1 September 2004) that
3
+ * every DSFinV-K export's index.xml names (`gdpdu-01-09-2004.dtd`) and ships
4
+ * beside it, as published by the Bundeszentralamt für Steuern with the
5
+ * DSFinV-K 2.4. Kept verbatim (with LF line endings; `dsfinvkExport` writes it with CRLF, as published).
6
+ */
7
+ export declare const GDPDU_DTD_FILE: 'gdpdu-01-09-2004.dtd';
8
+ export declare const GDPDU_DTD: string;
@@ -0,0 +1,276 @@
1
+ export const GDPDU_DTD_FILE="gdpdu-01-09-2004.dtd",GDPDU_DTD=`<?xml version="1.0" encoding="UTF-8"?>
2
+ <!--Versions available:
3
+ 1.1 (August-01-2002)
4
+
5
+ 1.2 (June-01-2003)
6
+ New Element Alias
7
+
8
+ 1.3 (November-01-2003)
9
+ New Element Extension
10
+
11
+ 1.4 (May-01-2004)
12
+ New Element AcceptNoTables
13
+ <Table> is now optional
14
+
15
+ 1.5 (September-01-2004)
16
+ Updated the ForeignKey and Alias elements so they allow
17
+ multiple Alias elements per ForeignKey. This allows one to use alias
18
+ elements for a Composite key.
19
+
20
+ Example:
21
+
22
+ <ForeignKey>
23
+ <Name>Order</Name>
24
+ <Name>Customer</Name>
25
+ <References>Orders</References>
26
+ <Alias>
27
+ <From>Order</From>
28
+ <To>OrderId</To>
29
+ </Alias>
30
+ <Alias>
31
+ <From>Customer</From>
32
+ <To>CustomerId</To>
33
+ </Alias>
34
+ -->
35
+
36
+ <!-- Start Simple Types -->
37
+
38
+ <!-- Supplementary Vocabulary -->
39
+ <!ELEMENT Version (#PCDATA)>
40
+ <!ELEMENT Location (#PCDATA)>
41
+ <!ELEMENT Comment (#PCDATA)>
42
+ <!ELEMENT Length (#PCDATA)>
43
+ <!ELEMENT References (#PCDATA)>
44
+ <!ELEMENT From (#PCDATA)>
45
+ <!ELEMENT To (#PCDATA)>
46
+
47
+ <!-- Sometimes, it is desirable to have a <Media> with
48
+ no tables.
49
+
50
+ Example:
51
+ An extension might provide metadata
52
+ that describes all tax-relevant data.
53
+
54
+ This option is turned off by default. -->
55
+ <!ELEMENT AcceptNoTables (#PCDATA)>
56
+
57
+ <!-- Use Alias to reference columns with different names in
58
+ a ForeignKey element. These Alias elements are optional.
59
+
60
+ The following rules apply to the Alias element:
61
+ - One Alias can be used per ForeignKey.
62
+ - Alias elements can appear in any order.
63
+
64
+ Example:
65
+ Table Orders has a primary key OrderId
66
+ Table Accounts has a foreign key Order.
67
+
68
+ You can use the Alias element to specify Order references OrderId.
69
+
70
+ <ForeignKey>
71
+ <Name>Order</Name>
72
+ <Name>Customer</Name>
73
+ <References>Orders</References>
74
+ <Alias>
75
+ <From>Order</From>
76
+ <To>OrderId</To>
77
+ </Alias>
78
+ </ForeignKey> -->
79
+ <!ELEMENT Alias (From, To)>
80
+
81
+ <!-- Specifying a maximum length for a VariableLength column can
82
+ reduce a VariableLength tables' import time. If MaxLength
83
+ is not specified then we parse URL to determine the MaxLength
84
+ for each column.
85
+
86
+ * Only applies to VariableLength tables. -->
87
+ <!ELEMENT MaxLength (#PCDATA)>
88
+
89
+ <!-- Specifies which character (if any) encapsulates a
90
+ VariableLength AlphaNumeric column.
91
+
92
+ Doublequote is the default TextEncapsulator "
93
+
94
+ * Only applies to VariableLength tables. (Optional) -->
95
+ <!ELEMENT TextEncapsulator (#PCDATA)>
96
+
97
+ <!-- Specifies how many digits appear to the right of the decimal symbol.
98
+
99
+ CAUTION: Results are undefined when importing numeric data with
100
+ greater Accuracy than the Accuracy defined in index.xml
101
+
102
+ For example trying to import the value 1000,25 with an
103
+ accuracy of 0 might result in 1000 or an error. This
104
+ behavior is specific to the implementation.
105
+
106
+ Zero is the default Accuracy '0' (Optional)
107
+ -->
108
+ <!ELEMENT Accuracy (#PCDATA)>
109
+
110
+ <!-- The decimal place is not always stored with numbers. If each number
111
+ is supposed to have decimal places use ImpliedAccuracy -->
112
+ <!ELEMENT ImpliedAccuracy (#PCDATA)>
113
+
114
+ <!-- Enables you to change how GDPdU displays dates.
115
+ DD.MM.YYYY is the default Format -->
116
+ <!ELEMENT Format (#PCDATA)>
117
+
118
+ <!-- Specifies the symbol that indicates decimal values.
119
+ Comma is the default DecimalSymbol. ','
120
+ Specified once per Table. -->
121
+ <!ELEMENT DecimalSymbol (#PCDATA)>
122
+
123
+ <!-- Specifies the symbol that groups the digits in large numbers.
124
+ Dot is the default DigitGroupingSymbol or ThousandsSeperator. '.'
125
+ Specified once per Table -->
126
+ <!ELEMENT DigitGroupingSymbol (#PCDATA)>
127
+
128
+ <!-- Command(s) are executed in the following manner
129
+ * before the import process
130
+ * after the import process
131
+ * before a Media is imported
132
+ * after a Media is imported
133
+ -->
134
+ <!ELEMENT Command (#PCDATA)>
135
+
136
+ <!-- Only the file protocol is supported at this time.
137
+
138
+ * The standard uses relative URLs.
139
+
140
+ Absolute URLs are not allowed. The following are all invalid:
141
+ * http://www.somewhere.com/data/Accounts.dat
142
+ * ftp://ftp.somewhere.com/data/Accounts.dat
143
+ * file://localhost/Accounts.dat
144
+ * file:///Accounts.dat
145
+
146
+ The following are valid examples
147
+ * Accounts.dat
148
+ * data/Accounts.dat
149
+ * data/january/Accounts.dat
150
+ * ../Accounts.dat
151
+ -->
152
+ <!ELEMENT URL (#PCDATA)>
153
+
154
+ <!-- Textual description of specified element (Optional) -->
155
+ <!ELEMENT Description (#PCDATA)>
156
+
157
+ <!-- The logical name of specified element.
158
+ Sometimes referred to business name.
159
+
160
+ If missing, URL will be used in place of Name. -->
161
+ <!ELEMENT Name (#PCDATA)>
162
+
163
+ <!-- Y2K Window Any year before Epoch is 2000+
164
+ Default value 30. -->
165
+ <!ELEMENT Epoch (#PCDATA)>
166
+
167
+ <!-- Element(s) that separate columns or records.
168
+ Semicolon is the default ColumnDelimiter. ';'
169
+ CRLF or &#13;&#10; is the default RecordDelimiter. -->
170
+ <!ELEMENT ColumnDelimiter (#PCDATA)>
171
+ <!ELEMENT RecordDelimiter (#PCDATA)>
172
+
173
+ <!-- The number of bytes skipped before reading of URL commences.
174
+ Zero is the default when not specified. '0'
175
+ -->
176
+ <!ELEMENT SkipNumBytes (#PCDATA)>
177
+
178
+ <!-- End Simple Types -->
179
+ <!-- Start Complex Types -->
180
+
181
+ <!-- Use Extension when you want to add application
182
+ specific functionality to the existing standard.
183
+
184
+ Name - the extension name or identifier.
185
+ URL - the supplementary .xml file that corresponds to the
186
+ extension.
187
+
188
+ An application that extends the standard should scan the
189
+ Dataset element for the presence of zero or more Extension
190
+ elements. The application can use the Name element to identify
191
+ the extension.
192
+
193
+ When choosing a name for your extension, do not choose a common
194
+ name. This will reduce undefined results for name conflicts.
195
+
196
+ It is possible that future extensions will be ratified as
197
+ mandatory to meet GDPdU guidelines. -->
198
+ <!ELEMENT Extension (Name, URL)>
199
+
200
+ <!-- Self-explanatory -->
201
+ <!ELEMENT Range (From, (To | Length)?)>
202
+ <!ELEMENT FixedRange (From, (To | Length))>
203
+
204
+ <!-- The document element -->
205
+ <!ELEMENT DataSet (Extension*, Version, DataSupplier?, Command*, Media+, Command*)>
206
+
207
+ <!-- Supported datatypes (mandatory) -->
208
+ <!ELEMENT AlphaNumeric EMPTY>
209
+ <!ELEMENT Date (Format?)>
210
+ <!ELEMENT Numeric ((ImpliedAccuracy | Accuracy)?)>
211
+
212
+ <!-- Supported codepages:
213
+ Be careful to explicitly define RecordDelimiter when using
214
+ a non-default codepage.
215
+
216
+ ANSI is the default codepage when not specified -->
217
+ <!ELEMENT ANSI EMPTY>
218
+ <!ELEMENT Macintosh EMPTY>
219
+ <!ELEMENT OEM EMPTY>
220
+ <!ELEMENT UTF16 EMPTY>
221
+ <!ELEMENT UTF7 EMPTY>
222
+ <!ELEMENT UTF8 EMPTY>
223
+
224
+ <!-- Supported file formats:
225
+ FixedLength
226
+ VariableLength -->
227
+ <!ELEMENT FixedLength ((Length | RecordDelimiter)?, ((FixedPrimaryKey+, FixedColumn*) | (FixedColumn+)), ForeignKey*)>
228
+ <!ELEMENT FixedColumn (Name, Description?, (Numeric | AlphaNumeric | Date), Map*, FixedRange)>
229
+ <!ELEMENT FixedPrimaryKey (Name, Description?, (Numeric | AlphaNumeric | Date), Map*, FixedRange)>
230
+ <!ELEMENT VariableLength (ColumnDelimiter?, RecordDelimiter?, TextEncapsulator?, ((VariablePrimaryKey+, VariableColumn*) | (VariableColumn+)), ForeignKey*)>
231
+ <!ELEMENT VariableColumn (Name, Description?, (Numeric | (AlphaNumeric, MaxLength?) | Date), Map*)>
232
+ <!ELEMENT VariablePrimaryKey (Name, Description?, (Numeric | (AlphaNumeric, MaxLength?) | Date), Map*)>
233
+
234
+ <!-- Description of the entity supplying the data. (Optional) -->
235
+ <!ELEMENT DataSupplier (Name, Location, Comment)>
236
+
237
+ <!-- The first Media will contain index.xml. Importing will process each media listed -->
238
+ <!ELEMENT Media (Name, Command*, Table*, Command*, AcceptNoTables?)>
239
+
240
+ <!-- Elements common to FixedLength & VariableLength are propagated to Table. -->
241
+ <!ELEMENT Table (URL, Name?, Description?, Validity?, (ANSI | Macintosh | OEM | UTF16 | UTF7 | UTF8)?, (DecimalSymbol, DigitGroupingSymbol)?, SkipNumBytes?, Range?, Epoch?, (VariableLength | FixedLength))>
242
+
243
+ <!-- ForeignKeys denote joins or relationships between tables.
244
+ To successfully join two tables make sure both the PrimaryKey
245
+ and the referenced column (ForeignKey) are of the same datatype.
246
+ Results are undefined when joining two tables with different
247
+ key datatypes. Most likely an error will occur.
248
+
249
+ see line 35 for information about Alias -->
250
+ <!ELEMENT ForeignKey (Name+, References, Alias*)>
251
+
252
+ <!-- Maps AlphaNumeric columns from 'From' to 'To'
253
+ ie. From To
254
+ ============ =============
255
+ True 1
256
+ True -1
257
+ False 0
258
+
259
+ Basically, a map is an associative container.
260
+
261
+ The standard implementation only supports
262
+ AlphaNumeric datatypes. The following
263
+ conversions are NOT supported.
264
+
265
+ Numeric -> AlphaNumeric
266
+ Date -> AlphaNumeric
267
+ AplhaNumeric -> Date
268
+ AlphaNumeric -> Numeric
269
+ -->
270
+ <!ELEMENT Map (Description?, From, To)>
271
+
272
+ <!-- Documentation for table validity. -->
273
+ <!ELEMENT Validity (Range, Format?)>
274
+
275
+ <!-- End Complex Types -->
276
+ `;
@@ -0,0 +1,99 @@
1
+ export declare function isDeVatKey(value: unknown): value is DeVatKey;
2
+ /** The VAT inside a gross amount at a rate (percent), rounded half away from zero, and the net rest. */
3
+ export declare function splitGross(grossCents: number, ratePercent: number): { net: number, vat: number };
4
+ /**
5
+ * An amount as the TSE and the QR code write it: "." as the decimal point,
6
+ * exactly two decimals, a "-" only when negative, no grouping.
7
+ */
8
+ export declare function tseAmount(cents: number): string;
9
+ /**
10
+ * The `processData` of a `Kassenbeleg-V1` transaction: the receipt type, the
11
+ * five gross VAT amounts and the payments, as
12
+ * `Beleg^75.33_7.99_0.00_0.00_0.00^10.00:Bar_5.00:Bar:CHF_64.30:Unbar`.
13
+ * Payments of one kind and currency are added up; zero ones are left out;
14
+ * cash comes first, euros before other currencies (alphabetical).
15
+ */
16
+ export declare function processData(input: { bonTyp?: BonTyp, vat: readonly TseVatAmount[], payments: readonly TsePayment[] }): string;
17
+ /** fiskaly's receipt schema (`schema.standard_v1.receipt`) for the same amounts. */
18
+ export declare function fiskalyReceiptFor(input: { vat: readonly TseVatAmount[], payments: readonly TsePayment[], receiptType?: 'RECEIPT' | 'TRAINING' | 'TRANSFER' | 'ORDER' | 'CANCELLATION' | 'ABORT' | 'BENEFIT_IN_KIND' | 'INVOICE' | 'OTHER' }): {
19
+ receipt_type: unknown;
20
+ amounts_per_vat_rate: unknown;
21
+ amounts_per_payment_type: unknown
22
+ };
23
+ /** A TSE time as receipts and the export write it: `YYYY-MM-DDThh:mm:ss.fffZ` (UTC). Takes unix seconds, an ISO string or a Date. */
24
+ export declare function tseTime(value: number | string | Date): string;
25
+ /** The receipt's QR code content (DSFinV-K Anhang I): twelve fields joined by ";", starting with "V0". */
26
+ export declare function receiptQrCode(input: ReceiptQrInput): string;
27
+ /** The parts of a receipt QR code, or null when it is not one. */
28
+ export declare function parseReceiptQrCode(value: string): ReceiptQrInput | null;
29
+ /** The VAT lines a receipt prints: per key, the gross, net and VAT, and the rate. */
30
+ export declare function receiptVatLines(vat: readonly TseVatAmount[]): Array<{ key: DeVatKey, rate: number, description: string, grossCents: number, netCents: number, vatCents: number }>;
31
+ /**
32
+ * What a German till signs and prints (KassenSichV, DSFinV-K 2.4 Anhang I):
33
+ * the VAT keys, the `processData` a receipt's TSE transaction carries, the
34
+ * receipt's QR code, and the amounts a cloud TSE such as fiskaly is sent.
35
+ * Pure and dependency-free (`@stacksjs/commerce/kassensichv`), so a till in
36
+ * the browser can draw the same receipt the server signed.
37
+ *
38
+ * Amounts are integers in cents (euro cents for a euro till). VAT is
39
+ * included in gross prices, as German consumer prices are.
40
+ */
41
+ /**
42
+ * DSFinV-K's VAT keys (UST_SCHLUESSEL, Anlage 2, Stand 05.12.2024) that a
43
+ * till selling goods and services uses: the general and reduced rates, and
44
+ * the three kinds of 0%. `bucket` is the key's place among the five VAT
45
+ * amounts of `processData` (general, reduced, § 24 Nr. 3, § 24 Nr. 1, 0%).
46
+ */
47
+ export declare const DE_VAT_KEYS: {
48
+ 1: {
49
+ rate: 19;
50
+ bucket: 0;
51
+ fiskaly: 'NORMAL';
52
+ description: 'Regelsteuersatz'
53
+ };
54
+ 2: {
55
+ rate: 7;
56
+ bucket: 1;
57
+ fiskaly: 'REDUCED_1';
58
+ description: 'Ermäßigter Steuersatz'
59
+ };
60
+ 5: {
61
+ rate: 0;
62
+ bucket: 4;
63
+ fiskaly: 'NULL';
64
+ description: 'Nicht Steuerbar'
65
+ };
66
+ 6: {
67
+ rate: 0;
68
+ bucket: 4;
69
+ fiskaly: 'NULL';
70
+ description: 'Umsatzsteuerfrei'
71
+ };
72
+ 7: {
73
+ rate: 0;
74
+ bucket: 4;
75
+ fiskaly: 'NULL';
76
+ description: 'UmsatzsteuerNichtErmittelbar'
77
+ }
78
+ };
79
+ /** What a receipt must say when the TSE failed (AEAO zu § 146a, Nr. 1.14.2): the failure, unmistakably. */
80
+ export declare const TSE_FAILURE_NOTICE: 'TSE ausgefallen';
81
+ export declare interface TseVatAmount { key: DeVatKey, grossCents: number }
82
+ export declare interface TsePayment { kind: TsePaymentKind, cents: number, currency?: string }
83
+ export declare interface ReceiptQrInput {
84
+ clientSerialNumber: string
85
+ processType: 'Kassenbeleg-V1' | 'Bestellung-V1' | 'SonstigerVorgang'
86
+ processData: string
87
+ transactionNumber: number
88
+ signatureCounter: number
89
+ start: number | string | Date
90
+ end: number | string | Date
91
+ signatureAlgorithm: string
92
+ logTimeFormat: string
93
+ signature: string
94
+ publicKey: string
95
+ }
96
+ export type DeVatKey = keyof typeof DE_VAT_KEYS;
97
+ /** How a payment counts for the TSE: cash, or anything else. */
98
+ export type TsePaymentKind = 'Bar' | 'Unbar';
99
+ export type BonTyp = 'Beleg' | 'AVTransfer' | 'AVBestellung' | 'AVTraining' | 'AVBelegstorno' | 'AVBelegabbruch' | 'AVSachbezug' | 'AVRechnung' | 'AVSonstige';
@@ -0,0 +1 @@
1
+ export const DE_VAT_KEYS={1:{rate:19,bucket:0,fiskaly:"NORMAL",description:"Regelsteuersatz"},2:{rate:7,bucket:1,fiskaly:"REDUCED_1",description:"Erm\xE4\xDFigter Steuersatz"},5:{rate:0,bucket:4,fiskaly:"NULL",description:"Nicht Steuerbar"},6:{rate:0,bucket:4,fiskaly:"NULL",description:"Umsatzsteuerfrei"},7:{rate:0,bucket:4,fiskaly:"NULL",description:"UmsatzsteuerNichtErmittelbar"}};export function isDeVatKey(value){return typeof value==="number"&&Object.prototype.hasOwnProperty.call(DE_VAT_KEYS,value)}export function splitGross(grossCents,ratePercent){if(!Number.isSafeInteger(grossCents))throw Error("A gross amount must be whole cents");if(!ratePercent)return{net:grossCents,vat:0};const exact=Math.abs(grossCents)*ratePercent/(100+ratePercent),vat=Math.sign(grossCents)*Math.round(exact);return{net:grossCents-vat,vat}}export function tseAmount(cents){if(!Number.isSafeInteger(cents))throw Error("An amount must be whole cents");const sign=cents<0?"-":"",abs=Math.abs(cents);return`${sign}${Math.floor(abs/100)}.${String(abs%100).padStart(2,"0")}`}export function processData(input){const buckets=[0,0,0,0,0];for(const amount of input.vat){if(!isDeVatKey(amount.key))throw Error(`Unknown VAT key ${String(amount.key)}`);buckets[DE_VAT_KEYS[amount.key].bucket]+=amount.grossCents}const sums=new Map;for(const payment of input.payments){const currency=(payment.currency||"EUR").toUpperCase(),key=`${payment.kind}:${currency}`,entry=sums.get(key)??{kind:payment.kind,currency,cents:0};entry.cents+=payment.cents;sums.set(key,entry)}const order=(entry)=>`${entry.kind==="Bar"?0:1}:${entry.currency==="EUR"?"":entry.currency}`,payments=[...sums.values()].filter((entry)=>entry.cents!==0).sort((a,b)=>order(a).localeCompare(order(b))).map((entry)=>`${tseAmount(entry.cents)}:${entry.kind}${entry.currency==="EUR"?"":`:${entry.currency}`}`);return`${input.bonTyp??"Beleg"}^${buckets.map(tseAmount).join("_")}^${payments.join("_")}`}export function fiskalyReceiptFor(input){const byRate=new Map;for(const amount of input.vat){if(!isDeVatKey(amount.key))throw Error(`Unknown VAT key ${String(amount.key)}`);const rate=DE_VAT_KEYS[amount.key].fiskaly;byRate.set(rate,(byRate.get(rate)??0)+amount.grossCents)}const byPayment=new Map;for(const payment of input.payments){const type=payment.kind==="Bar"?"CASH":"NON_CASH",currency=(payment.currency||"EUR").toUpperCase(),entry=byPayment.get(`${type}:${currency}`)??{payment_type:type,currency,cents:0};entry.cents+=payment.cents;byPayment.set(`${type}:${currency}`,entry)}return{receipt_type:input.receiptType??"RECEIPT",amounts_per_vat_rate:[...byRate.entries()].filter(([,cents])=>cents!==0).map(([vat_rate,cents])=>({vat_rate,amount:tseAmount(cents)})),amounts_per_payment_type:[...byPayment.values()].filter((entry)=>entry.cents!==0).map((entry)=>({payment_type:entry.payment_type,amount:tseAmount(entry.cents),...entry.currency==="EUR"?{}:{currency_code:entry.currency}}))}}export function tseTime(value){const date=typeof value==="number"?new Date(value*1000):new Date(value);if(Number.isNaN(date.getTime()))throw Error("Not a time");return date.toISOString()}export function receiptQrCode(input){return["V0",input.clientSerialNumber,input.processType,input.processData,String(input.transactionNumber),String(input.signatureCounter),tseTime(input.start),tseTime(input.end),input.signatureAlgorithm,input.logTimeFormat,input.signature,input.publicKey].join(";")}export function parseReceiptQrCode(value){const parts=value.split(";");if(parts.length!==12||parts[0]!=="V0")return null;const number=Number(parts[4]),counter=Number(parts[5]);if(!Number.isSafeInteger(number)||!Number.isSafeInteger(counter))return null;return{clientSerialNumber:parts[1],processType:parts[2],processData:parts[3],transactionNumber:number,signatureCounter:counter,start:parts[6],end:parts[7],signatureAlgorithm:parts[8],logTimeFormat:parts[9],signature:parts[10],publicKey:parts[11]}}export function receiptVatLines(vat){const byKey=new Map;for(const amount of vat)byKey.set(amount.key,(byKey.get(amount.key)??0)+amount.grossCents);return[...byKey.entries()].sort(([a],[b])=>a-b).map(([key,grossCents])=>{const info=DE_VAT_KEYS[key],split=splitGross(grossCents,info.rate);return{key,rate:info.rate,description:info.description,grossCents,netCents:split.net,vatCents:split.vat}})}export const TSE_FAILURE_NOTICE="TSE ausgefallen";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/commerce",
3
3
  "type": "module",
4
- "version": "0.75.169",
4
+ "version": "0.75.171",
5
5
  "description": "Stacks commerce utilities.",
6
6
  "author": "Chris Breuer",
7
7
  "contributors": [
@@ -57,18 +57,18 @@
57
57
  "prepublishOnly": "bun run build"
58
58
  },
59
59
  "dependencies": {
60
- "@stacksjs/cache": "0.75.169",
61
- "@stacksjs/config": "0.75.169",
62
- "@stacksjs/database": "0.75.169",
63
- "@stacksjs/env": "0.75.169",
64
- "@stacksjs/error-handling": "0.75.169",
65
- "@stacksjs/events": "0.75.169",
66
- "@stacksjs/logging": "0.75.169",
67
- "@stacksjs/orm": "0.75.169",
68
- "@stacksjs/payments": "0.75.169",
69
- "@stacksjs/realtime": "0.75.169",
70
- "@stacksjs/strings": "0.75.169",
71
- "@stacksjs/validation": "0.75.169",
60
+ "@stacksjs/cache": "0.75.171",
61
+ "@stacksjs/config": "0.75.171",
62
+ "@stacksjs/database": "0.75.171",
63
+ "@stacksjs/env": "0.75.171",
64
+ "@stacksjs/error-handling": "0.75.171",
65
+ "@stacksjs/events": "0.75.171",
66
+ "@stacksjs/logging": "0.75.171",
67
+ "@stacksjs/orm": "0.75.171",
68
+ "@stacksjs/payments": "0.75.171",
69
+ "@stacksjs/realtime": "0.75.171",
70
+ "@stacksjs/strings": "0.75.171",
71
+ "@stacksjs/validation": "0.75.171",
72
72
  "ts-spreadsheets": "^0.2.0"
73
73
  },
74
74
  "devDependencies": {