@fin.cx/chartdata 3.1.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.
@@ -1,6 +1,7 @@
1
1
  export * from './chartdata.types.js';
2
2
  export * from './chartdata.lookup.js';
3
3
  export * from './chartdata.hgb.js';
4
+ export * from './chartdata.bwa.js';
4
5
  export * from './chartdata.euer.js';
5
6
  export { SKR03_2026 } from './data/skr03.2026.js';
6
7
  export { SKR04_2026 } from './data/skr04.2026.js';
package/dist_ts/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  export * from './chartdata.types.js';
2
2
  export * from './chartdata.lookup.js';
3
3
  export * from './chartdata.hgb.js';
4
+ export * from './chartdata.bwa.js';
4
5
  export * from './chartdata.euer.js';
5
6
  export { SKR03_2026 } from './data/skr03.2026.js';
6
7
  export { SKR04_2026 } from './data/skr04.2026.js';
7
8
  export * from './chartdata.individual.js';
8
9
  export { DATEV_NUMBERS_SKR03_2026, DATEV_NUMBERS_SKR04_2026 } from './data/datevnumbers.2026.js';
9
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsdUJBQXVCLENBQUM7QUFDdEMsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLHFCQUFxQixDQUFDO0FBQ3BDLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNsRCxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDbEQsY0FBYywyQkFBMkIsQ0FBQztBQUMxQyxPQUFPLEVBQUUsd0JBQXdCLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQyJ9
10
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHNCQUFzQixDQUFDO0FBQ3JDLGNBQWMsdUJBQXVCLENBQUM7QUFDdEMsY0FBYyxvQkFBb0IsQ0FBQztBQUNuQyxjQUFjLG9CQUFvQixDQUFDO0FBQ25DLGNBQWMscUJBQXFCLENBQUM7QUFDcEMsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUNsRCxjQUFjLDJCQUEyQixDQUFDO0FBQzFDLE9BQU8sRUFBRSx3QkFBd0IsRUFBRSx3QkFBd0IsRUFBRSxNQUFNLDZCQUE2QixDQUFDIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fin.cx/chartdata",
3
- "version": "3.1.0",
3
+ "version": "3.3.0",
4
4
  "private": false,
5
5
  "description": "Year-versioned German SKR03/SKR04 chart-of-accounts datasets with Automatikkonto functions, BU-key rules, UStVA-Kennzahl and Anlage EÜR mappings",
6
6
  "main": "dist_ts/index.js",
@@ -28,10 +28,10 @@
28
28
  },
29
29
  "homepage": "https://code.foss.global/fin.cx/chartdata#readme",
30
30
  "devDependencies": {
31
- "@git.zone/tsbuild": "^5.1.2",
32
- "@git.zone/tsrun": "^3.0.2",
31
+ "@git.zone/tsbuild": "^6.0.2",
32
+ "@git.zone/tsrun": "^3.0.3",
33
33
  "@git.zone/tstest": "^7.2.0",
34
- "@types/node": "^26.6.3"
34
+ "@types/node": "^26.6.4"
35
35
  },
