@fin.cx/skr 3.2.0 → 3.3.1

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 CHANGED
@@ -41,10 +41,10 @@
41
41
  },
42
42
  "services": [
43
43
  "mongodb",
44
- "minio"
44
+ "objectstorage"
45
45
  ]
46
46
  },
47
47
  "@ship.zone/szci": {
48
48
  "npmGlobalTools": []
49
49
  }
50
- }
50
+ }
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/skr',
6
- version: '3.2.0',
6
+ version: '3.3.1',
7
7
  description: 'SKR03 and SKR04 German accounting standards for double-entry bookkeeping'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSxhQUFhO0lBQ25CLE9BQU8sRUFBRSxPQUFPO0lBQ2hCLFdBQVcsRUFBRSwwRUFBMEU7Q0FDeEYsQ0FBQSJ9
@@ -1,4 +1,5 @@
1
- import type { IJournalDraft, TBuKey, TCents } from './core.types.js';
1
+ import type { IJournalDraft, IJournalLineDraft, TBuKey, TCents } from './core.types.js';
2
+ import { type IAccountPolicy } from './core.accountpolicy.js';
2
3
  export declare const DATEV_COLUMN_COUNT = 125;
3
4
  export declare const DATEV_HEADER_FIELD_COUNT = 31;
4
5
  export interface IDatevBuchungsstapelRow {
@@ -11,6 +12,7 @@ export interface IDatevBuchungsstapelRow {
11
12
  belegfeld1?: string;
12
13
  belegfeld2?: string;
13
14
  buchungstext: string;
15
+ /** document GUID; written as `BEDI "GUID"` (field 20) */
14
16
  belegLink?: string;
15
17
  kost1?: string;
16
18
  kost2?: string;
@@ -31,13 +33,41 @@ export interface IDatevExtfHeaderOptions {
31
33
  origin?: string;
32
34
  exportedBy?: string;
33
35
  }
36
+ export interface IDatevRowsOptions {
37
+ festschreibung?: boolean;
38
+ /**
39
+ * Recognises VAT lines and Automatikkonten while consolidating; defaults to
40
+ * the default policy of each draft's chart.
41
+ */
42
+ policy?: IAccountPolicy;
43
+ }
34
44
  /**
35
- * Flatten journal drafts into DATEV rows one row per line, with the line's
36
- * explicit counterAccount as Gegenkonto (no pair reconstruction).
45
+ * Consolidate one balanced draft into the lines that become DATEV bookings.
46
+ *
47
+ * 1. VAT lines. A §13b / intra-EU pair (input and output VAT booked against
48
+ * each other) is dropped when a base line of the draft carries the key
49
+ * that makes DATEV compute both sides (94/91, 19/18); without such a key
50
+ * the pair stays and becomes one explicit booking. Domestic VAT lines are
51
+ * folded, per Gegenkonto and side, into the base lines whose BU key
52
+ * carries their rate (9/8, 3/2), sharing the VAT in proportion; a VAT
53
+ * line without a keyed base is folded into an Automatikkonto base (key
54
+ * 40 becomes '') only when it is the single remaining VAT line and that
55
+ * base is the single Automatikkonto candidate, because the policy does
56
+ * not know the rate of an Automatikkonto. Everything else stays an
57
+ * explicit booking on the tax account, next to a base that keeps key 40.
58
+ * 2. Mirror lines (A against B and B against A with the same amount) become
59
+ * one booking, taken from the line with a BU key, else from the debit line.
60
+ * 3. Split lines against one aggregate line (their signed sum cancels the
61
+ * aggregate) become one booking each; the aggregate line is dropped.
62
+ * Anything left over throws: writing it would double the booking on import.
37
63
  */
38
- export declare function journalDraftsToDatevRows(drafts: IJournalDraft[], options?: {
39
- festschreibung?: boolean;
40
- }): IDatevBuchungsstapelRow[];
64
+ export declare function consolidateJournalDraftForDatev(draft: IJournalDraft, policy: IAccountPolicy): IJournalLineDraft[];
65
+ /**
66
+ * Convert journal drafts into DATEV rows: one row per booking after
67
+ * consolidateJournalDraftForDatev, with the line's counterAccount as
68
+ * Gegenkonto.
69
+ */
70
+ export declare function journalDraftsToDatevRows(drafts: IJournalDraft[], options?: IDatevRowsOptions): IDatevBuchungsstapelRow[];
41
71
  /**
42
72
  * Produce the complete EXTF file content (header, caption row, data rows).
43
73
  */
@@ -7,11 +7,18 @@
7
7
  * STRING with CRLF; persisting it as CP1252 is the IO layer's job.
8
8
  * One manual import into real DATEV per release candidate remains the final
9
9
  * conformance gate (readme.plan.md, M0).
10
+ *
11
+ * A DATEV row is one Buchungssatz: the amount is booked on Konto with the
12
+ * Soll/Haben mark and on Gegenkonto with the opposite side. A journal draft
13
+ * is a balanced set of lines, so it is consolidated into bookings before it
14
+ * is written (see journalDraftsToDatevRows); writing one row per line would
15
+ * book every draft twice on import.
10
16
  */
11
17
  import * as plugins from './core.plugins.js';
18
+ import { getDefaultPolicy, isAutomatikAccount } from './core.accountpolicy.js';
12
19
  import { DATEV_COLUMN_CAPTIONS } from './core.datev.captions.js';
13
20
  import { DATEV_MAX_BELEGFELD, DATEV_MAX_BUCHUNGSTEXT } from './core.validate.js';
14
- const { centsToString } = plugins.calculation;
21
+ const { allocateCents, centsToString } = plugins.calculation;
15
22
  export const DATEV_COLUMN_COUNT = 125;
16
23
  export const DATEV_HEADER_FIELD_COUNT = 31;
17
24
  function pad(value, width) {
@@ -40,14 +47,169 @@ function quote(value, maxLength) {
40
47
  }
41
48
  return `"${sanitized}"`;
42
49
  }
50
+ /** Domestic tax keys whose row carries the gross amount; DATEV computes the VAT. */
51
+ const VAT_RATE_OF_KEY = { '9': 19, '8': 7, '3': 19, '2': 7 };
52
+ function isVatAccount(policy, accountNumber) {
53
+ return Object.values(policy.vat).includes(accountNumber);
54
+ }
55
+ /** Rate of a domestic VAT account; intra-EU and §13b accounts only ever appear as pairs. */
56
+ function vatRateOfAccount(policy, accountNumber) {
57
+ const { vat } = policy;
58
+ if (accountNumber === vat.inputStandard || accountNumber === vat.outputStandard)
59
+ return 19;
60
+ if (accountNumber === vat.inputReduced || accountNumber === vat.outputReduced)
61
+ return 7;
62
+ return undefined;
63
+ }
64
+ function signedCents(line) {
65
+ return line.side === 'debit' ? line.amountCents : -line.amountCents;
66
+ }
67
+ function mirrors(left, right) {
68
+ return (left.counterAccount === right.accountNumber &&
69
+ right.counterAccount === left.accountNumber &&
70
+ signedCents(left) + signedCents(right) === 0);
71
+ }
72
+ /** Keys that make DATEV compute a self-assessed VAT pair (input against output VAT). */
73
+ function selfAssessedKeysOf(policy, accountA, accountB) {
74
+ if (accountA === accountB)
75
+ return undefined;
76
+ const reverseCharge = [policy.vat.reverseChargeInput, policy.vat.reverseChargeOutput];
77
+ const intraEu = [policy.vat.intraEuAcqInput, policy.vat.intraEuAcqOutput];
78
+ if (reverseCharge.includes(accountA) && reverseCharge.includes(accountB))
79
+ return ['94', '91'];
80
+ if (intraEu.includes(accountA) && intraEu.includes(accountB))
81
+ return ['19', '18'];
82
+ return undefined;
83
+ }
84
+ /**
85
+ * Consolidate one balanced draft into the lines that become DATEV bookings.
86
+ *
87
+ * 1. VAT lines. A §13b / intra-EU pair (input and output VAT booked against
88
+ * each other) is dropped when a base line of the draft carries the key
89
+ * that makes DATEV compute both sides (94/91, 19/18); without such a key
90
+ * the pair stays and becomes one explicit booking. Domestic VAT lines are
91
+ * folded, per Gegenkonto and side, into the base lines whose BU key
92
+ * carries their rate (9/8, 3/2), sharing the VAT in proportion; a VAT
93
+ * line without a keyed base is folded into an Automatikkonto base (key
94
+ * 40 becomes '') only when it is the single remaining VAT line and that
95
+ * base is the single Automatikkonto candidate, because the policy does
96
+ * not know the rate of an Automatikkonto. Everything else stays an
97
+ * explicit booking on the tax account, next to a base that keeps key 40.
98
+ * 2. Mirror lines (A against B and B against A with the same amount) become
99
+ * one booking, taken from the line with a BU key, else from the debit line.
100
+ * 3. Split lines against one aggregate line (their signed sum cancels the
101
+ * aggregate) become one booking each; the aggregate line is dropped.
102
+ * Anything left over throws: writing it would double the booking on import.
103
+ */
104
+ export function consolidateJournalDraftForDatev(draft, policy) {
105
+ const lines = draft.lines
106
+ .map((line) => line.amountCents < 0
107
+ ? { ...line, amountCents: -line.amountCents, side: (line.side === 'debit' ? 'credit' : 'debit') }
108
+ : { ...line })
109
+ .filter((line) => line.amountCents !== 0);
110
+ const consumed = new Set();
111
+ const open = () => lines.map((_, index) => index).filter((index) => !consumed.has(index));
112
+ const isVat = (index) => isVatAccount(policy, lines[index].accountNumber);
113
+ // 1a self-assessed VAT pairs
114
+ for (const i of open()) {
115
+ if (consumed.has(i) || !isVat(i))
116
+ continue;
117
+ for (const j of open()) {
118
+ if (j <= i || !isVat(j) || !mirrors(lines[i], lines[j]))
119
+ continue;
120
+ const keys = selfAssessedKeysOf(policy, lines[i].accountNumber, lines[j].accountNumber);
121
+ if (keys && open().some((k) => !isVat(k) && keys.includes(lines[k].buKey))) {
122
+ consumed.add(i);
123
+ consumed.add(j);
124
+ }
125
+ break;
126
+ }
127
+ }
128
+ // 1b domestic VAT lines folded into their base lines, per Gegenkonto and side
129
+ const fold = (vatIndex, bases, clearKey) => {
130
+ const shares = allocateCents(lines[vatIndex].amountCents, bases.map((j) => lines[j].amountCents));
131
+ bases.forEach((j, position) => {
132
+ lines[j] = { ...lines[j], amountCents: lines[j].amountCents + shares[position], buKey: clearKey ? '' : lines[j].buKey };
133
+ });
134
+ consumed.add(vatIndex);
135
+ };
136
+ const groups = new Map();
137
+ for (const i of open()) {
138
+ if (!isVat(i) || vatRateOfAccount(policy, lines[i].accountNumber) === undefined)
139
+ continue;
140
+ const key = `${lines[i].counterAccount}|${lines[i].side}`;
141
+ groups.set(key, [...(groups.get(key) ?? []), i]);
142
+ }
143
+ for (const [key, vatLines] of groups) {
144
+ const bases = open().filter((j) => !isVat(j) && `${lines[j].counterAccount}|${lines[j].side}` === key);
145
+ const remaining = [];
146
+ for (const i of vatLines) {
147
+ const rate = vatRateOfAccount(policy, lines[i].accountNumber);
148
+ const keyed = bases.filter((j) => VAT_RATE_OF_KEY[lines[j].buKey] === rate);
149
+ if (keyed.length === 0) {
150
+ remaining.push(i);
151
+ continue;
152
+ }
153
+ fold(i, keyed, false);
154
+ }
155
+ const automatik = bases.filter((j) => isAutomatikAccount(policy, lines[j].accountNumber) && (lines[j].buKey === '' || lines[j].buKey === '40'));
156
+ if (remaining.length === 1 && automatik.length === 1) {
157
+ fold(remaining[0], automatik, true);
158
+ }
159
+ }
160
+ const bookings = [];
161
+ // 2 mirror lines
162
+ for (const i of open()) {
163
+ if (consumed.has(i))
164
+ continue;
165
+ for (const j of open()) {
166
+ if (j <= i)
167
+ continue;
168
+ if (mirrors(lines[i], lines[j])) {
169
+ const keep = lines[i].buKey !== '' ? i : lines[j].buKey !== '' ? j : lines[i].side === 'debit' ? i : j;
170
+ bookings.push(lines[keep]);
171
+ consumed.add(i);
172
+ consumed.add(j);
173
+ break;
174
+ }
175
+ }
176
+ }
177
+ // 3 split lines against their aggregate line
178
+ for (const aggregateIndex of open()) {
179
+ if (consumed.has(aggregateIndex))
180
+ continue;
181
+ const aggregate = lines[aggregateIndex];
182
+ const group = open().filter((index) => index !== aggregateIndex && lines[index].counterAccount === aggregate.accountNumber);
183
+ if (group.length === 0)
184
+ continue;
185
+ if (signedCents(aggregate) + group.reduce((sum, index) => sum + signedCents(lines[index]), 0) !== 0)
186
+ continue;
187
+ for (const index of group) {
188
+ bookings.push(lines[index]);
189
+ consumed.add(index);
190
+ }
191
+ consumed.add(aggregateIndex);
192
+ }
193
+ const leftover = open();
194
+ if (leftover.length > 0) {
195
+ const detail = leftover
196
+ .map((index) => `line ${index + 1} (${lines[index].accountNumber} ${lines[index].side} ${lines[index].amountCents} against ${lines[index].counterAccount})`)
197
+ .join(', ');
198
+ const when = draft.date instanceof Date && !Number.isNaN(draft.date.getTime()) ? draft.date.toISOString().slice(0, 10) : 'undated';
199
+ throw new Error(`Journal draft "${draft.description}" (${when}${draft.reference ? `, ${draft.reference}` : ''}) cannot be written as DATEV bookings: ${detail} has no counter line`);
200
+ }
201
+ return bookings;
202
+ }
43
203
  /**
44
- * Flatten journal drafts into DATEV rows one row per line, with the line's
45
- * explicit counterAccount as Gegenkonto (no pair reconstruction).
204
+ * Convert journal drafts into DATEV rows: one row per booking after
205
+ * consolidateJournalDraftForDatev, with the line's counterAccount as
206
+ * Gegenkonto.
46
207
  */
47
208
  export function journalDraftsToDatevRows(drafts, options) {
48
209
  const rows = [];
49
210
  for (const draft of drafts) {
50
- for (const line of draft.lines) {
211
+ const policy = options?.policy ?? getDefaultPolicy(draft.skrType);
212
+ for (const line of consolidateJournalDraftForDatev(draft, policy)) {
51
213
  rows.push({
52
214
  umsatzCents: line.amountCents,
53
215
  sollHaben: line.side === 'debit' ? 'S' : 'H',
@@ -67,6 +229,15 @@ export function journalDraftsToDatevRows(drafts, options) {
67
229
  }
68
230
  return rows;
69
231
  }
232
+ /** Field 20 links a document image: `BEDI "GUID"`, quotes doubled inside the CSV field. */
233
+ function formatBelegLink(value) {
234
+ const guid = value
235
+ .trim()
236
+ .replace(/^BEDI\s*/iu, '')
237
+ .replace(/^"+|"+$/gu, '')
238
+ .replace(/"/g, "'");
239
+ return `"BEDI ""${guid}"""`;
240
+ }
70
241
  function rowToFields(row) {
71
242
  const fields = new Array(DATEV_COLUMN_COUNT).fill('');
72
243
  fields[0] = formatAmount(row.umsatzCents); // 1 Umsatz
@@ -82,7 +253,7 @@ function rowToFields(row) {
82
253
  fields[11] = quote(row.belegfeld2, DATEV_MAX_BELEGFELD); // 12
83
254
  fields[13] = quote(row.buchungstext, DATEV_MAX_BUCHUNGSTEXT); // 14 Buchungstext
84
255
  if (row.belegLink)
85
- fields[19] = quote(row.belegLink); // 20 Beleglink
256
+ fields[19] = formatBelegLink(row.belegLink); // 20 Beleglink
86
257
  if (row.kost1)
87
258
  fields[36] = quote(row.kost1); // 37 KOST1
88
259
  if (row.kost2)
@@ -126,4 +297,4 @@ export function writeExtfBuchungsstapel(rows, header) {
126
297
  const filename = `EXTF_Buchungsstapel_${formatYyyymmdd(header.dateFrom)}_${formatYyyymmdd(header.dateTo)}.csv`;
127
298
  return { csv, filename };
128
299
  }
129
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29yZS5kYXRldi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2NvcmUvY29yZS5kYXRldi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7O0dBU0c7QUFDSCxPQUFPLEtBQUssT0FBTyxNQUFNLG1CQUFtQixDQUFDO0FBRTdDLE9BQU8sRUFBRSxxQkFBcUIsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ2pFLE9BQU8sRUFBRSxtQkFBbUIsRUFBRSxzQkFBc0IsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBRWpGLE1BQU0sRUFBRSxhQUFhLEVBQUUsR0FBRyxPQUFPLENBQUMsV0FBVyxDQUFDO0FBRTlDLE1BQU0sQ0FBQyxNQUFNLGtCQUFrQixHQUFHLEdBQUcsQ0FBQztBQUN0QyxNQUFNLENBQUMsTUFBTSx3QkFBd0IsR0FBRyxFQUFFLENBQUM7QUFrQzNDLFNBQVMsR0FBRyxDQUFDLEtBQWEsRUFBRSxLQUFhO0lBQ3ZDLE9BQU8sTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLFFBQVEsQ0FBQyxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7QUFDNUMsQ0FBQztBQUVELFNBQVMsY0FBYyxDQUFDLElBQVU7SUFDaEMsT0FBTyxHQUFHLElBQUksQ0FBQyxjQUFjLEVBQUUsR0FBRyxHQUFHLENBQUMsSUFBSSxDQUFDLFdBQVcsRUFBRSxHQUFHLENBQUMsRUFBRSxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUMsSUFBSSxDQUFDLFVBQVUsRUFBRSxFQUFFLENBQUMsQ0FBQyxFQUFFLENBQUM7QUFDakcsQ0FBQztBQUVELFNBQVMsVUFBVSxDQUFDLElBQVU7SUFDNUIsT0FBTyxHQUFHLEdBQUcsQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQyxXQUFXLEVBQUUsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUN6RSxDQUFDO0FBRUQsU0FBUyxl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
300
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29yZS5kYXRldi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3RzL2NvcmUvY29yZS5kYXRldi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7Ozs7Ozs7Ozs7Ozs7O0dBZUc7QUFDSCxPQUFPLEtBQUssT0FBTyxNQUFNLG1CQUFtQixDQUFDO0FBRTdDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsRUFBdUIsTUFBTSx5QkFBeUIsQ0FBQztBQUNwRyxPQUFPLEVBQUUscUJBQXFCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUNqRSxPQUFPLEVBQUUsbUJBQW1CLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUVqRixNQUFNLEVBQUUsYUFBYSxFQUFFLGFBQWEsRUFBRSxHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUM7QUFFN0QsTUFBTSxDQUFDLE1BQU0sa0JBQWtCLEdBQUcsR0FBRyxDQUFDO0FBQ3RDLE1BQU0sQ0FBQyxNQUFNLHdCQUF3QixHQUFHLEVBQUUsQ0FBQztBQW1DM0MsU0FBUyxHQUFHLENBQUMsS0FBYSxFQUFFLEtBQWE7SUFDdkMsT0FBTyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsUUFBUSxDQUFDLEtBQUssRUFBRSxHQUFHLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBRUQsU0FBUyxjQUFjLENBQUMsSUFBVTtJQUNoQyxPQUFPLEdBQUcsSUFBSSxDQUFDLGNBQWMsRUFBRSxHQUFHLEdBQUcsQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLEdBQUcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxHQUFHLEdBQUcsQ0FBQyxJQUFJLENBQUMsVUFBVSxFQUFFLEVBQUUsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUNqRyxDQUFDO0FBRUQsU0FBUyxVQUFVLENBQUMsSUFBVTtJQUM1QixPQUFPLEdBQUcsR0FBRyxDQUFDLElBQUksQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLENBQUMsR0FBRyxHQUFHLENBQUMsSUFB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
package/package.json CHANGED
@@ -1,9 +1,22 @@
1
1
  {
2
2
  "name": "@fin.cx/skr",
3
- "version": "3.2.0",
3
+ "version": "3.3.1",
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
+ "exports": {
8
+ ".": {
9
+ "types": "./dist_ts/index.d.ts",
10
+ "default": "./dist_ts/index.js"
11
+ },
12
+ "./core": {
13
+ "types": "./dist_ts/core/index.d.ts",
14
+ "default": "./dist_ts/core/index.js"
15
+ },
16
+ "./dist_ts/*": "./dist_ts/*",
17
+ "./dist_ts_web/*": "./dist_ts_web/*",
18
+ "./package.json": "./package.json"
19
+ },
7
20
  "type": "module",
8
21
  "keywords": [
9
22
  "skr03",
@@ -20,7 +33,7 @@
20
33
  "license": "MIT",
21
34
  "dependencies": {
22
35
  "@fin.cx/calculation": "^1.1.1",
23
- "@fin.cx/einvoice": "6.1.1",
36
+ "@fin.cx/einvoice": "^7.0.0",
24
37
  "@push.rocks/smartdata": "^7.2.1",
25
38
  "@push.rocks/smartfile": "^13.1.3",
26
39
  "@push.rocks/smartfs": "^1.5.1",
@@ -37,10 +50,10 @@
37
50
  "@fin.cx/chartdata": "^2.1.0",
38
51
  "@fin.cx/tax": "^1.1.0",
39
52
  "@git.zone/tsbuild": "^4.4.2",
40
- "@git.zone/tsrun": "^2.0.5",
41
- "@git.zone/tstest": "^3.6.6",
53
+ "@git.zone/tsrun": "^2.0.6",
54
+ "@git.zone/tstest": "^6.0.0",
42
55
  "@push.rocks/qenv": "^6.1.4",
43
- "@types/node": "^26.1.1",
56
+ "@types/node": "^26.4.1",
44
57
  "@types/node-forge": "^1.3.14"
45
58
  },
46
59
  "repository": {
@@ -65,7 +78,7 @@
65
78
  "license"
66
79
  ],
67
80
  "scripts": {
68
- "test": "tstest test/ --verbose --logfile --timeout=60 && pnpm run test:published-types",
81
+ "test": "tstest test/ --verbose --logfile --timeout 60 && pnpm run test:published-types",
69
82
  "test:published-types": "pnpm build && pnpm exec tsc --pretty false -p test/fixtures/strict-consumer/tsconfig.json",
70
83
  "build": "tsbuild --web --node",
71
84
  "buildDocs": "tsdoc"
package/readme.md CHANGED
@@ -10,8 +10,8 @@ It is built for developers who need a programmable accounting core instead of a
10
10
  - Atomic posting via MongoDB transactions (replica set required)
11
11
  - GoBD audit trail: gapless sequence numbers, SHA-256 hash chain, immutable posted entries, Storno-only corrections
12
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)
13
+ - A pure, stateless booking core (`core` export, also importable on its own as `@fin.cx/skr/core` without loading the MongoDB ledger): 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); `journalDraftsToDatevRows` consolidates every draft into one row per booking (VAT lines folded into the gross row with the BU key, §13b / intra-EU pairs dropped, split lines against their aggregate), and document links are written as `BEDI "GUID"`
15
15
 
16
16
  ### Breaking changes in v2
17
17
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/skr',
6
- version: '3.2.0',
6
+ version: '3.3.1',
7
7
  description: 'SKR03 and SKR04 German accounting standards for double-entry bookkeeping'
8
8
  }
@@ -7,13 +7,20 @@
7
7
  * STRING with CRLF; persisting it as CP1252 is the IO layer's job.
8
8
  * One manual import into real DATEV per release candidate remains the final
9
9
  * conformance gate (readme.plan.md, M0).
10
+ *
11
+ * A DATEV row is one Buchungssatz: the amount is booked on Konto with the
12
+ * Soll/Haben mark and on Gegenkonto with the opposite side. A journal draft
13
+ * is a balanced set of lines, so it is consolidated into bookings before it
14
+ * is written (see journalDraftsToDatevRows); writing one row per line would
15
+ * book every draft twice on import.
10
16
  */
11
17
  import * as plugins from './core.plugins.js';
12
- import type { IJournalDraft, TBuKey, TCents } from './core.types.js';
18
+ import type { IJournalDraft, IJournalLineDraft, TBuKey, TCents, TLineSide } from './core.types.js';
19
+ import { getDefaultPolicy, isAutomatikAccount, type IAccountPolicy } from './core.accountpolicy.js';
13
20
  import { DATEV_COLUMN_CAPTIONS } from './core.datev.captions.js';
14
21
  import { DATEV_MAX_BELEGFELD, DATEV_MAX_BUCHUNGSTEXT } from './core.validate.js';
15
22
 
16
- const { centsToString } = plugins.calculation;
23
+ const { allocateCents, centsToString } = plugins.calculation;
17
24
 
18
25
  export const DATEV_COLUMN_COUNT = 125;
19
26
  export const DATEV_HEADER_FIELD_COUNT = 31;
@@ -28,6 +35,7 @@ export interface IDatevBuchungsstapelRow {
28
35
  belegfeld1?: string;
29
36
  belegfeld2?: string;
30
37
  buchungstext: string;
38
+ /** document GUID; written as `BEDI "GUID"` (field 20) */
31
39
  belegLink?: string;
32
40
  kost1?: string;
33
41
  kost2?: string;
@@ -84,17 +92,189 @@ function quote(value: string, maxLength?: number): string {
84
92
  return `"${sanitized}"`;
85
93
  }
86
94
 
95
+ export interface IDatevRowsOptions {
96
+ festschreibung?: boolean;
97
+ /**
98
+ * Recognises VAT lines and Automatikkonten while consolidating; defaults to
99
+ * the default policy of each draft's chart.
100
+ */
101
+ policy?: IAccountPolicy;
102
+ }
103
+
104
+ /** Domestic tax keys whose row carries the gross amount; DATEV computes the VAT. */
105
+ const VAT_RATE_OF_KEY: Partial<Record<TBuKey, number>> = { '9': 19, '8': 7, '3': 19, '2': 7 };
106
+
107
+ function isVatAccount(policy: IAccountPolicy, accountNumber: string): boolean {
108
+ return Object.values(policy.vat).includes(accountNumber);
109
+ }
110
+
111
+ /** Rate of a domestic VAT account; intra-EU and §13b accounts only ever appear as pairs. */
112
+ function vatRateOfAccount(policy: IAccountPolicy, accountNumber: string): number | undefined {
113
+ const { vat } = policy;
114
+ if (accountNumber === vat.inputStandard || accountNumber === vat.outputStandard) return 19;
115
+ if (accountNumber === vat.inputReduced || accountNumber === vat.outputReduced) return 7;
116
+ return undefined;
117
+ }
118
+
119
+ function signedCents(line: IJournalLineDraft): TCents {
120
+ return line.side === 'debit' ? line.amountCents : -line.amountCents;
121
+ }
122
+
123
+ function mirrors(left: IJournalLineDraft, right: IJournalLineDraft): boolean {
124
+ return (
125
+ left.counterAccount === right.accountNumber &&
126
+ right.counterAccount === left.accountNumber &&
127
+ signedCents(left) + signedCents(right) === 0
128
+ );
129
+ }
130
+
131
+ /** Keys that make DATEV compute a self-assessed VAT pair (input against output VAT). */
132
+ function selfAssessedKeysOf(policy: IAccountPolicy, accountA: string, accountB: string): TBuKey[] | undefined {
133
+ if (accountA === accountB) return undefined;
134
+ const reverseCharge = [policy.vat.reverseChargeInput, policy.vat.reverseChargeOutput];
135
+ const intraEu = [policy.vat.intraEuAcqInput, policy.vat.intraEuAcqOutput];
136
+ if (reverseCharge.includes(accountA) && reverseCharge.includes(accountB)) return ['94', '91'];
137
+ if (intraEu.includes(accountA) && intraEu.includes(accountB)) return ['19', '18'];
138
+ return undefined;
139
+ }
140
+
141
+ /**
142
+ * Consolidate one balanced draft into the lines that become DATEV bookings.
143
+ *
144
+ * 1. VAT lines. A §13b / intra-EU pair (input and output VAT booked against
145
+ * each other) is dropped when a base line of the draft carries the key
146
+ * that makes DATEV compute both sides (94/91, 19/18); without such a key
147
+ * the pair stays and becomes one explicit booking. Domestic VAT lines are
148
+ * folded, per Gegenkonto and side, into the base lines whose BU key
149
+ * carries their rate (9/8, 3/2), sharing the VAT in proportion; a VAT
150
+ * line without a keyed base is folded into an Automatikkonto base (key
151
+ * 40 becomes '') only when it is the single remaining VAT line and that
152
+ * base is the single Automatikkonto candidate, because the policy does
153
+ * not know the rate of an Automatikkonto. Everything else stays an
154
+ * explicit booking on the tax account, next to a base that keeps key 40.
155
+ * 2. Mirror lines (A against B and B against A with the same amount) become
156
+ * one booking, taken from the line with a BU key, else from the debit line.
157
+ * 3. Split lines against one aggregate line (their signed sum cancels the
158
+ * aggregate) become one booking each; the aggregate line is dropped.
159
+ * Anything left over throws: writing it would double the booking on import.
160
+ */
161
+ export function consolidateJournalDraftForDatev(
162
+ draft: IJournalDraft,
163
+ policy: IAccountPolicy,
164
+ ): IJournalLineDraft[] {
165
+ const lines: IJournalLineDraft[] = draft.lines
166
+ .map((line) =>
167
+ line.amountCents < 0
168
+ ? { ...line, amountCents: -line.amountCents, side: (line.side === 'debit' ? 'credit' : 'debit') as TLineSide }
169
+ : { ...line },
170
+ )
171
+ .filter((line) => line.amountCents !== 0);
172
+ const consumed = new Set<number>();
173
+ const open = (): number[] => lines.map((_, index) => index).filter((index) => !consumed.has(index));
174
+ const isVat = (index: number): boolean => isVatAccount(policy, lines[index].accountNumber);
175
+
176
+ // 1a self-assessed VAT pairs
177
+ for (const i of open()) {
178
+ if (consumed.has(i) || !isVat(i)) continue;
179
+ for (const j of open()) {
180
+ if (j <= i || !isVat(j) || !mirrors(lines[i], lines[j])) continue;
181
+ const keys = selfAssessedKeysOf(policy, lines[i].accountNumber, lines[j].accountNumber);
182
+ if (keys && open().some((k) => !isVat(k) && keys.includes(lines[k].buKey))) {
183
+ consumed.add(i);
184
+ consumed.add(j);
185
+ }
186
+ break;
187
+ }
188
+ }
189
+ // 1b domestic VAT lines folded into their base lines, per Gegenkonto and side
190
+ const fold = (vatIndex: number, bases: number[], clearKey: boolean): void => {
191
+ const shares = allocateCents(lines[vatIndex].amountCents, bases.map((j) => lines[j].amountCents));
192
+ bases.forEach((j, position) => {
193
+ lines[j] = { ...lines[j], amountCents: lines[j].amountCents + shares[position], buKey: clearKey ? '' : lines[j].buKey };
194
+ });
195
+ consumed.add(vatIndex);
196
+ };
197
+ const groups = new Map<string, number[]>();
198
+ for (const i of open()) {
199
+ if (!isVat(i) || vatRateOfAccount(policy, lines[i].accountNumber) === undefined) continue;
200
+ const key = `${lines[i].counterAccount}|${lines[i].side}`;
201
+ groups.set(key, [...(groups.get(key) ?? []), i]);
202
+ }
203
+ for (const [key, vatLines] of groups) {
204
+ const bases = open().filter((j) => !isVat(j) && `${lines[j].counterAccount}|${lines[j].side}` === key);
205
+ const remaining: number[] = [];
206
+ for (const i of vatLines) {
207
+ const rate = vatRateOfAccount(policy, lines[i].accountNumber);
208
+ const keyed = bases.filter((j) => VAT_RATE_OF_KEY[lines[j].buKey] === rate);
209
+ if (keyed.length === 0) {
210
+ remaining.push(i);
211
+ continue;
212
+ }
213
+ fold(i, keyed, false);
214
+ }
215
+ const automatik = bases.filter(
216
+ (j) => isAutomatikAccount(policy, lines[j].accountNumber) && (lines[j].buKey === '' || lines[j].buKey === '40'),
217
+ );
218
+ if (remaining.length === 1 && automatik.length === 1) {
219
+ fold(remaining[0], automatik, true);
220
+ }
221
+ }
222
+
223
+ const bookings: IJournalLineDraft[] = [];
224
+ // 2 mirror lines
225
+ for (const i of open()) {
226
+ if (consumed.has(i)) continue;
227
+ for (const j of open()) {
228
+ if (j <= i) continue;
229
+ if (mirrors(lines[i], lines[j])) {
230
+ const keep = lines[i].buKey !== '' ? i : lines[j].buKey !== '' ? j : lines[i].side === 'debit' ? i : j;
231
+ bookings.push(lines[keep]);
232
+ consumed.add(i);
233
+ consumed.add(j);
234
+ break;
235
+ }
236
+ }
237
+ }
238
+ // 3 split lines against their aggregate line
239
+ for (const aggregateIndex of open()) {
240
+ if (consumed.has(aggregateIndex)) continue;
241
+ const aggregate = lines[aggregateIndex];
242
+ const group = open().filter((index) => index !== aggregateIndex && lines[index].counterAccount === aggregate.accountNumber);
243
+ if (group.length === 0) continue;
244
+ if (signedCents(aggregate) + group.reduce((sum, index) => sum + signedCents(lines[index]), 0) !== 0) continue;
245
+ for (const index of group) {
246
+ bookings.push(lines[index]);
247
+ consumed.add(index);
248
+ }
249
+ consumed.add(aggregateIndex);
250
+ }
251
+
252
+ const leftover = open();
253
+ if (leftover.length > 0) {
254
+ const detail = leftover
255
+ .map((index) => `line ${index + 1} (${lines[index].accountNumber} ${lines[index].side} ${lines[index].amountCents} against ${lines[index].counterAccount})`)
256
+ .join(', ');
257
+ const when = draft.date instanceof Date && !Number.isNaN(draft.date.getTime()) ? draft.date.toISOString().slice(0, 10) : 'undated';
258
+ throw new Error(
259
+ `Journal draft "${draft.description}" (${when}${draft.reference ? `, ${draft.reference}` : ''}) cannot be written as DATEV bookings: ${detail} has no counter line`,
260
+ );
261
+ }
262
+ return bookings;
263
+ }
264
+
87
265
  /**
88
- * Flatten journal drafts into DATEV rows one row per line, with the line's
89
- * explicit counterAccount as Gegenkonto (no pair reconstruction).
266
+ * Convert journal drafts into DATEV rows: one row per booking after
267
+ * consolidateJournalDraftForDatev, with the line's counterAccount as
268
+ * Gegenkonto.
90
269
  */
91
270
  export function journalDraftsToDatevRows(
92
271
  drafts: IJournalDraft[],
93
- options?: { festschreibung?: boolean },
272
+ options?: IDatevRowsOptions,
94
273
  ): IDatevBuchungsstapelRow[] {
95
274
  const rows: IDatevBuchungsstapelRow[] = [];
96
275
  for (const draft of drafts) {
97
- for (const line of draft.lines) {
276
+ const policy = options?.policy ?? getDefaultPolicy(draft.skrType);
277
+ for (const line of consolidateJournalDraftForDatev(draft, policy)) {
98
278
  rows.push({
99
279
  umsatzCents: line.amountCents,
100
280
  sollHaben: line.side === 'debit' ? 'S' : 'H',
@@ -115,6 +295,16 @@ export function journalDraftsToDatevRows(
115
295
  return rows;
116
296
  }
117
297
 
298
+ /** Field 20 links a document image: `BEDI "GUID"`, quotes doubled inside the CSV field. */
299
+ function formatBelegLink(value: string): string {
300
+ const guid = value
301
+ .trim()
302
+ .replace(/^BEDI\s*/iu, '')
303
+ .replace(/^"+|"+$/gu, '')
304
+ .replace(/"/g, "'");
305
+ return `"BEDI ""${guid}"""`;
306
+ }
307
+
118
308
  function rowToFields(row: IDatevBuchungsstapelRow): string[] {
119
309
  const fields = new Array<string>(DATEV_COLUMN_COUNT).fill('');
120
310
  fields[0] = formatAmount(row.umsatzCents); // 1 Umsatz
@@ -126,7 +316,7 @@ function rowToFields(row: IDatevBuchungsstapelRow): string[] {
126
316
  if (row.belegfeld1) fields[10] = quote(row.belegfeld1, DATEV_MAX_BELEGFELD); // 11
127
317
  if (row.belegfeld2) fields[11] = quote(row.belegfeld2, DATEV_MAX_BELEGFELD); // 12
128
318
  fields[13] = quote(row.buchungstext, DATEV_MAX_BUCHUNGSTEXT); // 14 Buchungstext
129
- if (row.belegLink) fields[19] = quote(row.belegLink); // 20 Beleglink
319
+ if (row.belegLink) fields[19] = formatBelegLink(row.belegLink); // 20 Beleglink
130
320
  if (row.kost1) fields[36] = quote(row.kost1); // 37 KOST1
131
321
  if (row.kost2) fields[37] = quote(row.kost2); // 38 KOST2
132
322
  if (row.festschreibung !== undefined) fields[113] = row.festschreibung ? '1' : '0'; // 114
package/readme.hints.md DELETED
@@ -1,103 +0,0 @@
1
- # Project Readme Hints
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
- ## Chart rebase (2026-07-09, v3)
18
-
19
- - The built-in SKR03/04 charts are now GENERATED from @fin.cx/chartdata >= 2.1.0
20
- (`tsx tools/generate-chart-data.ts`) — the v1 hand-written charts were an
21
- invented hybrid layout and are gone. Notable corrections: SKR03 revenue lives
22
- in class 8 (8400/8300), expenses in class 4 (default 4900 — 4980 is
23
- Mietleasing!), VSt/USt rate accounts fixed (1571/1771 = 7 %); SKR04 bank is
24
- 1800, Kasse 1600, controls 1200/3300, default expense 6300.
25
- - Account.isAutomaticAccount: SKR04 controls corrected to 1200/3300.
26
-
27
- ## M0 Phase 2 — stateless core (2026-07-09)
28
-
29
- - `ts/core/` is the pure booking core (exported as `core` from the package index):
30
- types, BU keys, tax-scenario cascade, account policies, recipes, validation,
31
- DATEV EXTF writer, hash chain. Hard rule: core imports only @fin.cx/calculation
32
- (+ smarthash for SHA-256) — no smartdata/fs/network.
33
- - **BU-key corrections vs v1/finance.plus** (cross-checked 2026-07-09): 18/19 =
34
- steuerpflichtiger innergemeinschaftlicher Erwerb 7%/19% (§1a UStG); 91/94 =
35
- Leistungsempfänger schuldet die Steuer 7%/19% (§13b UStG); 40 = Aufhebung der
36
- Automatik. v1's `skr.postingkeys.ts` (numeric keys with partly different meanings)
37
- is superseded by `ts/core/core.bukeys.ts`. Official DATEV table (Hilfe-Center doc
38
- 0904313) is portal-gated — final verification = manual DATEV import per RC.
39
- - **DATEV EXTF v13 facts** (verified against a DATEV-importable reference file):
40
- 125 data columns, 31 header fields, field 114 = Festschreibung, decimal-comma
41
- amounts, Belegdatum TTMM, text fields quoted, CRLF; file encoding must be CP1252
42
- (conversion happens at the IO layer, core emits a JS string).
43
- `ts/core/core.datev.captions.ts` is generated from that reference — do not edit.
44
- - Automatik convention in recipes: a line on an Automatikkonto carries key '' when
45
- the entry has no explicit VAT lines, and key '40' when explicit VAT lines exist
46
- (automatism must be cancelled so DATEV import doesn't double the tax).
47
- - PROVISIONAL policy accounts (verify in M-A): reduced-rate Skonto accounts
48
- (3731/8731, 5731/4731) and rounding-diff accounts (2700/2300, 4830/6300).
49
-
50
- ## Current Status (2025-10-27)
51
-
52
- ### Test Results
53
- ✅ **ALL 65/65 TESTS PASSING** (100%)
54
-
55
- ### Recent Fixes
56
-
57
- #### Fixed: SKR04 Bug (Account 3300 Misclassification)
58
- **Problem**: Account 3300 was incorrectly hardcoded as an automatic account for SKR04
59
- **Root Cause**: Bug in `ts/skr.classes.account.ts:192` - account 3300 is "Fahrzeugkosten" (vehicle costs), NOT an automatic account
60
- **Solution**:
61
- 1. Removed 3300 from automatic accounts list in `isAutomaticAccount()` method
62
- 2. Updated test.skr04.ts to use timestamped database names to avoid conflicts
63
- **Files Changed**:
64
- - `ts/skr.classes.account.ts` - Fixed automatic account detection
65
- - `test/test.skr04.ts` - Added timestamp to database name
66
-
67
- **Result**: ✅ All SKR04 tests now passing (jahresabschluss.skr04 + basic SKR04 tests)
68
-
69
- ### Architecture Notes
70
-
71
- #### VAT Validation Logic (Recent Changes)
72
- - **skr.classes.journalentry.ts:224-273**: Detects VAT lines in entries to enable smart validation
73
- - **skr.postingkeys.ts:87-100**: Exempts VAT accounts and debtor/creditor accounts from VAT amount requirements
74
- - **Rationale**: VAT accounts ARE the VAT; settlement transactions don't need VAT details again
75
-
76
- #### Posting Key Usage Pattern
77
- - **Tax-free operations** (key 40): Internal adjustments, depreciation, closing entries
78
- - **VAT operations** (keys 3, 8, 9, 19, 94): Customer/supplier transactions
79
- - **Best practice**: Use posting key 40 for non-VAT lines in mixed entries
80
-
81
- #### Account Structure
82
- - **Automatic accounts**: Cannot be posted to directly (1400 Debtors, 1600 Creditors, 3300 Bank)
83
- - **Personal accounts**: Created in ranges 10000-69999 (debtors), 70000-99999 (creditors)
84
- - **System enforces**: Must use personal variants instead of automatic accounts
85
-
86
- ### Validation Pipeline
87
- 1. **Line-level**: Posting key required, account exists, VAT rules
88
- 2. **Posting key level**: VAT amount requirements (with exemptions)
89
- 3. **Consistency level**: No mixing tax-free and taxed (unless intentional)
90
- 4. **Balance level**: Debits must equal credits (0.01 tolerance)
91
-
92
- ### Test Coverage
93
- - 65 test cases covering full accounting cycle
94
- - Complete Jahresabschluss (annual closing) workflow in SKR03
95
- - Report generation (Trial Balance, Income Statement, Balance Sheet)
96
- - Transaction reversal and audit trails
97
- - DATEV posting key validation
98
-
99
- ### Dependencies
100
- - MongoDB via @push.rocks/smartdata for persistence
101
- - TypeScript 5.8.3 with strict mode
102
- - @git.zone/tstest for testing framework
103
- - @push.rocks/smartexpect for assertions
package/readme.plan.md DELETED
@@ -1,264 +0,0 @@
1
- # fin.cx Accounting Program — "No External Tax Accountant"
2
-
3
- Program-level plan anchored in @fin.cx/skr. Supersedes the original build checklist
4
- (that scaffold was stale; the package is long past it — see changelog).
5
- Status source of truth for current findings: `readme.hints.md`.
6
-
7
- ## Context
8
-
9
- Run German company books end-to-end — bookkeeping, VAT filings, year-end — without an
10
- external Steuerberater for the mechanical work. skr v1.x is a solid bookkeeping core but
11
- not filing-grade: float money with 0.01-epsilon balance checks, non-atomic 3-write
12
- posting, no GoBD Festschreibung (mutable docs, non-gapless numbering, no audit trail),
13
- 6 BU keys, ~110-account chart subsets, non-importable DATEV export stub, no stateless
14
- booking API, and zero filing coverage (ELSTER/E-Bilanz/HGB statements/OPOS/
15
- Anlagenbuchhaltung/bank reconciliation).
16
-
17
- **Critical path:** skr v2 hardening → complete FiBu → UStVA via ELSTER (removes monthly
18
- StB dependency) → E-Bilanz + HGB year-end (removes annual dependency).
19
-
20
- ## Decisions (assumptions where user confirmation pending)
21
-
22
- 1. Full program roadmap; each milestone gets its own detailed plan when it starts.
23
- 2. Own company books first; APIs product-grade; multi-tenant/RBAC deferred.
24
- 3. ELSTER owned: ERiC native lib via @git.zone/tsrust + @push.rocks/smartrust →
25
- `@fin.cx/elster`.
26
- 4. Full SKR03/04 + E-Bilanz taxonomy mapping from public sources (no DATEV license);
27
- legal review before public release.
28
- 5. Payroll OUT of scope (integrate provider, import GL postings).
29
- 6. Statutory WP audit (§316 HGB) out of scope by law. Goal = "StB consulted on judgment
30
- calls only".
31
-
32
- ## Calendar anchors (from 2026-07-09, FY = calendar year)
33
-
34
- UStVA parallel-run 09–11/2026 → first live self-filed UStVA 12/2026 or 01/2027 →
35
- FY2026 Jahresabschluss self-prepared H1 2027 (one paid StB review as backstop;
36
- KSt/GewSt/UStJE deadline ~31 Jul 2027; Offenlegung 31 Dec 2027) → fully solo from FY2027.
37
-
38
- **Week-0 actions (hard external clocks, user):**
39
- 1. ELSTER developer registration (Bayerisches LfSt) → Hersteller-ID + ERiC access
40
- (days–4 wks).
41
- 2. Confirm/obtain ELSTER Organisationszertifikat for TVC GmbH (post, 1–2 wks).
42
- 3. Bundesanzeiger/Unternehmensregister publisher registration.
43
-
44
- ## Milestones
45
-
46
- ### M0 — skr v2.0.0 core hardening (foundation; blocks B, C2, D, E1)
47
-
48
- After **@fin.cx/calculation v1.1.0** (cents module — RELEASED 2026-07-09 ✓).
49
-
50
- - **Money:** ledger amounts = integer cents (`TCents`); exact `===` balance checks.
51
- Fractional math via calculation cents module (`vatFromNetCents`, `splitGrossCents`
52
- with net+vat===gross guarantee, `allocateCents` largest-remainder, `convertCents`,
53
- `centsFromFloat` with drift guard). ROUND_HALF_UP = kaufmännisches Runden.
54
- - **Stateless core `ts/core/`** (imports only @fin.cx/calculation + each other):
55
- `core.types.ts` (IJournalDraft/IJournalLineDraft with explicit per-line Gegenkonto;
56
- TBuKey as string union; TTaxScenario), `core.bukeys.ts` (full BU table from official
57
- DATEV Steuerschlüssel docs — v1 table + finance.plus logic both suspect, do not copy
58
- blind), `core.taxscenario.ts` (confidence-scored cascade), `core.accountpolicy.ts`
59
- (IAccountPolicy + SKR03/04 defaults incl. Geldtransit, FX gain/loss, rounding-diff),
60
- `core.recipes.ts` (pure builders: vendor/customer invoice incl. reverse-charge +
61
- intra-EU paired VAT, bank payment clearing with Skonto, internal transfer via
62
- Geldtransit, suspense book/resolve, FX difference lines), `core.validate.ts`,
63
- `core.datev.ts` (full Buchungsstapel row model, EXTF 700, buSchluessel on every row),
64
- `core.hash.ts` (canonical serialization + SHA-256 chain).
65
- - **Atomic posting:** smartdata 7.1.7 sessions (verified supported). Mongo MUST be a
66
- replica set; `SkrApi.initialize()` probes and fails hard. `JournalPoster`
67
- (skr.posting.ts): pre-flight → withTransaction: period-lock re-check, gapless sequence
68
- via $inc findOneAndUpdate, hash chain CAS on head. JournalEntry = single source of
69
- truth (v1 Transaction explosion removed; collection frozen as legacy read model).
70
- Balances recomputed via aggregation (skr.balances.ts); Account.updateBalance deleted
71
- from posting path.
72
- - **Festschreibung:** posted == immutable (corrections via Storno only). JournalEntry +=
73
- sequenceNumber (gapless per skrType+fiscalYear), prevHash/entryHash, finalizedAt,
74
- status posted|finalized, schemaVersion 2. beforeSave()/delete() throw on mutation of
75
- hashed docs; reversedBy derived (not stored). LedgerPeriod doc + festschreibePeriod();
76
- closePeriod() posts P&L-zero entries then festschreibt. verifyHashChain() in
77
- skr.verify.ts. SecurityManager: keep CAdES-B; RFC-3161 stub → loud NotImplementedError;
78
- fix require('crypto') ESM bug in skr.export.ts.
79
- - **Migration** (idempotent, offline, float fields kept): M001 cents backfill (abort on
80
- drift > 0.5 cent) → M002 unify legacy Transactions into synthesized JournalEntries →
81
- M003 sequence + chain assignment + operator-confirmed festschreiben. JSON report.
82
- - **Phases (status 2026-07-09):** (0) replica-set env ✓ (gitzone services ≥2.23.0),
83
- EXTF-700/v13 layout ✓ (verified vs DATEV-importable reference; official
84
- Steuerschlüssel doc 0904313 portal-gated → manual import check per RC remains) →
85
- (1) calculation v1.1.1 ✓ released → (2) stateless core ✓ → (3) persistence
86
- hardening ✓ → (4) facade/reports rewiring + security descope ✓ →
87
- (5) migrations + docs + v2.0.0 release ✓. **M0 COMPLETE** except the standing
88
- per-RC manual DATEV import validation.
89
- - **Tests:** recipe goldens (both charts, exact cents), byte-exact EXTF files, property
90
- tests (seeded PRNG), concurrency (N parallel posts → gapless, chain verifies, exact
91
- trial balance), locked-period race, immutability throws, txn-abort no-partials,
92
- migration round-trip; existing 65 tests green minus documented divergences; manual
93
- DATEV import check per RC.
94
-
95
- ### M-A — `@fin.cx/chartdata` (new pkg; parallel to M0)
96
-
97
- **Status 2026-07-09: A1 seed COMPLETE locally** (repo at
98
- `/mnt/data/foss.global/fin.cx/chartdata`, committed, 12/12 tests): year-keyed
99
- SKR03/04 2026 datasets (~125 accounts each incl. all fiscally-relevant VAT/
100
- Skonto/igE/§13b/EUSt accounts missing from skr v1), Automatik functions +
101
- allowed BU keys, account→UStVA-Kennzahl mappings, scenario Kennzahlen table
102
- (89/93, 46/47, 84/85, 41, 43, 81, 86, 60, 66/61/67/62), coverage introspection,
103
- provenance per record, reproducible seed pipeline in tools/.
104
- **RELEASED as @fin.cx/chartdata v1.1.0** (2026-07-09, verdaccio + npmjs).
105
- **A1 REBASE COMPLETE (2026-07-09):** chartdata v2.1.0 = curated oracle-verified
106
- datasets (91 SKR03 / 78 SKR04 accounts, verification-only oracle, no foreign
107
- strings); skr v3.0.0 aligned: charts GENERATED from chartdata
108
- (tools/generate-chart-data.ts), policies corrected (expense default 4900/6300,
109
- SKR04 controls 1200/3300, bank 1800/Kasse 1600), all 98 tests remapped to real
110
- DATEV numbers and green. A2 HGB §266/§275 mappings DONE
111
- (chartdata v2.2.0: every non-statistical account carries hgb266Position or
112
- hgb275Position, validated against the statute Gliederung trees in
113
- chartdata.hgb.ts and verified against the oracle's position tags — 11
114
- documented modeling exceptions where German textbook treatment differs, e.g.
115
- USt liabilities in Passiva C.8). NEXT: A3 E-Bilanz taxonomy; D1 can now build
116
- the §266/§275 Gliederung from chartdata.
117
-
118
- **A1 cross-diff done (2026-07-09):** Odoo l10n_de oracle proves the skr-v1 seed
119
- charts are an invented hybrid (~50/125 SKR03 + ~66/130 SKR04 divergences);
120
- ALL fiscal A1 additions verified clean. Rate-label fixes released (v1.1.1:
121
- 1570/1571/1770/1771). NEXT: A1 seed-base rebase (clean-room base set, remove
122
- invented blocks, align skr built-in charts + tests — coupled change), then A2
123
- (HGB mappings; Odoo tags as verification oracle), then A3. Details:
124
- chartdata/tools/a1-crossdiff-report.md.
125
-
126
- Year-versioned full SKR03/04: account master, Automatikkonto functions + allowed BU keys,
127
- UStVA-Kennzahl / HGB §266/§275 / E-Bilanz (de-gaap-ci) mappings. Pure data + lookup API;
128
- `getMappingCoverage()` fails loudly. Pipeline in unpublished tools/ (public sources,
129
- provenance per record). A1 chart+Automatik+Kennzahlen → A2 HGB → A3 E-Bilanz taxonomy
130
- (esteuer.de). Own-books account coverage first. Verify: schema checks, cross-diff vs
131
- Kivitendo/Odoo l10n_de (reviewed, not copied), SKR PDF spot checks, ERiC as E-Bilanz
132
- oracle. Legal review before public MIT release.
133
-
134
- ### M-B — Subledgers + reconciliation
135
-
136
- **B1 status 2026-07-09: package layer RELEASED** — @fin.cx/camt v1.1.0
137
- (CAMT.052/053/054, exact cents, SEPA extraction, statement-internal balance
138
- check) and @fin.cx/bankrec v1.1.0 (canonical signed-cent transactions, camt +
139
- mt940 adapters, cross-statement continuity control, confidence-scored matching
140
- engine: direction filter, Skonto window, tie-forces-review, no double
141
- settlement). @fin.cx/mt940parser v1.1.1 published for the first time
142
- (modernized: smartconfig v2, direct npm publish, pnpm pinned). Remaining B1:
143
- regression run against real TVC bank statements (user to provide), then wire
144
- auto/review/unmatched decisions to skr recipes in the consuming app.
145
-
146
- B1 `@fin.cx/camt` (CAMT.052/053/054; MT940 = legacy) + `@fin.cx/bankrec` (canonical
147
- decimal bank txn with SEPA refs, adapters, balance-chain continuity, confidence-scored
148
- matching, auto-post above threshold, review queue below).
149
-
150
- **B2 status 2026-07-09: RELEASED in skr v3.1.0** — open items DERIVED from the
151
- hashed v2 ledger (no drifting side-state): grouped by (personal account,
152
- belegfeld1), partial settlements book only the paid amount, full settlements
153
- with Skonto+VSt correction via the core recipe (ordinary audited postings),
154
- aging buckets, dunning metadata (OposMeta, process state only), control totals
155
- = aggregated personal ranges (feed HGB B.II.1/C.4 in D1). Dunning LETTERS out
156
- of scope as planned. A2 also DONE (chartdata v2.2.0 HGB mappings).
157
-
158
- B2 original scope: OPOS inside skr (ts/opos/:
159
- masters, open items, clearItems with Skonto+VAT correction, aging, dunning states;
160
- invariant: subledger == control account).
161
-
162
- **B3 status 2026-07-09: RELEASED as @fin.cx/assets v1.1.0** — pure-function
163
- Anlagenbuchhaltung: exact-cent AfA schedules (linear + degressive with switch,
164
- pro rata temporis), GWG immediate/Sammelposten, year-keyed policy data (GWG
165
- thresholds, degressive windows incl. Investitionsbooster 2025, BMF useful-life
166
- suggestions), disposals with Buchgewinn/-verlust, §284(3) Anlagespiegel (gross
167
- method, tie-outs tested), ledger-agnostic posting proposals for skr recipes.
168
- 12/12 tests. Remaining M-B: A3 taxonomy, then D1 consumes Anlagespiegel +
169
- getControlTotals + chartdata HGB mappings.
170
-
171
- B3 original scope: `@fin.cx/assets` (AfA schedules from BMF
172
- tables, GWG/degressive as year-keyed policy, Anlagespiegel → D1). B1 before live UStVA;
173
- B2/B3 may trail.
174
-
175
- ### M-C — `@fin.cx/elster` (ERiC via Rust bridge) — first-win lane
176
-
177
- Thin transport: validate / submitTest (Testmerker) / submitLive / confirmation PDF.
178
- Rust binary dlopens ERiC from operator-supplied ERIC_HOME (ERiC never enters npm/repo;
179
- version-compat matrix + checksum gate). Kennzahl computation in `@fin.cx/tax` (VAT module
180
- ships early). C1 bridge → C2 UStVA 3-month parallel-run (3 consecutive zero/explained-diff
181
- months required) → C2' live + E1 → C3 DFV/ZM/UStJE.
182
-
183
- **UStVA Kennzahl module RELEASED 2026-07-09 as @fin.cx/tax v1.1.0** —
184
- `computeUstva()`: pure aggregation of period account movements into UStVA
185
- Kennzahlen via chartdata mappings (81/86/41/43/48/60 bases, 66/61/67/62 taxes),
186
- form rounding done right (Bemessungsgrundlagen truncated to full euros on the
187
- period sum per Kennzahl; taxes cent-exact; output tax = form computation from
188
- truncated bases), igE/§13b bases from exact `scenarioBases` (skr taxScenario
189
- tags) with `(derived)` fallback from output-tax movements, Kz 47/85 split
190
- preserving the ledger total, Kz 83 Zahllast + Kz 39 Sondervorauszahlung,
191
- ledger-vs-form reconciliation with tolerance (default 5 EUR) and loud `issues`
192
- (unmapped revenue, unknown revenue-class accounts, undifferentiated output
193
- VAT). Soll/Ist is the CALLER's period selection; `taxationMethod` recorded on
194
- the result — open question #3 still needs answering before first filing.
195
- 12/12 tests. Remaining C2: skr-side glue (movements + scenario base extraction
196
- per period), then parallel-run diffing once ELSTER access exists.
197
-
198
- ### M-D — Year-end (`@fin.cx/jahresabschluss` + `@fin.cx/tax`, Q1–Q2 2027)
199
-
200
- **D1 CORE RELEASED 2026-07-09 as @fin.cx/jahresabschluss v1.1.0** (built early
201
- — all inputs existed): §266 Bilanz + §275 GKV GuV from chartdata mappings and
202
- exact-cent balances (loud failure on unmapped balances; Jahresergebnis derived
203
- twice and tied out on Passiva A.V; personal accounts auto-roll to B.II.1/C.4),
204
- §267/§267a size classes as year-keyed data (post-2024 thresholds + two-year
205
- rule), condensed statements for klein/kleinst. 10/10 tests. Remaining D1:
206
- Anhang skeleton, Anlagespiegel integration glue, real-data validation
207
- ("re-file the past" needs prior-year books).
208
-
209
- D1 §266/§275 Gliederung + §267 size classes + Anhang skeleton + Anlagespiegel. D3 tax
210
- engine (§4(5) EStG, §8b KStG, GewSt §8/§9 add-backs, loss carryforwards, KSt 15%+Soli,
211
- GewSt Hebesatz Bremen 460%; provisions posted back to skr). D2 E-Bilanz XBRL writer
212
- (ERiC validates locally — no heavyweight XBRL lib) via elster. D4 Offenlegung generator +
213
- checklist (semi-manual upload). Verify: "re-file the past" vs StB-filed prior year +
214
- Bescheide; tie-outs; paid StB review of first Abschluss.
215
-
216
- ### M-E — Operational readiness (thin, inside skr)
217
-
218
- E1 Verfahrensdoku generator (ships WITH first live UStVA — GoBD requires from go-live).
219
- E2 GDPdU/GoBD Z3 Datenträgerüberlassung (Beschreibungsstandard index.xml + IDEA CSVs, on
220
- BagIt export). E3 retention (10y books, 8y Belege post-2025).
221
-
222
- ## Waves
223
-
224
- 1. **W0 (now):** registrations; A1 pipeline; M0 start. ✓ calculation v1.1.0 released.
225
- 2. **W1 (Jul–Sep 2026):** M0 lands; C1; tax VAT module; A1; B1 MVP; parallel-run from
226
- 09/2026.
227
- 3. **W2 (Q4 2026):** live UStVA + E1 ⇒ monthly StB dependency removed. C3 DFV. B2/B3/A2/A3.
228
- 4. **W3 (Q1–Q2 2027):** D1→D3→D2→D4 for FY2026; StB review ⇒ fully solo from FY2027.
229
- 5. **Continuous:** E2/E3; annual maintenance ~4–8 wks/yr (ERiC Nov release = hard
230
- calendar item; taxonomy bump; form-year schemas; year-keyed tax parameters).
231
-
232
- ## Top risks
233
-
234
- 1. Incorrect live filing → parallel-run gate, re-file-the-past regression, ERiC+Testmerker
235
- CI, human sign-off, StB review yr 1.
236
- 2. ERiC licensing/platform (non-redistributable; ARM64 TBD) → ERIC_HOME + compat matrix;
237
- x86_64 first.
238
- 3. DATEV database rights on chart data → multi-source, provenance, legal review.
239
- 4. Completeness failures → balance-chain continuity as monthly-close gate; OPOS
240
- invariants.
241
- 5. Regulatory churn → year-keyed data concentrated in chartdata+elster; DATEV export as
242
- permanent escape hatch.
243
-
244
- ## Open questions (user)
245
-
246
- 1. Location of `fin-accountingbackend` repo (not on this machine; finance.plus used as
247
- reference).
248
- 2. ELSTER Organisationszertifikat present? Dauerfristverlängerung in place?
249
- 3. **Ist- oder Soll-Versteuerung (§20 UStG)?** Blocks Wave-1 VAT engine design.
250
- 4. Intra-EU B2B supplies (ZM needed)?
251
- 5. elster deployment host architecture (ERiC aarch64 TBD)?
252
- 6. chartdata public MIT immediately or after legal review?
253
- 7. Beleg archival: doclake vs skr export bundles?
254
- 8. StB cooperation for parallel-run (filed Kennzahlen + prior-year working papers)?
255
- 9. Festschreibung: posted==immutable OK, or correction window until UStVA submission?
256
- 10. RFC-3161 descope in M0 OK (loud failure instead of fake stub)?
257
- 11. One company per DB confirmed (sequences keyed skrType+fiscalYear)?
258
-
259
- ## Program gates
260
-
261
- - **Gate 1 (before live UStVA):** 3 zero/explained-diff parallel months + B1 continuity
262
- green + E1 published.
263
- - **Gate 2 (before FY2026 self-filing):** re-file-the-past matches StB filings +
264
- Bescheide; ERiC-valid E-Bilanz; StB review sign-off.