@fin.cx/skr 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.smartconfig.json +33 -6
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/core.accountpolicy.d.ts +67 -0
- package/dist_ts/core/core.accountpolicy.js +91 -0
- package/dist_ts/core/core.bukeys.d.ts +38 -0
- package/dist_ts/core/core.bukeys.js +117 -0
- package/dist_ts/core/core.datev.captions.d.ts +7 -0
- package/dist_ts/core/core.datev.captions.js +134 -0
- package/dist_ts/core/core.datev.d.ts +47 -0
- package/dist_ts/core/core.datev.js +129 -0
- package/dist_ts/core/core.hash.d.ts +27 -0
- package/dist_ts/core/core.hash.js +72 -0
- package/dist_ts/core/core.plugins.d.ts +3 -0
- package/dist_ts/core/core.plugins.js +9 -0
- package/dist_ts/core/core.recipes.d.ts +118 -0
- package/dist_ts/core/core.recipes.js +504 -0
- package/dist_ts/core/core.taxscenario.d.ts +27 -0
- package/dist_ts/core/core.taxscenario.js +85 -0
- package/dist_ts/core/core.types.d.ts +48 -0
- package/dist_ts/core/core.types.js +2 -0
- package/dist_ts/core/core.validate.d.ts +17 -0
- package/dist_ts/core/core.validate.js +89 -0
- package/dist_ts/core/index.d.ts +15 -0
- package/dist_ts/core/index.js +16 -0
- package/dist_ts/index.d.ts +15 -0
- package/dist_ts/index.js +10 -1
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +4 -2
- package/dist_ts/skr.api.d.ts +91 -22
- package/dist_ts/skr.api.js +357 -42
- package/dist_ts/skr.balances.d.ts +28 -0
- package/dist_ts/skr.balances.js +96 -0
- package/dist_ts/skr.classes.journalentry.d.ts +19 -3
- package/dist_ts/skr.classes.journalentry.js +67 -4
- package/dist_ts/skr.classes.reports.js +49 -102
- package/dist_ts/skr.database.d.ts +2 -0
- package/dist_ts/skr.database.js +1 -1
- package/dist_ts/skr.errors.d.ts +18 -0
- package/dist_ts/skr.errors.js +34 -0
- package/dist_ts/skr.export.js +4 -7
- package/dist_ts/skr.invoice.adapter.d.ts +15 -1
- package/dist_ts/skr.invoice.adapter.js +253 -41
- package/dist_ts/skr.invoice.booking.js +14 -28
- package/dist_ts/skr.migrate.d.ts +20 -0
- package/dist_ts/skr.migrate.js +223 -0
- package/dist_ts/skr.period.d.ts +47 -0
- package/dist_ts/skr.period.js +95 -0
- package/dist_ts/skr.posting.d.ts +74 -0
- package/dist_ts/skr.posting.js +209 -0
- package/dist_ts/skr.security.d.ts +6 -9
- package/dist_ts/skr.security.js +16 -89
- package/dist_ts/skr.sequence.d.ts +32 -0
- package/dist_ts/skr.sequence.js +51 -0
- package/dist_ts/skr.txview.d.ts +46 -0
- package/dist_ts/skr.txview.js +235 -0
- package/dist_ts/skr.types.d.ts +35 -1
- package/dist_ts/skr.verify.d.ts +13 -0
- package/dist_ts/skr.verify.js +84 -0
- package/package.json +21 -24
- package/readme.hints.md +37 -0
- package/readme.md +94 -1
- package/readme.plan.md +180 -243
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/core.accountpolicy.ts +154 -0
- package/ts/core/core.bukeys.ts +146 -0
- package/ts/core/core.datev.captions.ts +133 -0
- package/ts/core/core.datev.ts +174 -0
- package/ts/core/core.hash.ts +76 -0
- package/ts/core/core.plugins.ts +9 -0
- package/ts/core/core.recipes.ts +659 -0
- package/ts/core/core.taxscenario.ts +156 -0
- package/ts/core/core.types.ts +74 -0
- package/ts/core/core.validate.ts +119 -0
- package/ts/core/index.ts +15 -0
- package/ts/index.ts +31 -0
- package/ts/plugins.ts +4 -0
- package/ts/skr.api.ts +432 -46
- package/ts/skr.balances.ts +127 -0
- package/ts/skr.classes.journalentry.ts +54 -5
- package/ts/skr.classes.reports.ts +54 -126
- package/ts/skr.database.ts +3 -0
- package/ts/skr.errors.ts +44 -0
- package/ts/skr.export.ts +3 -6
- package/ts/skr.invoice.adapter.ts +291 -45
- package/ts/skr.invoice.booking.ts +22 -34
- package/ts/skr.migrate.ts +268 -0
- package/ts/skr.period.ts +139 -0
- package/ts/skr.posting.ts +273 -0
- package/ts/skr.security.ts +18 -93
- package/ts/skr.sequence.ts +89 -0
- package/ts/skr.txview.ts +268 -0
- package/ts/skr.types.ts +37 -1
- package/ts/skr.verify.ts +99 -0
- /package/{license.md → license} +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridges between the legacy v1 shapes (float euros, numeric posting keys,
|
|
3
|
+
* debit/credit account pairs) and the v2 ledger (cents, string BU keys,
|
|
4
|
+
* per-line Gegenkonto):
|
|
5
|
+
*
|
|
6
|
+
* - input converters: ITransactionData / IJournalEntry -> core.IJournalDraft
|
|
7
|
+
* - output projections: v2 JournalEntry (lines) -> transaction-shaped views
|
|
8
|
+
*
|
|
9
|
+
* The Transaction collection is FROZEN in v2 — these projections are what
|
|
10
|
+
* keeps transaction-style reads working.
|
|
11
|
+
*/
|
|
12
|
+
import * as plugins from './plugins.js';
|
|
13
|
+
import * as core from './core/index.js';
|
|
14
|
+
import { getDbSync } from './skr.database.js';
|
|
15
|
+
import { JOURNAL_ENTRY_COLLECTION } from './skr.period.js';
|
|
16
|
+
const { centsFromFloat } = plugins.calculation;
|
|
17
|
+
/** v1 numeric posting keys that map 1:1 onto the v2 string BU keys */
|
|
18
|
+
const NUMERIC_KEY_MAP = {
|
|
19
|
+
1: '1',
|
|
20
|
+
2: '2',
|
|
21
|
+
3: '3',
|
|
22
|
+
8: '8',
|
|
23
|
+
9: '9',
|
|
24
|
+
18: '18',
|
|
25
|
+
19: '19',
|
|
26
|
+
40: '40',
|
|
27
|
+
91: '91',
|
|
28
|
+
94: '94',
|
|
29
|
+
};
|
|
30
|
+
export function mapNumericPostingKey(postingKey) {
|
|
31
|
+
if (postingKey === undefined || postingKey === null)
|
|
32
|
+
return '';
|
|
33
|
+
return NUMERIC_KEY_MAP[postingKey] ?? '';
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* v1 postTransaction input -> two-line v2 draft. The amount is NOT split into
|
|
37
|
+
* net/VAT (v1 semantics: vatAmount was informational) so ledger figures stay
|
|
38
|
+
* identical across the migration.
|
|
39
|
+
*/
|
|
40
|
+
export function transactionDataToDraft(data) {
|
|
41
|
+
const amountCents = centsFromFloat(data.amount);
|
|
42
|
+
const vatCents = data.vatAmount ? centsFromFloat(data.vatAmount) : 0;
|
|
43
|
+
const lines = [
|
|
44
|
+
{
|
|
45
|
+
accountNumber: data.debitAccount,
|
|
46
|
+
counterAccount: data.creditAccount,
|
|
47
|
+
side: 'debit',
|
|
48
|
+
amountCents,
|
|
49
|
+
buKey: '',
|
|
50
|
+
buchungstext: data.description,
|
|
51
|
+
belegfeld1: data.reference,
|
|
52
|
+
costCenter1: data.costCenter,
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
accountNumber: data.creditAccount,
|
|
56
|
+
counterAccount: data.debitAccount,
|
|
57
|
+
side: 'credit',
|
|
58
|
+
amountCents,
|
|
59
|
+
buKey: '',
|
|
60
|
+
buchungstext: data.description,
|
|
61
|
+
belegfeld1: data.reference,
|
|
62
|
+
costCenter1: data.costCenter,
|
|
63
|
+
},
|
|
64
|
+
];
|
|
65
|
+
lines[0].vatAmountCents = vatCents;
|
|
66
|
+
return {
|
|
67
|
+
date: data.date,
|
|
68
|
+
description: data.description,
|
|
69
|
+
reference: data.reference,
|
|
70
|
+
skrType: data.skrType,
|
|
71
|
+
currency: 'EUR',
|
|
72
|
+
lines,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* v1 postJournalEntry input -> v2 draft. Gegenkonto derivation: when one side
|
|
77
|
+
* of the entry has exactly one line, it is the counter account for every line
|
|
78
|
+
* of the other side (the dominant v1 usage); otherwise the first line of the
|
|
79
|
+
* opposite side is used (documented heuristic — v1 stored no Gegenkonto).
|
|
80
|
+
*/
|
|
81
|
+
export function journalInputToDraft(entry) {
|
|
82
|
+
const debitLines = entry.lines.filter((line) => (line.debit ?? 0) > 0);
|
|
83
|
+
const creditLines = entry.lines.filter((line) => (line.credit ?? 0) > 0);
|
|
84
|
+
const counterFor = (side) => {
|
|
85
|
+
const opposite = side === 'debit' ? creditLines : debitLines;
|
|
86
|
+
return opposite[0]?.accountNumber ?? '';
|
|
87
|
+
};
|
|
88
|
+
const lines = entry.lines.map((line) => {
|
|
89
|
+
const isDebit = (line.debit ?? 0) > 0;
|
|
90
|
+
return {
|
|
91
|
+
accountNumber: line.accountNumber,
|
|
92
|
+
counterAccount: counterFor(isDebit ? 'debit' : 'credit'),
|
|
93
|
+
side: isDebit ? 'debit' : 'credit',
|
|
94
|
+
amountCents: centsFromFloat(isDebit ? line.debit : line.credit),
|
|
95
|
+
buKey: mapNumericPostingKey(line.postingKey),
|
|
96
|
+
buchungstext: line.description ?? entry.description,
|
|
97
|
+
costCenter1: line.costCenter,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
date: entry.date,
|
|
102
|
+
description: entry.description,
|
|
103
|
+
reference: entry.reference,
|
|
104
|
+
skrType: entry.skrType,
|
|
105
|
+
currency: 'EUR',
|
|
106
|
+
lines,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Project a two-line v2 entry back into the classic transaction shape.
|
|
111
|
+
* Returns null for entries with more than two lines (journal-only view).
|
|
112
|
+
*/
|
|
113
|
+
export function entryToTransactionView(entry) {
|
|
114
|
+
const debitLine = entry.lines.find((line) => line.side === 'debit');
|
|
115
|
+
const creditLine = entry.lines.find((line) => line.side === 'credit');
|
|
116
|
+
if (!debitLine || !creditLine || entry.lines.length !== 2)
|
|
117
|
+
return null;
|
|
118
|
+
return {
|
|
119
|
+
id: entry.id,
|
|
120
|
+
transactionNumber: entry.journalNumber ?? entry.id,
|
|
121
|
+
date: entry.date,
|
|
122
|
+
debitAccount: debitLine.accountNumber,
|
|
123
|
+
creditAccount: creditLine.accountNumber,
|
|
124
|
+
amount: (debitLine.amountCents ?? 0) / 100,
|
|
125
|
+
vatAmount: (debitLine.vatAmountCents ?? 0) / 100,
|
|
126
|
+
description: entry.description,
|
|
127
|
+
reference: entry.reference ?? '',
|
|
128
|
+
skrType: entry.skrType,
|
|
129
|
+
status: entry.status,
|
|
130
|
+
period: entry.period ?? '',
|
|
131
|
+
fiscalYear: entry.fiscalYear ?? entry.date.getUTCFullYear(),
|
|
132
|
+
costCenter: debitLine.costCenter1 ?? creditLine.costCenter1,
|
|
133
|
+
reversalOf: entry.reversalOf,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
function dayRange(params) {
|
|
137
|
+
const from = params?.dateFrom ? new Date(params.dateFrom) : null;
|
|
138
|
+
const to = params?.dateTo ? new Date(params.dateTo) : null;
|
|
139
|
+
if (from)
|
|
140
|
+
from.setHours(0, 0, 0, 0);
|
|
141
|
+
if (to)
|
|
142
|
+
to.setHours(23, 59, 59, 999);
|
|
143
|
+
return { from, to };
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Load v2 entries of a date range as core drafts (for DATEV export).
|
|
147
|
+
*/
|
|
148
|
+
export async function loadDraftsForExport(skrType, dateFrom, dateTo) {
|
|
149
|
+
const docs = await getDbSync()
|
|
150
|
+
.mongoDb.collection(JOURNAL_ENTRY_COLLECTION)
|
|
151
|
+
.find({
|
|
152
|
+
skrType,
|
|
153
|
+
schemaVersion: 2,
|
|
154
|
+
status: { $in: ['posted', 'finalized'] },
|
|
155
|
+
date: { $gte: dateFrom, $lte: dateTo },
|
|
156
|
+
})
|
|
157
|
+
.sort({ sequenceNumber: 1 })
|
|
158
|
+
.toArray();
|
|
159
|
+
return docs.map((doc) => {
|
|
160
|
+
const entry = doc;
|
|
161
|
+
return {
|
|
162
|
+
date: entry.date,
|
|
163
|
+
description: entry.description,
|
|
164
|
+
reference: entry.reference,
|
|
165
|
+
skrType: entry.skrType,
|
|
166
|
+
currency: 'EUR',
|
|
167
|
+
lines: entry.lines.map((line) => ({
|
|
168
|
+
accountNumber: line.accountNumber,
|
|
169
|
+
counterAccount: line.counterAccount ?? '',
|
|
170
|
+
side: (line.side ?? 'debit'),
|
|
171
|
+
amountCents: line.amountCents ?? 0,
|
|
172
|
+
buKey: (line.buKey ?? ''),
|
|
173
|
+
buchungstext: line.buchungstext ?? entry.description,
|
|
174
|
+
belegfeld1: line.belegfeld1,
|
|
175
|
+
costCenter1: line.costCenter1,
|
|
176
|
+
docLink: line.docLink,
|
|
177
|
+
})),
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Per-account activity as transaction-shaped line views from v2 entries —
|
|
183
|
+
* the data source for reports (trial balance, general ledger, cash flow).
|
|
184
|
+
* Each view carries the line's own side and its Gegenkonto.
|
|
185
|
+
*/
|
|
186
|
+
export async function accountLineViews(accountNumber, skrType, params) {
|
|
187
|
+
const { from, to } = dayRange(params);
|
|
188
|
+
const match = {
|
|
189
|
+
skrType,
|
|
190
|
+
schemaVersion: 2,
|
|
191
|
+
status: { $in: ['posted', 'finalized'] },
|
|
192
|
+
'lines.accountNumber': accountNumber,
|
|
193
|
+
};
|
|
194
|
+
if (from || to) {
|
|
195
|
+
const range = {};
|
|
196
|
+
if (from)
|
|
197
|
+
range.$gte = from;
|
|
198
|
+
if (to)
|
|
199
|
+
range.$lte = to;
|
|
200
|
+
match.date = range;
|
|
201
|
+
}
|
|
202
|
+
const docs = await getDbSync()
|
|
203
|
+
.mongoDb.collection(JOURNAL_ENTRY_COLLECTION)
|
|
204
|
+
.find(match)
|
|
205
|
+
.sort({ date: 1, sequenceNumber: 1 })
|
|
206
|
+
.toArray();
|
|
207
|
+
const views = [];
|
|
208
|
+
for (const doc of docs) {
|
|
209
|
+
const entry = doc;
|
|
210
|
+
for (const line of entry.lines) {
|
|
211
|
+
if (line.accountNumber !== accountNumber)
|
|
212
|
+
continue;
|
|
213
|
+
const isDebit = line.side === 'debit';
|
|
214
|
+
views.push({
|
|
215
|
+
id: entry.id,
|
|
216
|
+
transactionNumber: entry.journalNumber ?? entry.id,
|
|
217
|
+
date: entry.date,
|
|
218
|
+
debitAccount: isDebit ? accountNumber : line.counterAccount ?? '',
|
|
219
|
+
creditAccount: isDebit ? line.counterAccount ?? '' : accountNumber,
|
|
220
|
+
amount: (line.amountCents ?? 0) / 100,
|
|
221
|
+
vatAmount: (line.vatAmountCents ?? 0) / 100,
|
|
222
|
+
description: line.buchungstext ?? entry.description,
|
|
223
|
+
reference: entry.reference ?? '',
|
|
224
|
+
skrType: entry.skrType,
|
|
225
|
+
status: entry.status,
|
|
226
|
+
period: entry.period ?? '',
|
|
227
|
+
fiscalYear: entry.fiscalYear ?? entry.date.getUTCFullYear(),
|
|
228
|
+
costCenter: line.costCenter1,
|
|
229
|
+
reversalOf: entry.reversalOf,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return views;
|
|
234
|
+
}
|
|
235
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2tyLnR4dmlldy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3Nrci50eHZpZXcudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7R0FVRztBQUNILE9BQU8sS0FBSyxPQUFPLE1BQU0sY0FBYyxDQUFDO0FBQ3hDLE9BQU8sS0FBSyxJQUFJLE1BQU0saUJBQWlCLENBQUM7QUFDeEMsT0FBTyxFQUFFLFNBQVMsRUFBRSxNQUFNLG1CQUFtQixDQUFDO0FBQzlDLE9BQU8sRUFBRSx3QkFBd0IsRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBVTNELE1BQU0sRUFBRSxjQUFjLEVBQUUsR0FBRyxPQUFPLENBQUMsV0FBVyxDQUFDO0FBRS9DLHNFQUFzRTtBQUN0RSxNQUFNLGVBQWUsR0FBZ0M7SUFDbkQsQ0FBQyxFQUFFLEdBQUc7SUFDTixDQUFDLEVBQUUsR0FBRztJQUNOLENBQUMsRUFBRSxHQUFHO0lBQ04sQ0FBQyxFQUFFLEdBQUc7SUFDTixDQUFDLEVBQUUsR0FBRztJQUNOLEVBQUUsRUFBRSxJQUFJO0lBQ1IsRUFBRSxFQUFFLElBQUk7SUFDUixFQUFFLEVBQUUsSUFBSTtJQUNSLEVBQUUsRUFBRSxJQUFJO0lBQ1IsRUFBRSxFQUFFLElBQUk7Q0FDVCxDQUFDO0FBRUYsTUFBTSxVQUFVLG9CQUFvQixDQUFDLFVBQW1CO0lBQ3RELElBQUksVUFBVSxLQUFLLFNBQVMsSUFBSSxVQUFVLEtBQUssSUFBSTtRQUFFLE9BQU8sRUFBRSxDQUFDO0lBQy9ELE9BQU8sZUFBZSxDQUFDLFVBQVUsQ0FBQyxJQUFJLEVBQUUsQ0FBQztBQUMzQyxDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxzQkFBc0IsQ0FBQyxJQUFzQjtJQUMzRCxNQUFNLFdBQVcsR0FBRyxjQUFjLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ2hELE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDLGNBQWMs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
|
package/dist_ts/skr.types.d.ts
CHANGED
|
@@ -53,7 +53,41 @@ export interface IJournalEntryLine {
|
|
|
53
53
|
credit?: number;
|
|
54
54
|
description?: string;
|
|
55
55
|
costCenter?: string;
|
|
56
|
-
|
|
56
|
+
/** v1 numeric DATEV posting key; optional since schemaVersion 2 (see buKey) */
|
|
57
|
+
postingKey?: TPostingKey;
|
|
58
|
+
side?: 'debit' | 'credit';
|
|
59
|
+
amountCents?: number;
|
|
60
|
+
counterAccount?: string;
|
|
61
|
+
/** string-typed DATEV BU-Schlüssel ('' = none) */
|
|
62
|
+
buKey?: string;
|
|
63
|
+
buchungstext?: string;
|
|
64
|
+
belegfeld1?: string;
|
|
65
|
+
costCenter1?: string;
|
|
66
|
+
docLink?: string;
|
|
67
|
+
/** informational VAT share carried on the line (v1 compatibility) */
|
|
68
|
+
vatAmountCents?: number;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Transaction-shaped projection of a v2 journal entry (or one of its lines).
|
|
72
|
+
* The Transaction collection is frozen in v2 — reads go through these views.
|
|
73
|
+
*/
|
|
74
|
+
export interface ITransactionView {
|
|
75
|
+
id: string;
|
|
76
|
+
transactionNumber: string;
|
|
77
|
+
date: Date;
|
|
78
|
+
debitAccount: string;
|
|
79
|
+
creditAccount: string;
|
|
80
|
+
amount: number;
|
|
81
|
+
vatAmount: number;
|
|
82
|
+
description: string;
|
|
83
|
+
reference: string;
|
|
84
|
+
skrType: TSKRType;
|
|
85
|
+
status: string;
|
|
86
|
+
period: string;
|
|
87
|
+
fiscalYear: number;
|
|
88
|
+
costCenter?: string;
|
|
89
|
+
/** id of the entry this one reverses (Storno linkage) */
|
|
90
|
+
reversalOf?: string;
|
|
57
91
|
}
|
|
58
92
|
export interface ITrialBalanceEntry {
|
|
59
93
|
accountNumber: string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TSKRType } from './skr.types.js';
|
|
2
|
+
export interface IChainVerificationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
entriesChecked: number;
|
|
5
|
+
issues: string[];
|
|
6
|
+
headHash: string | null;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Walk sequence 1..N for (skrType, fiscalYear): check gaplessness, recompute
|
|
10
|
+
* every entry hash from the persisted payload, verify the chain links, and
|
|
11
|
+
* compare the final hash against the stored chain head.
|
|
12
|
+
*/
|
|
13
|
+
export declare function verifyHashChain(skrType: TSKRType, fiscalYear: number): Promise<IChainVerificationResult>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cryptographic audit-trail verification. Prevention of tampering is
|
|
3
|
+
* best-effort (doc-level guards); detection is this module: recompute the
|
|
4
|
+
* whole hash chain from the persisted entries and compare against the stored
|
|
5
|
+
* chain head.
|
|
6
|
+
*/
|
|
7
|
+
import * as core from './core/index.js';
|
|
8
|
+
import { getDbSync } from './skr.database.js';
|
|
9
|
+
import { JOURNAL_ENTRY_COLLECTION } from './skr.period.js';
|
|
10
|
+
import { getSequenceState } from './skr.sequence.js';
|
|
11
|
+
import { buildHashPayload } from './skr.posting.js';
|
|
12
|
+
/**
|
|
13
|
+
* Walk sequence 1..N for (skrType, fiscalYear): check gaplessness, recompute
|
|
14
|
+
* every entry hash from the persisted payload, verify the chain links, and
|
|
15
|
+
* compare the final hash against the stored chain head.
|
|
16
|
+
*/
|
|
17
|
+
export async function verifyHashChain(skrType, fiscalYear) {
|
|
18
|
+
const issues = [];
|
|
19
|
+
const collection = getDbSync().mongoDb.collection(JOURNAL_ENTRY_COLLECTION);
|
|
20
|
+
const docs = await collection
|
|
21
|
+
.find({ skrType, fiscalYear, schemaVersion: 2 })
|
|
22
|
+
.sort({ sequenceNumber: 1 })
|
|
23
|
+
.toArray();
|
|
24
|
+
const state = await getSequenceState(skrType, fiscalYear);
|
|
25
|
+
if (docs.length === 0) {
|
|
26
|
+
return {
|
|
27
|
+
valid: !state || state.value === 0,
|
|
28
|
+
entriesChecked: 0,
|
|
29
|
+
issues: state && state.value !== 0 ? [`sequence counter is ${state.value} but no entries exist`] : [],
|
|
30
|
+
headHash: state?.headHash ?? null,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// gaplessness
|
|
34
|
+
docs.forEach((doc, index) => {
|
|
35
|
+
const expected = index + 1;
|
|
36
|
+
if (doc.sequenceNumber !== expected) {
|
|
37
|
+
issues.push(`sequence gap: expected ${expected}, found ${doc.sequenceNumber}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
// chain
|
|
41
|
+
const genesis = core.genesisHash(skrType, fiscalYear);
|
|
42
|
+
const chain = docs.map((doc) => ({
|
|
43
|
+
sequenceNumber: doc.sequenceNumber,
|
|
44
|
+
prevHash: doc.prevHash,
|
|
45
|
+
entryHash: doc.entryHash,
|
|
46
|
+
payload: buildHashPayload({
|
|
47
|
+
sequenceNumber: doc.sequenceNumber,
|
|
48
|
+
skrType: doc.skrType,
|
|
49
|
+
fiscalYear: doc.fiscalYear,
|
|
50
|
+
date: doc.date,
|
|
51
|
+
postedAt: doc.postedAt,
|
|
52
|
+
description: doc.description,
|
|
53
|
+
reference: doc.reference,
|
|
54
|
+
taxScenario: doc.taxScenario,
|
|
55
|
+
currency: doc.currency,
|
|
56
|
+
reversalOf: doc.reversalOf,
|
|
57
|
+
lines: (doc.lines ?? []),
|
|
58
|
+
}),
|
|
59
|
+
}));
|
|
60
|
+
const failure = core.verifyChain(chain, genesis);
|
|
61
|
+
if (failure) {
|
|
62
|
+
issues.push(failure.reason);
|
|
63
|
+
}
|
|
64
|
+
// head comparison
|
|
65
|
+
const lastHash = chain[chain.length - 1].entryHash;
|
|
66
|
+
if (!state) {
|
|
67
|
+
issues.push('no sequence state document found');
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
if (state.headHash !== lastHash) {
|
|
71
|
+
issues.push(`stored chain head ${state.headHash} !== last entry hash ${lastHash}`);
|
|
72
|
+
}
|
|
73
|
+
if (state.value !== docs.length) {
|
|
74
|
+
issues.push(`sequence counter ${state.value} !== entry count ${docs.length}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
valid: issues.length === 0,
|
|
79
|
+
entriesChecked: docs.length,
|
|
80
|
+
issues,
|
|
81
|
+
headHash: state?.headHash ?? null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2tyLnZlcmlmeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3Nrci52ZXJpZnkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7O0dBS0c7QUFDSCxPQUFPLEtBQUssSUFBSSxNQUFNLGlCQUFpQixDQUFDO0FBQ3hDLE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUM5QyxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUMzRCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQVVwRDs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLEtBQUssVUFBVSxlQUFlLENBQ25DLE9BQWlCLEVBQ2pCLFVBQWtCO0lBRWxCLE1BQU0sTUFBTSxHQUFhLEVBQUUsQ0FBQztJQUM1QixNQUFNLFVBQVUsR0FBRyxTQUFTLEVBQUUsQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLHdCQUF3QixDQUFDLENBQUM7SUFDNUUsTUFBTSxJQUFJLEdBQUcsTUFBTSxVQUFVO1NBQzFCLElBQUksQ0FBQyxFQUFFLE9BQU8sRUFBRSxVQUFVLEVBQUUsYUFBYSxFQUFFLENBQUMsRUFBRSxDQUFDO1NBQy9DLElBQUksQ0FBQyxFQUFFLGNBQWMsRUFBRSxDQUFDLEVBQUUsQ0FBQztTQUMzQixPQUFPLEVBQUUsQ0FBQztJQUViLE1BQU0sS0FBSyxHQUFHLE1BQU0sZ0JBQWdCLENBQUMsT0FBTyxFQUFFLFVBQVUsQ0FBQyxDQUFDO0lBQzFELElBQUksSUFBSSxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztRQUN0QixPQUFPO1lBQ0wsS0FBSyxFQUFFLENBQUMsS0FBSyxJQUFJLEtBQUssQ0FBQyxLQUFLLEtBQUssQ0FBQztZQUNsQyxjQUFjLEVBQUUsQ0FBQztZQUNqQixNQUFNLEVBQUUsS0FBSyxJQUFJLEtBQUss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
|
package/package.json
CHANGED
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fin.cx/skr",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "SKR03 and SKR04 German accounting standards for double-entry bookkeeping",
|
|
5
5
|
"main": "dist_ts/index.js",
|
|
6
6
|
"typings": "dist_ts/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
|
-
"scripts": {
|
|
9
|
-
"test": "tstest test/ --verbose --logfile --timeout=60 && pnpm run test:published-types",
|
|
10
|
-
"test:published-types": "pnpm build && pnpm exec tsc --pretty false -p test/fixtures/strict-consumer/tsconfig.json",
|
|
11
|
-
"build": "tsbuild --web --node",
|
|
12
|
-
"buildDocs": "tsdoc"
|
|
13
|
-
},
|
|
14
8
|
"keywords": [
|
|
15
9
|
"skr03",
|
|
16
10
|
"skr04",
|
|
@@ -24,28 +18,28 @@
|
|
|
24
18
|
],
|
|
25
19
|
"author": "Fin.cx",
|
|
26
20
|
"license": "MIT",
|
|
27
|
-
"packageManager": "pnpm@10.11.0",
|
|
28
21
|
"dependencies": {
|
|
29
|
-
"@e-invoice-eu/core": "^3.1.
|
|
30
|
-
"@fin.cx/
|
|
31
|
-
"@
|
|
32
|
-
"@push.rocks/
|
|
33
|
-
"@push.rocks/
|
|
34
|
-
"@push.rocks/
|
|
22
|
+
"@e-invoice-eu/core": "^3.1.1",
|
|
23
|
+
"@fin.cx/calculation": "^1.1.1",
|
|
24
|
+
"@fin.cx/einvoice": "6.0.0",
|
|
25
|
+
"@push.rocks/smartdata": "^7.2.1",
|
|
26
|
+
"@push.rocks/smartfile": "^13.1.3",
|
|
27
|
+
"@push.rocks/smartfs": "^1.5.1",
|
|
28
|
+
"@push.rocks/smarthash": "^3.2.7",
|
|
35
29
|
"@push.rocks/smartlog": "^3.2.2",
|
|
36
30
|
"@push.rocks/smartpath": "^6.0.0",
|
|
37
|
-
"@push.rocks/smartpdf": "^4.2.
|
|
31
|
+
"@push.rocks/smartpdf": "^4.2.2",
|
|
38
32
|
"@push.rocks/smarttime": "^4.2.3",
|
|
39
33
|
"@push.rocks/smartunique": "^3.0.9",
|
|
40
34
|
"merkletreejs": "^0.6.0",
|
|
41
35
|
"node-forge": "^1.4.0"
|
|
42
36
|
},
|
|
43
37
|
"devDependencies": {
|
|
44
|
-
"@git.zone/tsbuild": "^4.4.
|
|
45
|
-
"@git.zone/tsrun": "^2.0.
|
|
46
|
-
"@git.zone/tstest": "^3.6.
|
|
47
|
-
"@push.rocks/qenv": "^6.1.
|
|
48
|
-
"@types/node": "^
|
|
38
|
+
"@git.zone/tsbuild": "^4.4.2",
|
|
39
|
+
"@git.zone/tsrun": "^2.0.5",
|
|
40
|
+
"@git.zone/tstest": "^3.6.6",
|
|
41
|
+
"@push.rocks/qenv": "^6.1.4",
|
|
42
|
+
"@types/node": "^26.1.1",
|
|
49
43
|
"@types/node-forge": "^1.3.14"
|
|
50
44
|
},
|
|
51
45
|
"repository": {
|
|
@@ -67,9 +61,12 @@
|
|
|
67
61
|
"cli.js",
|
|
68
62
|
".smartconfig.json",
|
|
69
63
|
"readme.md",
|
|
70
|
-
"license
|
|
64
|
+
"license"
|
|
71
65
|
],
|
|
72
|
-
"
|
|
73
|
-
"
|
|
66
|
+
"scripts": {
|
|
67
|
+
"test": "tstest test/ --verbose --logfile --timeout=60 && pnpm run test:published-types",
|
|
68
|
+
"test:published-types": "pnpm build && pnpm exec tsc --pretty false -p test/fixtures/strict-consumer/tsconfig.json",
|
|
69
|
+
"build": "tsbuild --web --node",
|
|
70
|
+
"buildDocs": "tsdoc"
|
|
74
71
|
}
|
|
75
|
-
}
|
|
72
|
+
}
|
package/readme.hints.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Project Readme Hints
|
|
2
2
|
|
|
3
|
+
## Dev Environment (2026-07-09)
|
|
4
|
+
|
|
5
|
+
- Local services are managed by `gitzone services` (@git.zone/cli >= 2.23.0); the old
|
|
6
|
+
`services.sh` was removed. `gitzone services start` creates MongoDB as a **single-node
|
|
7
|
+
replica set** (rs0, keyfile auth, mongod on the mapped port inside the container) —
|
|
8
|
+
required because skr v2 posting uses MongoDB transactions. Verify with
|
|
9
|
+
`.nogit/debug/check-replicaset.ts` (tsx).
|
|
10
|
+
- Service selection lives in `.smartconfig.json` under `@git.zone/cli.services`;
|
|
11
|
+
runtime config in `.nogit/env.json`. Old standalone data was backed up to
|
|
12
|
+
`.nogit/mongodata.pre-replset.bak` (disposable test data).
|
|
13
|
+
- Program plan for the accounting roadmap ("no external tax accountant") is in
|
|
14
|
+
`readme.plan.md`; M0 = skr v2 core hardening. @fin.cx/calculation ships the cents
|
|
15
|
+
module (TCents) that skr v2 money handling builds on.
|
|
16
|
+
|
|
17
|
+
## M0 Phase 2 — stateless core (2026-07-09)
|
|
18
|
+
|
|
19
|
+
- `ts/core/` is the pure booking core (exported as `core` from the package index):
|
|
20
|
+
types, BU keys, tax-scenario cascade, account policies, recipes, validation,
|
|
21
|
+
DATEV EXTF writer, hash chain. Hard rule: core imports only @fin.cx/calculation
|
|
22
|
+
(+ smarthash for SHA-256) — no smartdata/fs/network.
|
|
23
|
+
- **BU-key corrections vs v1/finance.plus** (cross-checked 2026-07-09): 18/19 =
|
|
24
|
+
steuerpflichtiger innergemeinschaftlicher Erwerb 7%/19% (§1a UStG); 91/94 =
|
|
25
|
+
Leistungsempfänger schuldet die Steuer 7%/19% (§13b UStG); 40 = Aufhebung der
|
|
26
|
+
Automatik. v1's `skr.postingkeys.ts` (numeric keys with partly different meanings)
|
|
27
|
+
is superseded by `ts/core/core.bukeys.ts`. Official DATEV table (Hilfe-Center doc
|
|
28
|
+
0904313) is portal-gated — final verification = manual DATEV import per RC.
|
|
29
|
+
- **DATEV EXTF v13 facts** (verified against a DATEV-importable reference file):
|
|
30
|
+
125 data columns, 31 header fields, field 114 = Festschreibung, decimal-comma
|
|
31
|
+
amounts, Belegdatum TTMM, text fields quoted, CRLF; file encoding must be CP1252
|
|
32
|
+
(conversion happens at the IO layer, core emits a JS string).
|
|
33
|
+
`ts/core/core.datev.captions.ts` is generated from that reference — do not edit.
|
|
34
|
+
- Automatik convention in recipes: a line on an Automatikkonto carries key '' when
|
|
35
|
+
the entry has no explicit VAT lines, and key '40' when explicit VAT lines exist
|
|
36
|
+
(automatism must be cancelled so DATEV import doesn't double the tax).
|
|
37
|
+
- PROVISIONAL policy accounts (verify in M-A): reduced-rate Skonto accounts
|
|
38
|
+
(3731/8731, 5731/4731) and rounding-diff accounts (2700/2300, 4830/6300).
|
|
39
|
+
|
|
3
40
|
## Current Status (2025-10-27)
|
|
4
41
|
|
|
5
42
|
### Test Results
|
package/readme.md
CHANGED
|
@@ -4,6 +4,24 @@
|
|
|
4
4
|
|
|
5
5
|
It is built for developers who need a programmable accounting core instead of a pile of CSV glue code: initialize a chart of accounts, post validated transactions and journal entries, generate reports, and archive year-end data in a structured export format.
|
|
6
6
|
|
|
7
|
+
**Version 2** makes the ledger filing-grade:
|
|
8
|
+
|
|
9
|
+
- Exact integer-cent amounts internally (no float drift; balance checks are `===`)
|
|
10
|
+
- Atomic posting via MongoDB transactions (replica set required)
|
|
11
|
+
- GoBD audit trail: gapless sequence numbers, SHA-256 hash chain, immutable posted entries, Storno-only corrections
|
|
12
|
+
- Festschreibung: period locking with cryptographic chain verification
|
|
13
|
+
- A pure, stateless booking core (`core` export): BU keys, tax-scenario detection, booking recipes, validation — no database needed
|
|
14
|
+
- DATEV Buchungsstapel export in the real EXTF 700 / Formatversion 13 layout (125 columns)
|
|
15
|
+
|
|
16
|
+
### Breaking changes in v2
|
|
17
|
+
|
|
18
|
+
- MongoDB must run as a **replica set** (single-node is fine); `initialize()` fails hard without transaction support. `gitzone services start` creates a suitable MongoDB automatically.
|
|
19
|
+
- `postTransaction()` / `reverseTransaction()` return transaction-shaped views (`ITransactionView`), not `Transaction` documents; the Transaction collection is frozen as a legacy read model.
|
|
20
|
+
- Posted entries are immutable: reversing never mutates the original (`status` stays `posted`; linkage via `reversalOf`).
|
|
21
|
+
- `exportToDATEV()` now emits the EXTF 700 format; persist the string as **CP1252**.
|
|
22
|
+
- `IJournalEntryLine.postingKey` is optional; the v2 line fields (`side`, `amountCents`, `counterAccount`, string `buKey`) are authoritative.
|
|
23
|
+
- Existing v1 data: run `api.migrateToV2()` once (idempotent; keeps all legacy fields; returns a report to archive).
|
|
24
|
+
|
|
7
25
|
## Issue Reporting and Security
|
|
8
26
|
|
|
9
27
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
|
@@ -29,11 +47,15 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
29
47
|
## Requirements
|
|
30
48
|
|
|
31
49
|
- Node.js 20+ with ESM support
|
|
32
|
-
- A reachable MongoDB instance
|
|
50
|
+
- A reachable MongoDB instance running as a **replica set** (transactions are
|
|
51
|
+
mandatory for atomic posting; a single-node replica set is sufficient —
|
|
52
|
+
`gitzone services start` provides one locally)
|
|
33
53
|
- `pnpm`
|
|
34
54
|
|
|
35
55
|
The test setup reads MongoDB connection details from `.nogit/` via `@push.rocks/qenv`, but the runtime API only needs a `mongoDbUrl` and an optional `dbName`.
|
|
36
56
|
|
|
57
|
+
If you use the invoice helpers, pass `invoiceExportPath` to control where content-addressed invoice XML, PDF files, metadata, validation output, and the registry are stored. Without it, invoices are stored under `./exports/invoices` from the current working directory.
|
|
58
|
+
|
|
37
59
|
## Installation
|
|
38
60
|
|
|
39
61
|
```bash
|
|
@@ -48,6 +70,7 @@ import { SkrApi } from '@fin.cx/skr';
|
|
|
48
70
|
const api = new SkrApi({
|
|
49
71
|
mongoDbUrl: 'mongodb://localhost:27017',
|
|
50
72
|
dbName: 'accounting_demo',
|
|
73
|
+
invoiceExportPath: './exports/invoices',
|
|
51
74
|
});
|
|
52
75
|
|
|
53
76
|
await api.initialize('SKR03');
|
|
@@ -176,6 +199,73 @@ const ok = api.validateDoubleEntry(100, 100);
|
|
|
176
199
|
const reversed = await api.reverseTransaction(transactionId);
|
|
177
200
|
```
|
|
178
201
|
|
|
202
|
+
Period, balance, and audit helpers:
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
await api.closePeriod('2024-12', '9400');
|
|
206
|
+
|
|
207
|
+
const bankBalance = await api.getAccountBalance('1200', new Date('2024-12-31'));
|
|
208
|
+
|
|
209
|
+
await api.recalculateBalances();
|
|
210
|
+
|
|
211
|
+
const auditFindings = await api.getUnbalancedTransactions();
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Account CSV import and export:
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
const imported = await api.importAccountsFromCSV(csvContent);
|
|
218
|
+
const accountCsv = await api.exportAccountsToCSV();
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## v2 Ledger: Recipes, Festschreibung, Audit Chain
|
|
222
|
+
|
|
223
|
+
The stateless core builds validated drafts; the poster writes them atomically:
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
import { SkrApi, core } from '@fin.cx/skr';
|
|
227
|
+
|
|
228
|
+
const api = new SkrApi({ mongoDbUrl, dbName });
|
|
229
|
+
await api.initialize('SKR03');
|
|
230
|
+
const policy = api.getDefaultPolicy();
|
|
231
|
+
|
|
232
|
+
// booking recipes are pure functions — usable without any database
|
|
233
|
+
const draft = core.bookVendorInvoice(
|
|
234
|
+
{
|
|
235
|
+
date: new Date(),
|
|
236
|
+
vendorAccount: '70001',
|
|
237
|
+
grossCents: 11900,
|
|
238
|
+
scenario: 'domestic_standard',
|
|
239
|
+
invoiceNumber: 'RE-1001',
|
|
240
|
+
description: 'Bürobedarf',
|
|
241
|
+
},
|
|
242
|
+
policy,
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
const entry = await api.postDraft(draft); // gapless sequence + hash chain
|
|
246
|
+
const storno = await api.postStorno(entry); // corrections never mutate
|
|
247
|
+
|
|
248
|
+
await api.festschreibePeriod('2026-03'); // GoBD period lock
|
|
249
|
+
const audit = await api.verifyAuditChain(2026); // recompute the whole chain
|
|
250
|
+
const balance = await api.getAccountBalanceCents('1200'); // exact cents
|
|
251
|
+
|
|
252
|
+
const { csv, filename } = await api.exportDatevBuchungsstapel({
|
|
253
|
+
dateFrom: new Date('2026-03-01'),
|
|
254
|
+
dateTo: new Date('2026-03-31'),
|
|
255
|
+
consultantNumber: 1001,
|
|
256
|
+
clientNumber: 456,
|
|
257
|
+
fiscalYearStart: new Date('2026-01-01'),
|
|
258
|
+
festschreibung: true,
|
|
259
|
+
});
|
|
260
|
+
// persist csv as CP1252 — DATEV expects ANSI encoding
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Also in `core`: `bookCustomerInvoice` (incl. intra-EU supply, export, §13b),
|
|
264
|
+
`bookBankPayment` (Skonto with VAT correction, tolerance write-offs),
|
|
265
|
+
`bookInternalTransfer` (Geldtransit), `bookToSuspense`/`resolveSuspense`,
|
|
266
|
+
`fxDifferenceLines`, `detectTaxScenario` (confidence-scored cascade), the
|
|
267
|
+
BU-key table (`BU_KEY_RULES`), and `validateJournalDraft`.
|
|
268
|
+
|
|
179
269
|
## Reports And Exports
|
|
180
270
|
|
|
181
271
|
Available reporting methods on `SkrApi`:
|
|
@@ -256,6 +346,8 @@ The API also exposes:
|
|
|
256
346
|
- `createInvoiceComplianceReport()`
|
|
257
347
|
- `generateInvoice()`
|
|
258
348
|
|
|
349
|
+
Invoice imports are stored by content hash, deduplicated, indexed in an NDJSON registry, and searchable by direction, date range, party, amount range, invoice number, and status metadata.
|
|
350
|
+
|
|
259
351
|
## Public Exports
|
|
260
352
|
|
|
261
353
|
Top-level exports include:
|
|
@@ -274,6 +366,7 @@ Top-level exports include:
|
|
|
274
366
|
- `PdfReportGenerator`
|
|
275
367
|
- `SecurityManager`
|
|
276
368
|
- `SKR03_ACCOUNTS`, `SKR04_ACCOUNTS`
|
|
369
|
+
- `SKR03_ACCOUNT_CLASSES`, `SKR04_ACCOUNT_CLASSES`
|
|
277
370
|
|
|
278
371
|
This makes the package usable as both an application-facing API and a toolkit for custom accounting workflows.
|
|
279
372
|
|