@fin.cx/skr 3.2.0 → 3.3.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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/core/core.datev.d.ts +36 -6
- package/dist_ts/core/core.datev.js +177 -6
- package/package.json +18 -5
- package/readme.md +2 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/core/core.datev.ts +197 -7
- package/readme.hints.md +0 -103
- package/readme.plan.md +0 -264
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@fin.cx/skr',
|
|
6
|
-
version: '3.
|
|
6
|
+
version: '3.3.0',
|
|
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
|
-
*
|
|
36
|
-
*
|
|
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
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
*
|
|
45
|
-
*
|
|
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
|
-
|
|
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] =
|
|
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.
|
|
3
|
+
"version": "3.3.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
|
+
"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",
|
|
@@ -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.
|
|
41
|
-
"@git.zone/tstest": "^
|
|
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.
|
|
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
|
|
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
|
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/core/core.datev.ts
CHANGED
|
@@ -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
|
-
*
|
|
89
|
-
*
|
|
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?:
|
|
272
|
+
options?: IDatevRowsOptions,
|
|
94
273
|
): IDatevBuchungsstapelRow[] {
|
|
95
274
|
const rows: IDatevBuchungsstapelRow[] = [];
|
|
96
275
|
for (const draft of drafts) {
|
|
97
|
-
|
|
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] =
|
|
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.
|