@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.
Files changed (94) hide show
  1. package/.smartconfig.json +33 -6
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/core/core.accountpolicy.d.ts +67 -0
  4. package/dist_ts/core/core.accountpolicy.js +91 -0
  5. package/dist_ts/core/core.bukeys.d.ts +38 -0
  6. package/dist_ts/core/core.bukeys.js +117 -0
  7. package/dist_ts/core/core.datev.captions.d.ts +7 -0
  8. package/dist_ts/core/core.datev.captions.js +134 -0
  9. package/dist_ts/core/core.datev.d.ts +47 -0
  10. package/dist_ts/core/core.datev.js +129 -0
  11. package/dist_ts/core/core.hash.d.ts +27 -0
  12. package/dist_ts/core/core.hash.js +72 -0
  13. package/dist_ts/core/core.plugins.d.ts +3 -0
  14. package/dist_ts/core/core.plugins.js +9 -0
  15. package/dist_ts/core/core.recipes.d.ts +118 -0
  16. package/dist_ts/core/core.recipes.js +504 -0
  17. package/dist_ts/core/core.taxscenario.d.ts +27 -0
  18. package/dist_ts/core/core.taxscenario.js +85 -0
  19. package/dist_ts/core/core.types.d.ts +48 -0
  20. package/dist_ts/core/core.types.js +2 -0
  21. package/dist_ts/core/core.validate.d.ts +17 -0
  22. package/dist_ts/core/core.validate.js +89 -0
  23. package/dist_ts/core/index.d.ts +15 -0
  24. package/dist_ts/core/index.js +16 -0
  25. package/dist_ts/index.d.ts +15 -0
  26. package/dist_ts/index.js +10 -1
  27. package/dist_ts/plugins.d.ts +2 -1
  28. package/dist_ts/plugins.js +4 -2
  29. package/dist_ts/skr.api.d.ts +91 -22
  30. package/dist_ts/skr.api.js +357 -42
  31. package/dist_ts/skr.balances.d.ts +28 -0
  32. package/dist_ts/skr.balances.js +96 -0
  33. package/dist_ts/skr.classes.journalentry.d.ts +19 -3
  34. package/dist_ts/skr.classes.journalentry.js +67 -4
  35. package/dist_ts/skr.classes.reports.js +49 -102
  36. package/dist_ts/skr.database.d.ts +2 -0
  37. package/dist_ts/skr.database.js +1 -1
  38. package/dist_ts/skr.errors.d.ts +18 -0
  39. package/dist_ts/skr.errors.js +34 -0
  40. package/dist_ts/skr.export.js +4 -7
  41. package/dist_ts/skr.invoice.adapter.d.ts +15 -1
  42. package/dist_ts/skr.invoice.adapter.js +253 -41
  43. package/dist_ts/skr.invoice.booking.js +14 -28
  44. package/dist_ts/skr.migrate.d.ts +20 -0
  45. package/dist_ts/skr.migrate.js +223 -0
  46. package/dist_ts/skr.period.d.ts +47 -0
  47. package/dist_ts/skr.period.js +95 -0
  48. package/dist_ts/skr.posting.d.ts +74 -0
  49. package/dist_ts/skr.posting.js +209 -0
  50. package/dist_ts/skr.security.d.ts +6 -9
  51. package/dist_ts/skr.security.js +16 -89
  52. package/dist_ts/skr.sequence.d.ts +32 -0
  53. package/dist_ts/skr.sequence.js +51 -0
  54. package/dist_ts/skr.txview.d.ts +46 -0
  55. package/dist_ts/skr.txview.js +235 -0
  56. package/dist_ts/skr.types.d.ts +35 -1
  57. package/dist_ts/skr.verify.d.ts +13 -0
  58. package/dist_ts/skr.verify.js +84 -0
  59. package/package.json +21 -24
  60. package/readme.hints.md +37 -0
  61. package/readme.md +94 -1
  62. package/readme.plan.md +180 -243
  63. package/ts/00_commitinfo_data.ts +1 -1
  64. package/ts/core/core.accountpolicy.ts +154 -0
  65. package/ts/core/core.bukeys.ts +146 -0
  66. package/ts/core/core.datev.captions.ts +133 -0
  67. package/ts/core/core.datev.ts +174 -0
  68. package/ts/core/core.hash.ts +76 -0
  69. package/ts/core/core.plugins.ts +9 -0
  70. package/ts/core/core.recipes.ts +659 -0
  71. package/ts/core/core.taxscenario.ts +156 -0
  72. package/ts/core/core.types.ts +74 -0
  73. package/ts/core/core.validate.ts +119 -0
  74. package/ts/core/index.ts +15 -0
  75. package/ts/index.ts +31 -0
  76. package/ts/plugins.ts +4 -0
  77. package/ts/skr.api.ts +432 -46
  78. package/ts/skr.balances.ts +127 -0
  79. package/ts/skr.classes.journalentry.ts +54 -5
  80. package/ts/skr.classes.reports.ts +54 -126
  81. package/ts/skr.database.ts +3 -0
  82. package/ts/skr.errors.ts +44 -0
  83. package/ts/skr.export.ts +3 -6
  84. package/ts/skr.invoice.adapter.ts +291 -45
  85. package/ts/skr.invoice.booking.ts +22 -34
  86. package/ts/skr.migrate.ts +268 -0
  87. package/ts/skr.period.ts +139 -0
  88. package/ts/skr.posting.ts +273 -0
  89. package/ts/skr.security.ts +18 -93
  90. package/ts/skr.sequence.ts +89 -0
  91. package/ts/skr.txview.ts +268 -0
  92. package/ts/skr.types.ts +37 -1
  93. package/ts/skr.verify.ts +99 -0
  94. /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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
@@ -53,7 +53,41 @@ export interface IJournalEntryLine {
53
53
  credit?: number;
54
54
  description?: string;
55
55
  costCenter?: string;
56
- postingKey: TPostingKey;
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": "1.3.0",
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.0",
30
- "@fin.cx/einvoice": "5.2.0",
31
- "@push.rocks/smartdata": "^7.1.7",
32
- "@push.rocks/smartfile": "^13.1.2",
33
- "@push.rocks/smartfs": "^1.5.0",
34
- "@push.rocks/smarthash": "^3.2.6",
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.0",
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.0",
45
- "@git.zone/tsrun": "^2.0.2",
46
- "@git.zone/tstest": "^3.6.3",
47
- "@push.rocks/qenv": "^6.1.3",
48
- "@types/node": "^25.6.0",
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.md"
64
+ "license"
71
65
  ],
72
- "pnpm": {
73
- "overrides": {}
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