36
36
  "files": [
37
37
  "ts/**/*",
package/readme.md CHANGED
@@ -38,6 +38,10 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
38
38
  of a payroll until they are paid, the company-car benefit and the levy
39
39
  refunds, and the clearing account, one account per role, found with
40
40
  `getPayrollAccount`
41
+ - Expense accounts with their expense role (`expenseRole`): an employee's
42
+ travel, the business's other expenses an employee lays out and claims back,
43
+ and the Kreditkartenabrechnung, one account per role, found with
44
+ `getExpenseAccount`
41
45
  - DATEV's mark "HB" (`commercialOnly`): an account posted in the commercial
42
46
  balance sheet only
43
47
  - The tax rule an income or expense account is kept for (`taxRule`):
@@ -54,6 +58,8 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
54
58
  checked against the numbers the DATEV Kontenrahmen assigns and reserves
55
59
  (`getDatevNumbers`)
56
60
  - Coverage introspection that fails loudly on mapping gaps
61
+ - Report metadata: an independently defined standard KER (`bwaLine`, `BWA_LINES`),
62
+ balance-side rules (`balanceRule`), and explicit technical exclusions (`hgb266Exclusion`)
57
63
  - Provenance on every record
58
64
 
59
65
  ## Installation
@@ -217,6 +223,51 @@ Nebenkosten" (Anleitung 2026); the company-car benefit is a taxable receipt
217
223
  (Kz 112), the levy refunds a non-taxable one (Kz 103). An individual account
218
224
  takes no payroll role: a role names the DATEV account.
219
225
 
226
+ ### Expense claims
227
+
228
+ An expense claim (Reisekostenabrechnung, Auslagenersatz) finds the accounts of
229
+ its lines by the expense role; within a chart each role names one account, and
230
+ every role has its account in both standard charts. None of them carries a tax
231
+ function: a receipt's input VAT is booked by BU key. The claim is settled
232
+ through the payroll role `clearing` (the Lohn- und Gehaltsverrechnungskonto),
233
+ which takes no expense role.
234
+
235
+ ```typescript
236
+ import { getExpenseAccount } from '@fin.cx/chartdata';
237
+
238
+ getExpenseAccount('SKR03', 2026, 'travel_employee_meals')?.accountNumber; // '4664' Reisekosten Arbeitnehmer Verpflegungsmehraufwand
239
+ getExpenseAccount('SKR04', 2026, 'business_meals_nondeductible')?.euer; // { kennzahl: 165 }
240
+ getExpenseAccount('SKR04', 2026, 'credit_card_settlement')?.hgb266Position; // 'Passiva C.8'
241
+ ```
242
+
243
+ | Role | What it books | SKR03 | SKR04 | Anlage EÜR |
244
+ | --- | --- | --- | --- | --- |
245
+ | `travel_employee` | Reisekosten Arbeitnehmer: travel costs of no other role, such as parking and tolls | 4660 | 6650 | Kz 120 |
246
+ | `travel_employee_transport` | Fahrtkosten: tickets, taxis, a hired car and its fuel | 4663 | 6663 | Kz 120 |
247
+ | `travel_employee_meals` | Verpflegungsmehraufwand | 4664 | 6664 | Kz 120 |
248
+ | `travel_employee_lodging` | Übernachtungsaufwand | 4666 | 6660 | Kz 120 |
249
+ | `mileage_employee` | Kilometergelderstattung Arbeitnehmer | 4668 | 6668 | Kz 120 |
250
+ | `business_meals` | Bewirtungskosten, the deductible 70 % | 4650 | 6640 | Kz 175 |
251
+ | `business_meals_nondeductible` | nicht abzugsfähige Bewirtungskosten, the other 30 % | 4654 | 6644 | Kz 165 |
252
+ | `vehicle_running_costs` | Laufende Fahrzeug-Betriebskosten: a company car's fuel | 4530 | 6530 | Kz 146 |
253
+ | `office_supplies` | Bürobedarf | 4930 | 6815 | Kz 229 |
254
+ | `postage` | Porto | 4910 | 6800 | Kz 229 |
255
+ | `literature` | Zeitschriften, Bücher, digitale Medien (Fachliteratur) | 4940 | 6820 | Kz 229 |
256
+ | `training` | Fortbildungskosten | 4945 | 6821 | Kz 281 |
257
+ | `software` | software licensed or subscribed to (Wartungskosten für Hard- und Software) | 4806 | 6495 | Kz 228 |
258
+ | `other_operating_expenses` | Sonstige betriebliche Aufwendungen | 4900 | 6300 | Kz 183 |
259
+ | `credit_card_settlement` | Kreditkartenabrechnung, a balance (Passiva C.8) | 1730 | 3610 | balance |
260
+
261
+ The expenses stand under the other operating expenses (GuV 8). An employee's
262
+ travel goes with the staff in the Anlage EÜR (Zeile 31): "Aufwendungen für
263
+ Reisen von Arbeitnehmern sind in Zeile 31 zu erfassen" (Anleitung 2026,
264
+ Zeile 45). Software books on DATEV's Wartungskosten für Hard- und Software,
265
+ the running EDV costs of the Anlage EÜR (Zeile 51), not on the licences of
266
+ rights (SKR03 4964, SKR04 6837), which DATEV's trade tax program adds back in
267
+ part (§ 8 Nr. 1 f GewStG); the books book a licence of a right there
268
+ themselves. An individual account takes no expense role: a role names the
269
+ DATEV account.
270
+
220
271
  ### Individual accounts
221
272
 
222
273
  The books may set up accounts of their own (Individualkonten) on the numbers
@@ -275,7 +326,7 @@ books.getMappingCoverage(usedAccountNumbers, 'hgb275');
275
326
  A `BooksChart` is a chart (`IChart`) with the lookups of the chart over the
276
327
  DATEV accounts and the books' own: `getAccount`, `getAccountsByRole`,
277
328
  `getAccountsBySecuritiesRole`, `getSecuritiesAccount`, `getPayrollAccount`,
278
- `getMappingCoverage`, `getEuerLines`.
329
+ `getExpenseAccount`, `getMappingCoverage`, `getEuerLines`.
279
330
 
280
331
  `INDIVIDUAL_ACCOUNT_TEMPLATES` suggests the parent and name of the results the
281
332
  law keeps apart: the gains and losses of Termingeschäfte, which § 15 Abs. 4
@@ -351,6 +402,139 @@ getMappingCoverage('SKR04', 2026, usedAccountNumbers, 'euer').missing; // []
351
402
  and another member state's VAT (OSS) is received in Kz 103 and paid in
352
403
  Kz 183.
353
404
 
405
+ ### BWA: independently defined standard KER
406
+
407
+ `BWA_LINES` is this package's own short-term income statement (Kurzfristige
408
+ Erfolgsrechnung), **not DATEV BWA schema 0906300** and not an implementation of
409
+ its proprietary account-assignment rules. It combines the existing HGB §275
410
+ Gesamtkostenverfahren positions with the public account purposes/ranges of SKR03
411
+ and SKR04 2026. Every curated revenue or expense account has exactly one
412
+ `bwaLine: TBwaPostingLineKey`; balance-sheet accounts have none. Unknown HGB
413
+ positions fail generation instead of silently reaching an unassigned bucket.
414
+
415
+ The ordered, exported `BWA_LINES: readonly TBwaLine[]` contains:
416
+
417
+ - Posting lines: `{ key, label, kind: 'posting', normalSide: 'debit' | 'credit' }`.
418
+ Use selected-period movements, credit minus debit for a credit-normal line,
419
+ debit minus credit for a debit-normal line. Account type does not override this
420
+ sign: disposal proceeds can reduce a neutral expense, and a disposed asset's
421
+ book value can reduce a neutral income.
422
+ - Computed lines: `{ key, label, kind: 'subtotal', terms: [{ line, factor }] }`.
423
+ `factor` is `1` or `-1`; every referenced line precedes the subtotal. Sum the
424
+ already computed amounts times those factors, without posting to subtotals.
425
+
426
+ Credit-normal posting keys are `revenue`, `inventory_changes`,
427
+ `own_work_capitalized`, `other_operating_income`, `interest_income`,
428
+ `other_neutral_income`, and `unassigned_account_classes`. All other posting
429
+ keys are debit-normal. `unassigned_account_classes` is the explicit
430
+ "Kontenklassen-unbesetzt" row, zero for the curated charts; it is no fallback
431
+ for unknown accounts or missing mappings.
432
+
433
+ Exact subtotal definitions (the exported `terms` encode these formulas):
434
+
435
+ | Subtotal key | Definition |
436
+ | --- | --- |
437
+ | `total_output` | `revenue + inventory_changes + own_work_capitalized` |
438
+ | `gross_profit` | `total_output - material_costs` |
439
+ | `operating_gross_profit` | `gross_profit + other_operating_income` |
440
+ | `total_costs` | `personnel_costs + premises_costs + business_taxes + insurance_contributions + special_costs + vehicle_costs + advertising_travel_costs + goods_distribution_costs + depreciation + repairs_maintenance + other_operating_costs` |
441
+ | `operating_result` | `operating_gross_profit - total_costs` |
442
+ | `neutral_expense` | `interest_expense + other_neutral_expense` |
443
+ | `neutral_income` | `interest_income + other_neutral_income` |
444
+ | `result_before_taxes` | `operating_result - neutral_expense + neutral_income + unassigned_account_classes` |
445
+ | `provisional_result` | `result_before_taxes - income_taxes` |
446
+
447
+ Derivation order is significant. GuV 1→`revenue`, 2→`inventory_changes`,
448
+ 3→`own_work_capitalized`, 5→`material_costs`, 6a/6b→`personnel_costs`,
449
+ 7→`depreciation`, 9/10→`other_neutral_income`, 11→`interest_income`,
450
+ 12→`other_neutral_expense`, 13→`interest_expense`, 14→`income_taxes`,
451
+ 16→`business_taxes`. GuV 15 and 17 are results, not posting positions.
452
+ In particular, trade tax (4320/7610, GuV 14) is an income tax, not an
453
+ operating tax merely because of its number.
454
+
455
+ GuV 4 disposal-role accounts and neutral-income ranges (SKR03 2500–2699,
456
+ 2710–2729; SKR04 4840–4929) go to `other_neutral_income`; remaining GuV 4
457
+ accounts go to `other_operating_income`. GuV 8 disposal-role accounts and
458
+ neutral-expense ranges (SKR03 2000–2399, 4976–4977; SKR04 6857–6858,
459
+ 6880–6909) go to `other_neutral_expense`. Remaining GuV 8 accounts use these
460
+ inclusive, non-overlapping operating-cost ranges, then `other_operating_costs`:
461
+
462
+ | Posting key | SKR03 | SKR04 |
463
+ | --- | --- | --- |
464
+ | `premises_costs` | 4200–4299 | 6310–6349 |
465
+ | `business_taxes` | 4340–4359 | 7680–7699 |
466
+ | `insurance_contributions` | 4360–4399 | 6400–6449 |
467
+ | `special_costs` | 4400–4499 (free-purpose range) | 6390–6399 (donations) |
468
+ | `vehicle_costs` | 4500–4599 | 6500–6599 |
469
+ | `advertising_travel_costs` | 4600–4699 | 6600–6699 |
470
+ | `goods_distribution_costs` | 4700–4799 | 6700–6799 |
471
+ | `repairs_maintenance` | 4800–4809 | 6450–6499 |
472
+ | `depreciation` | 4810–4899 | 6910–6919 |
473
+
474
+ The ranges categorise existing GuV accounts; they neither add accounts to the
475
+ curated selection nor license booking on DATEV-reserved numbers. Financial
476
+ write-downs stay in the neutral result because GuV 12 is resolved first.
477
+ The ordinary write-down of other current assets (4886/6910, GuV 8) is included
478
+ in the KER's depreciation line. This is our published derivation, not a promise
479
+ of identical intermediate totals to any third-party BWA.
480
+
481
+ ### Bilanz: balance rules and exclusions
482
+
483
+ Use cumulative balances through the cutoff, not only the report-period
484
+ movements. `hgb266Position` remains the normal-side position;
485
+ `balanceRule: IBalanceRule` supplies an exceptional `debitPosition` or
486
+ `creditPosition` only. An individual account's normal-side HGB override
487
+ therefore remains authoritative. The standard metadata specifies:
488
+
489
+ - Bank credit balances: `Passiva C.2` (SKR03 1200–1299, SKR04 1800–1899,
490
+ restricted to accounts whose normal position is `Aktiva B.IV`). Cash and
491
+ Geldtransit are not bank-overdraft rules.
492
+ - Debtor credit balances: `Passiva C.4`; creditor debit balances:
493
+ `Aktiva B.II.1`. The Debitoren/Kreditoren carry-forward accounts 9008/9009
494
+ keep their normal positions and these same exceptional-side rules.
495
+ - Current domestic VAT: `vatNetGroup: 'vat_current_year'`, the cumulative
496
+ debit-minus-credit sum of the group at `Aktiva B.II.4` when positive and
497
+ `Passiva C.8` when negative. SKR03 members are 1570, 1571, 1574, 1576, 1577,
498
+ 1588, 1770, 1771, 1774, 1776, 1780, 1781, 1784, 1787; SKR04 members are
499
+ 1400, 1401, 1404, 1406, 1407, 1433, 3800, 3801, 3804, 3806, 3820, 3830,
500
+ 3837. Do not state individual members again after netting the group.
501
+ - Prior-year VAT (1790/1791, 3841/3845) and foreign VAT (1728/1767/1768,
502
+ 3798/3817/3818) have sign-sensitive asset/liability rules but **no net group**:
503
+ they remain separate from the current domestic VAT. Payroll taxes do not
504
+ enter the VAT group.
505
+ - Private drawings **and contributions** (1800/1890, 2100/2180):
506
+ `legalForms: ['sole_trader', 'partnership']`. `TLegalForm` also contains
507
+ `'corporation'`, which these accounts do not permit. A report must explicitly
508
+ refuse a nonzero private balance for a corporation instead of treating it as
509
+ corporate equity. This is a conservative report eligibility rule, not a
510
+ substitute for the distinct partnership equity presentation of §264c HGB.
511
+ - Exactly 9000/9400/9998: `hgb266Exclusion: 'technical'`, no balance-sheet
512
+ position. Exclusion explains classification, **not permission to discard
513
+ a nonzero balance**. A report must refuse nonzero technical balances; 9000
514
+ must net to zero across a complete set of opening entries.
515
+
516
+ `HGB_ANNUAL_RESULT_POSITION` is `Passiva A.V`. Add cumulative, credit-minus-debit
517
+ P&L through the cutoff there once, including a negative annual result. Do not
518
+ use `Passiva A.IV` (prior-year retained result) or technical 9400 for it.
519
+ `getMappingCoverage(..., 'hgb266')` treats an explicit technical exclusion as
520
+ classified; the report still enforces the nonzero-balance refusal.
521
+ `getMappingCoverage(..., 'bwa')` checks posting-line metadata. In either
522
+ dimension, unknown and missing accounts remain distinct errors.
523
+
524
+ `BooksChart` individual accounts inherit `bwaLine`, `hgb266Exclusion`, and a
525
+ copy of `balanceRule` from their parent. Report consumers use the books' chart,
526
+ not a separate hardcoded account map. Mapping and report-generation rules
527
+ live in `tools/report-mappings.ts` and `tools/generate-charts.ts`.
528
+
529
+ Primary sources read on 2026-10-09: [HGB §266](https://www.gesetze-im-internet.de/hgb/__266.html),
530
+ [HGB §275](https://www.gesetze-im-internet.de/hgb/__275.html),
531
+ [HGB §264c](https://www.gesetze-im-internet.de/hgb/__264c.html),
532
+ [UStG §16](https://www.gesetze-im-internet.de/ustg_1980/__16.html), and the
533
+ public DATEV Kontenrahmen 2026 [SKR03, Art.-Nr. 11174](https://help-center.apps.datev.de/api/amr/knowledge-common/v1/entities/st9007262836099723_de.pdf)
534
+ (pp. 13–17, 22–25) and [SKR04, Art.-Nr. 11175](https://help-center.apps.datev.de/api/amr/knowledge-common/v1/entities/st9007262836101387_de.pdf)
535
+ (pp. 17–24). Account categories come from those public charts; the KER
536
+ selection, labels, boundaries and calculations above are independently defined.
537
+
354
538
  ## Data Provenance and Verification
355
539
 
356
540
  Records carry a `provenance` string. The datasets are a curated table authored
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@fin.cx/chartdata',
6
- version: '3.1.0',
6
+ version: '3.3.0',
7
7
  description: 'Year-versioned German SKR03/SKR04 chart-of-accounts datasets with Automatikkonto functions, BU-key rules, UStVA-Kennzahl and Anlage EÜR mappings'
8
8
  }
@@ -0,0 +1,74 @@
1
+ import type { TBwaLine } from './chartdata.types.js';
2
+
3
+ export const BWA_LINES: readonly TBwaLine[] = [
4
+ { key: 'revenue', label: 'Umsatzerlöse', kind: 'posting', normalSide: 'credit' },
5
+ { key: 'inventory_changes', label: 'Bestandsveränderungen', kind: 'posting', normalSide: 'credit' },
6
+ { key: 'own_work_capitalized', label: 'Aktivierte Eigenleistungen', kind: 'posting', normalSide: 'credit' },
7
+ { key: 'total_output', label: 'Gesamtleistung', kind: 'subtotal', terms: [
8
+ { line: 'revenue', factor: 1 },
9
+ { line: 'inventory_changes', factor: 1 },
10
+ { line: 'own_work_capitalized', factor: 1 },
11
+ ] },
12
+ { key: 'material_costs', label: 'Material- und Wareneinsatz', kind: 'posting', normalSide: 'debit' },
13
+ { key: 'gross_profit', label: 'Rohertrag', kind: 'subtotal', terms: [
14
+ { line: 'total_output', factor: 1 },
15
+ { line: 'material_costs', factor: -1 },
16
+ ] },
17
+ { key: 'other_operating_income', label: 'Sonstige betriebliche Erlöse', kind: 'posting', normalSide: 'credit' },
18
+ { key: 'operating_gross_profit', label: 'Betrieblicher Rohertrag', kind: 'subtotal', terms: [
19
+ { line: 'gross_profit', factor: 1 },
20
+ { line: 'other_operating_income', factor: 1 },
21
+ ] },
22
+ { key: 'personnel_costs', label: 'Personalkosten', kind: 'posting', normalSide: 'debit' },
23
+ { key: 'premises_costs', label: 'Raumkosten', kind: 'posting', normalSide: 'debit' },
24
+ { key: 'business_taxes', label: 'Betriebliche Steuern', kind: 'posting', normalSide: 'debit' },
25
+ { key: 'insurance_contributions', label: 'Versicherungen und Beiträge', kind: 'posting', normalSide: 'debit' },
26
+ { key: 'special_costs', label: 'Besondere Kosten', kind: 'posting', normalSide: 'debit' },
27
+ { key: 'vehicle_costs', label: 'Fahrzeugkosten', kind: 'posting', normalSide: 'debit' },
28
+ { key: 'advertising_travel_costs', label: 'Werbe- und Reisekosten', kind: 'posting', normalSide: 'debit' },
29
+ { key: 'goods_distribution_costs', label: 'Kosten der Warenabgabe', kind: 'posting', normalSide: 'debit' },
30
+ { key: 'depreciation', label: 'Abschreibungen', kind: 'posting', normalSide: 'debit' },
31
+ { key: 'repairs_maintenance', label: 'Reparaturen und Instandhaltung', kind: 'posting', normalSide: 'debit' },
32
+ { key: 'other_operating_costs', label: 'Sonstige Kosten', kind: 'posting', normalSide: 'debit' },
33
+ { key: 'total_costs', label: 'Gesamtkosten', kind: 'subtotal', terms: [
34
+ { line: 'personnel_costs', factor: 1 },
35
+ { line: 'premises_costs', factor: 1 },
36
+ { line: 'business_taxes', factor: 1 },
37
+ { line: 'insurance_contributions', factor: 1 },
38
+ { line: 'special_costs', factor: 1 },
39
+ { line: 'vehicle_costs', factor: 1 },
40
+ { line: 'advertising_travel_costs', factor: 1 },
41
+ { line: 'goods_distribution_costs', factor: 1 },
42
+ { line: 'depreciation', factor: 1 },
43
+ { line: 'repairs_maintenance', factor: 1 },
44
+ { line: 'other_operating_costs', factor: 1 },
45
+ ] },
46
+ { key: 'operating_result', label: 'Betriebsergebnis', kind: 'subtotal', terms: [
47
+ { line: 'operating_gross_profit', factor: 1 },
48
+ { line: 'total_costs', factor: -1 },
49
+ ] },
50
+ { key: 'interest_expense', label: 'Zinsaufwand', kind: 'posting', normalSide: 'debit' },
51
+ { key: 'other_neutral_expense', label: 'Sonstiger neutraler Aufwand', kind: 'posting', normalSide: 'debit' },
52
+ { key: 'neutral_expense', label: 'Neutraler Aufwand', kind: 'subtotal', terms: [
53
+ { line: 'interest_expense', factor: 1 },
54
+ { line: 'other_neutral_expense', factor: 1 },
55
+ ] },
56
+ { key: 'interest_income', label: 'Zinserträge', kind: 'posting', normalSide: 'credit' },
57
+ { key: 'other_neutral_income', label: 'Sonstiger neutraler Ertrag', kind: 'posting', normalSide: 'credit' },
58
+ { key: 'neutral_income', label: 'Neutraler Ertrag', kind: 'subtotal', terms: [
59
+ { line: 'interest_income', factor: 1 },
60
+ { line: 'other_neutral_income', factor: 1 },
61
+ ] },
62
+ { key: 'unassigned_account_classes', label: 'Kontenklassen-unbesetzt', kind: 'posting', normalSide: 'credit' },
63
+ { key: 'result_before_taxes', label: 'Ergebnis vor Steuern', kind: 'subtotal', terms: [
64
+ { line: 'operating_result', factor: 1 },
65
+ { line: 'neutral_expense', factor: -1 },
66
+ { line: 'neutral_income', factor: 1 },
67
+ { line: 'unassigned_account_classes', factor: 1 },
68
+ ] },
69
+ { key: 'income_taxes', label: 'Steuern vom Einkommen und Ertrag', kind: 'posting', normalSide: 'debit' },
70
+ { key: 'provisional_result', label: 'Vorläufiges Ergebnis', kind: 'subtotal', terms: [
71
+ { line: 'result_before_taxes', factor: 1 },
72
+ { line: 'income_taxes', factor: -1 },
73
+ ] },
74
+ ];
@@ -10,6 +10,8 @@ export interface IHgbPosition {
10
10
  label: string;
11
11
  }
12
12
 
13
+ export const HGB_ANNUAL_RESULT_POSITION = 'Passiva A.V';
14
+
13
15
  export const HGB_266_POSITIONS: IHgbPosition[] = [
14
16
  { key: 'Aktiva A.I.1', label: 'Selbst geschaffene gewerbliche Schutzrechte und ähnliche Rechte und Werte' },
15
17
  { key: 'Aktiva A.I.2', label: 'Entgeltlich erworbene Konzessionen, gewerbliche Schutzrechte und ähnliche Rechte und Werte sowie Lizenzen an solchen Rechten und Werten' },
@@ -9,6 +9,7 @@ import type {
9
9
  TAccountRole,
10
10
  TChartName,
11
11
  TEuerAccountLines,
12
+ TExpenseRole,
12
13
  TMappingDimension,
13
14
  TPayrollRole,
14
15
  TSecuritiesRole,
@@ -246,6 +247,12 @@ function individualAccountOf(parentArg: IChartAccount, declarationArg: IIndividu
246
247
  ...(ustva ? { ustva } : {}),
247
248
  ...(hgb266Position ? { hgb266Position } : {}),
248
249
  ...(hgb275Position ? { hgb275Position } : {}),
250
+ ...(parentArg.bwaLine ? { bwaLine: parentArg.bwaLine } : {}),
251
+ ...(parentArg.hgb266Exclusion ? { hgb266Exclusion: parentArg.hgb266Exclusion } : {}),
252
+ ...(parentArg.balanceRule ? { balanceRule: {
253
+ ...parentArg.balanceRule,
254
+ ...(parentArg.balanceRule.legalForms ? { legalForms: [...parentArg.balanceRule.legalForms] } : {}),
255
+ } } : {}),
249
256
  ...(parentArg.ebilanzPosition ? { ebilanzPosition: parentArg.ebilanzPosition } : {}),
250
257
  ...(euer ? { euer } : {}),
251
258
  ...(euerExclusion ? { euerExclusion } : {}),
@@ -310,6 +317,11 @@ export class BooksChart implements IChart {
310
317
  return this.accounts.find((account) => account.payrollRole === roleArg) ?? null;
311
318
  }
312
319
 
320
+ /** The one account of the books that books the expense role, or `null`; an individual account takes none. */
321
+ public getExpenseAccount(roleArg: TExpenseRole): IChartAccount | null {
322
+ return this.accounts.find((account) => account.expenseRole === roleArg) ?? null;
323
+ }
324
+
313
325
  public getMappingCoverage(usedAccountsArg: string[], dimensionArg: TMappingDimension): IMappingCoverage {
314
326
  return mappingCoverageOf(this.accounts, usedAccountsArg, dimensionArg);
315
327
  }
@@ -6,6 +6,7 @@ import type {
6
6
  TAccountRole,
7
7
  TChartName,
8
8
  TEuerAccountLines,
9
+ TExpenseRole,
9
10
  TMappingDimension,
10
11
  TPayrollRole,
11
12
  TSecuritiesRole,
@@ -122,12 +123,22 @@ export function getPayrollAccount(chart: TChartName, year: number, role: TPayrol
122
123
  return getChart(chart, year).accounts.find((account) => account.payrollRole === role) ?? null;
123
124
  }
124
125
 
126
+ /**
127
+ * The one account of a chart that books the expense role (`IChartAccount.expenseRole`), or `null` where the chart
128
+ * keeps none for it. Throws for unknown chart/year combinations, as `getChart`.
129
+ */
130
+ export function getExpenseAccount(chart: TChartName, year: number, role: TExpenseRole): IChartAccount | null {
131
+ return getChart(chart, year).accounts.find((account) => account.expenseRole === role) ?? null;
132
+ }
133
+
125
134
  function hasMapping(account: IChartAccount, dimension: TMappingDimension): boolean {
126
135
  switch (dimension) {
127
136
  case 'ustva':
128
137
  return account.ustva !== undefined;
129
138
  case 'hgb266':
130
- return account.hgb266Position !== undefined;
139
+ return account.hgb266Position !== undefined || account.hgb266Exclusion !== undefined;
140
+ case 'bwa':
141
+ return account.bwaLine !== undefined;
131
142
  case 'hgb275':
132
143
  return account.hgb275Position !== undefined;
133
144
  case 'ebilanz':
@@ -198,6 +198,60 @@ export type TPayrollRole =
198
198
  | 'estimated_contributions'
199
199
  | 'clearing';
200
200
 
201
+ /**
202
+ * What an account books of the expenses an employee lays out for the business
203
+ * and claims back (Reisekostenabrechnung, Auslagenersatz), and of the
204
+ * business's card through which such expenses are paid. An expense claim
205
+ * finds its account by this role: within a chart, no two accounts share one,
206
+ * and every role has its account in both standard charts (DATEV Kontenrahmen
207
+ * SKR 03 Art.-Nr. 11174, pp. 10, 23, 24, 25, and SKR 04 Art.-Nr. 11175,
208
+ * pp. 12, 23, 24, gültig für 2026). None of the accounts carries a tax
209
+ * function: the input VAT of a receipt is booked by BU key. The claim itself
210
+ * is settled through the payroll role 'clearing' (the Lohn- und
211
+ * Gehaltsverrechnungskonto), which takes no expense role.
212
+ *
213
+ * An employee's business travel, under the other operating expenses
214
+ * (§ 275 Abs. 2 Nr. 8 HGB, `GuV 8`), all of it with the staff in the Anlage
215
+ * EÜR (Zeile 31): 'travel_employee' (Reisekosten Arbeitnehmer: the travel
216
+ * costs of no other role, such as parking and tolls, the Reisenebenkosten),
217
+ * 'travel_employee_transport' (Fahrtkosten: tickets, taxis, a hired car and
218
+ * its fuel), 'travel_employee_meals' (Verpflegungsmehraufwand),
219
+ * 'travel_employee_lodging' (Übernachtungsaufwand) and 'mileage_employee'
220
+ * (Kilometergelderstattung Arbeitnehmer, for an employee's own car).
221
+ *
222
+ * The business's other expenses an employee lays out, under the other
223
+ * operating expenses (`GuV 8`): 'business_meals' (Bewirtungskosten, the
224
+ * deductible 70 %) and 'business_meals_nondeductible' (their non-deductible
225
+ * 30 %, § 4 Abs. 5 Satz 1 Nr. 2 EStG), 'vehicle_running_costs' (Laufende
226
+ * Fahrzeug-Betriebskosten: a company car's fuel), 'office_supplies'
227
+ * (Bürobedarf), 'postage' (Porto), 'literature' (Zeitschriften, Bücher,
228
+ * digitale Medien), 'training' (Fortbildungskosten), 'software' (software
229
+ * licensed or subscribed to as an expense, on DATEV's Wartungskosten für
230
+ * Hard- und Software, the running EDV costs of the Anlage EÜR) and
231
+ * 'other_operating_expenses' (Sonstige betriebliche Aufwendungen).
232
+ *
233
+ * 'credit_card_settlement': the Kreditkartenabrechnung, the clearing account
234
+ * of a card's charges until its statement is paid, under the other
235
+ * liabilities (§ 266 Abs. 3 C.8 HGB; DATEV states it under the other assets
236
+ * or the other liabilities by its balance).
237
+ */
238
+ export type TExpenseRole =
239
+ | 'travel_employee'
240
+ | 'travel_employee_transport'
241
+ | 'travel_employee_meals'
242
+ | 'travel_employee_lodging'
243
+ | 'mileage_employee'
244
+ | 'business_meals'
245
+ | 'business_meals_nondeductible'
246
+ | 'vehicle_running_costs'
247
+ | 'office_supplies'
248
+ | 'postage'
249
+ | 'literature'
250
+ | 'training'
251
+ | 'software'
252
+ | 'other_operating_expenses'
253
+ | 'credit_card_settlement';
254
+
201
255
  /**
202
256
  * A rule of the income tax law an income or expense account is kept for, as
203
257
  * its DATEV name states it:
@@ -332,6 +386,70 @@ export type TEuerAccountLines =
332
386
  | { kind: 'split'; debitLine: IEuerFormLine; creditLine: IEuerFormLine }
333
387
  | { kind: 'excluded'; exclusion: TEuerExclusion };
334
388
 
389
+ export type TLegalForm = 'sole_trader' | 'partnership' | 'corporation';
390
+
391
+ export type THgb266Exclusion = 'technical';
392
+
393
+ export interface IBalanceRule {
394
+ debitPosition?: string;
395
+ creditPosition?: string;
396
+ vatNetGroup?: string;
397
+ legalForms?: readonly TLegalForm[];
398
+ }
399
+
400
+ export type TBwaPostingLineKey =
401
+ | 'revenue'
402
+ | 'inventory_changes'
403
+ | 'own_work_capitalized'
404
+ | 'material_costs'
405
+ | 'other_operating_income'
406
+ | 'personnel_costs'
407
+ | 'premises_costs'
408
+ | 'business_taxes'
409
+ | 'insurance_contributions'
410
+ | 'special_costs'
411
+ | 'vehicle_costs'
412
+ | 'advertising_travel_costs'
413
+ | 'goods_distribution_costs'
414
+ | 'depreciation'
415
+ | 'repairs_maintenance'
416
+ | 'other_operating_costs'
417
+ | 'interest_expense'
418
+ | 'other_neutral_expense'
419
+ | 'interest_income'
420
+ | 'other_neutral_income'
421
+ | 'unassigned_account_classes'
422
+ | 'income_taxes';
423
+
424
+ export type TBwaSubtotalLineKey =
425
+ | 'total_output'
426
+ | 'gross_profit'
427
+ | 'operating_gross_profit'
428
+ | 'total_costs'
429
+ | 'operating_result'
430
+ | 'neutral_expense'
431
+ | 'neutral_income'
432
+ | 'result_before_taxes'
433
+ | 'provisional_result';
434
+
435
+ export type TBwaLineKey = TBwaPostingLineKey | TBwaSubtotalLineKey;
436
+
437
+ export interface IBwaPostingLine {
438
+ key: TBwaPostingLineKey;
439
+ label: string;
440
+ kind: 'posting';
441
+ normalSide: 'debit' | 'credit';
442
+ }
443
+
444
+ export interface IBwaSubtotalLine {
445
+ key: TBwaSubtotalLineKey;
446
+ label: string;
447
+ kind: 'subtotal';
448
+ terms: readonly { line: TBwaLineKey; factor: 1 | -1 }[];
449
+ }
450
+
451
+ export type TBwaLine = IBwaPostingLine | IBwaSubtotalLine;
452
+
335
453
  export interface IChartAccount {
336
454
  accountNumber: string;
337
455
  accountName: string;
@@ -343,6 +461,8 @@ export interface IChartAccount {
343
461
  securitiesRole?: TSecuritiesRole;
344
462
  /** what the account books of a business's payroll; unset = nothing of its own */
345
463
  payrollRole?: TPayrollRole;
464
+ /** what the account books of the expenses an employee lays out and claims back; unset = nothing of its own */
465
+ expenseRole?: TExpenseRole;
346
466
  /** the income tax rule the account is kept for, as its DATEV name states it; unset = none of its own */
347
467
  taxRule?: TTaxRule;
348
468
  /**
@@ -361,6 +481,9 @@ export interface IChartAccount {
361
481
  hgb266Position?: string;
362
482
  /** HGB §275 GuV position path (A2) */
363
483
  hgb275Position?: string;
484
+ bwaLine?: TBwaPostingLineKey;
485
+ hgb266Exclusion?: THgb266Exclusion;
486
+ balanceRule?: IBalanceRule;
364
487
  /** E-Bilanz de-gaap-ci taxonomy position (A3 — not yet populated) */
365
488
  ebilanzPosition?: string;
366
489
  /** the Anlage EÜR line(s) the account's movements go to; every account has this or `euerExclusion` */
@@ -381,7 +504,7 @@ export interface IChart {
381
504
  }
382
505
 
383
506
  /** 'euer' counts an account covered when it maps to the Anlage EÜR or is excluded from it with a reason. */
384
- export type TMappingDimension = 'ustva' | 'hgb266' | 'hgb275' | 'ebilanz' | 'euer';
507
+ export type TMappingDimension = 'ustva' | 'hgb266' | 'hgb275' | 'bwa' | 'ebilanz' | 'euer';
385
508
 
386
509
  export interface IMappingCoverage {
387
510
  dimension: TMappingDimension;