13 KiB
Buchhaltung System — Design
Date: 2026-03-28 Status: Approved
Overview
A budget and transaction tracking system for the Feuerwehr. Tracks money flows against budget pots and real bank accounts, scoped by fiscal year. Integrates with the existing Bestellungen system and permission matrix.
Full Cycle
Budget Plan → Fiscal Year → Freigaben → Bestellungen → Pending Transactions → Booked Transactions
1. Data Model
buchhaltung_konto_typen
Dynamic list of bank account types (Girokonto, Tagesgeldkonto, Handkasse, PayPal, …). Managed in settings by users with manage_settings.
| Column | Type | Notes |
|---|---|---|
| id | serial PK | |
| name | varchar | |
| beschreibung | text | nullable |
| created_at | timestamptz |
buchhaltung_bankkonten
Real bank accounts (where money physically is).
| Column | Type | Notes |
|---|---|---|
| id | serial PK | |
| name | varchar | |
| typ_id | FK konto_typen | |
| iban | varchar | nullable |
| opening_balance | decimal(12,2) | starting balance when first added |
| aktiv | bool | default true |
| created_at | timestamptz |
Balance = opening_balance + SUM(booked transactions).
buchhaltung_haushaltsjahre
Fiscal years. Bank accounts are not year-scoped; budget accounts are.
| Column | Type | Notes |
|---|---|---|
| id | serial PK | |
| jahr | int | unique (e.g. 2026) |
| start_datum | date | |
| end_datum | date | |
| abgeschlossen | bool | default false; manually closed by manage_settings user |
Closing a year locks all its transactions (no edits, no new bookings).
buchhaltung_konten
Budget accounts / pots. Scoped per fiscal year.
| Column | Type | Notes |
|---|---|---|
| id | serial PK | |
| haushaltsjahr_id | FK haushaltsjahre | |
| name | varchar | |
| beschreibung | text | nullable |
| budget | decimal(12,2) | |
| alert_threshold | int | % (0-100); falls back to global setting if null |
| aktiv | bool | default true |
| created_at | timestamptz |
buchhaltung_transaktionen
Core transaction table.
| Column | Type | Notes |
|---|---|---|
| id | serial PK | internal DB key |
| haushaltsjahr_id | FK haushaltsjahre | |
| laufende_nummer | int | sequential per fiscal year; display as 2026/0001 |
| datum | date | |
| beschreibung | text | |
| betrag | decimal(12,2) | nullable for variable recurring transactions |
| typ | enum | einnahme, ausgabe, transfer |
| konto_id | FK konten | nullable (null for transfers) |
| bankkonto_id | FK bankkonten | source bank account |
| transfer_ziel_bankkonto_id | FK bankkonten | nullable; only for typ=transfer |
| bestellung_id | FK bestellungen | nullable; set when auto-created from order |
| split_gruppe_id | uuid | nullable; groups split transactions from same Bestellung |
| gebucht | bool | false = pending, true = booked |
| erstellt_von | FK users | |
| gebucht_von | FK users | nullable |
| gebucht_am | timestamptz | nullable |
| created_at | timestamptz |
Display ID: ${jahr}/${laufende_nummer.toString().padStart(4, '0')} e.g. 2026/0001.
laufende_nummer is unique per haushaltsjahr_id, incremented per new transaction.
buchhaltung_belege
Receipt/invoice attachments per transaction.
| Column | Type | Notes |
|---|---|---|
| id | serial PK | |
| transaktion_id | FK transaktionen | |
| dateiname | varchar | |
| pfad | varchar | disk path |
| mime_type | varchar | |
| erstellt_von | FK users | |
| created_at | timestamptz |
Uses same multer disk storage pattern as Bestellungen (uploads/buchhaltung/).
buchhaltung_wiederkehrend
Recurring transaction templates.
| Column | Type | Notes |
|---|---|---|
| id | serial PK | |
| beschreibung | varchar | |
| betrag | decimal(12,2) | nullable (e.g. variable energy bills) |
| typ | enum | einnahme, ausgabe |
| konto_id | FK konten | nullable |
| bankkonto_id | FK bankkonten | nullable |
| frequenz | enum | monatlich, vierteljaehrlich, jaehrlich |
| naechste_faelligkeit | date | advanced by job after each creation |
| aktiv | bool | |
| erstellt_von | FK users |
Job runs daily → creates pending transaction → advances naechste_faelligkeit.
Betrag null → pending transaction has null betrag → Kassenwart fills it in before booking.
buchhaltung_freigaben
Budget delegation — authorizes a user to spend against a budget account.
| Column | Type | Notes |
|---|---|---|
| id | serial PK | |
| konto_id | FK konten | budget account |
| user_id | FK users | authorized user |
| beschreibung | text | what this covers |
| betrag_limit | decimal(12,2) | nullable; max spending allowed |
| planposition_id | FK planpositionen | nullable; source plan item |
| aktiv | bool | |
| erstellt_von | FK users | |
| created_at | timestamptz |
Amount is editable after creation (plan may change mid-year).
Users with a Freigabe can create Bestellungen against that account even without buchhaltung:create.
buchhaltung_audit
Append-only audit log.
| Column | Type | Notes |
|---|---|---|
| id | serial PK | |
| transaktion_id | FK transaktionen | |
| aktion | enum | erstellt, gebucht, bearbeitet, geloescht |
| benutzer_id | FK users | |
| details | jsonb | before/after snapshot |
| created_at | timestamptz |
buchhaltung_einstellungen
Key/value store for global settings.
| Key | Default | Notes |
|---|---|---|
default_alert_threshold |
80 |
% budget used before notification |
pdf_footer |
'' |
reuses PDF settings pattern |
Budget Planning Tables
buchhaltung_planung
A plan for a future fiscal year.
| Column | Type | Notes |
|---|---|---|
| id | serial PK | |
| ziel_jahr | int | the year being planned |
| name | varchar | e.g. "Haushaltsplan 2027" |
| status | enum | entwurf, genehmigt |
| erstellt_von | FK users | |
| created_at | timestamptz |
Starts pre-populated from current open budget accounts. New accounts can be added. Approving locks the plan. Only approved plans can be used for fiscal year rollover.
buchhaltung_plankonten
Planned budget pots within a plan.
| Column | Type | Notes |
|---|---|---|
| id | serial PK | |
| planung_id | FK planung | |
| konto_referenz_id | FK konten | nullable; links to existing account if carried over |
| name | varchar | |
| beschreibung | text | nullable |
| sort_order | int |
buchhaltung_planpositionen
Line items within a planned pot. Sum = pot total.
| Column | Type | Notes |
|---|---|---|
| id | serial PK | |
| plankonto_id | FK plankonten | |
| beschreibung | text | what it's for |
| betrag | decimal(12,2) | |
| sort_order | int |
From approved plan items → manual Freigaben creation flow (UI, not automatic). Amount editable on Freigabe creation and after.
2. Permissions
New buchhaltung feature group in the existing permission matrix:
| Permission | Description |
|---|---|
buchhaltung:view |
See transactions, accounts, bank accounts |
buchhaltung:create |
Add transactions manually |
buchhaltung:edit |
Edit unbooked transactions |
buchhaltung:delete |
Delete unbooked transactions |
buchhaltung:manage_accounts |
Create/edit/archive budget accounts, bank accounts, Freigaben |
buchhaltung:manage_settings |
Global settings, account types, fiscal year management, recurring templates |
buchhaltung:export |
PDF and CSV export |
buchhaltung:widget |
See the dashboard widget |
manage_settings ≠ dashboard_admin — a dedicated Kassenwart can have this without being a global admin.
3. Backend Architecture
Follows the existing project pattern (service → controller → routes → app.ts).
Routes mounted at /api/buchhaltung/:
| Route | Description |
|---|---|
bankkonten |
CRUD bank accounts |
konten |
CRUD budget accounts (scoped by fiscal year) |
transaktionen |
list, create, book, edit, delete |
transfers |
create bank-to-bank transfers |
freigaben |
CRUD budget delegations |
wiederkehrend |
CRUD recurring templates |
haushaltsjahre |
create new year, close year, list |
planung |
CRUD budget plans and plan items |
einstellungen |
key/value settings |
export/pdf |
annual report PDF |
export/csv |
transaction CSV export |
audit/:transaktionId |
audit trail for one transaction |
Jobs:
buchhaltung-recurring.job.ts— runs daily; creates pending transactions from due recurring templates; advancesnaechste_faelligkeit- Budget alert fires inline after each booking: recalculates account total → if crosses threshold →
notificationService.createNotification()formanage_accountsusers (deduped by DB constraint)
4. Bestellungen Integration
Costs on positions
bestellpositionengets aneinzelpreis/gesamtpreisfield (if not already present)- Status change to
vollstaendig/abgeschlossenis blocked until all positions have costs entered - Attempting the status change without costs →
showWarningsnackbar: "Bestellung kann erst abgeschlossen werden, wenn Kosten für alle Positionen eingetragen wurden." - Backend enforces the same rule (400 response)
- The order creator/manager enters costs — not the Kassenwart
Pending transaction creation
When a Bestellung reaches abgeschlossen:
buchhaltungService.createPendingFromBestellung(bestellung)is called- Creates a pending
ausgabetransaction: betrag = sum of position costs, beschreibung = order title, bestellung_id = FK - Notifies users with
buchhaltung:createorbuchhaltung:manage_accounts
Split booking
When booking a pending transaction from a Bestellung, the Kassenwart can split it into N parts:
- Each part: betrag, budget konto, bankkonto, Beleg (receipt)
- Parts sum to ≤ total (partial booking allowed; remainder stays pending)
- Each part becomes its own booked transaction with its own
<year>/<id> - All parts share the same
bestellung_idandsplit_gruppe_id(visually grouped in audit trail)
5. Frontend Pages
Navigation
New "Buchhaltung" sidebar group:
- Übersicht
- Transaktionen
- Bankkonten
- Konten & Haushaltsjahre
- Freigaben (only with
manage_accounts) - Haushaltsplan (only with
manage_settings)
Pages
Buchhaltung.tsx — overview. Year selector at top. Budget account cards: name, budget, spent, remaining, % bar, alert indicator. Pending transactions count with link.
Transaktionen.tsx — full list. Filter by year / account / bank account / typ. Columns: ID (2026/0001), Datum, Beschreibung, Konto, Bankkonto, Betrag, Status. Click → booking dialog.
BankkontoDetail.tsx — statement view. Running balance column per row. Date range filter. CSV/PDF export.
BuchhaltungKonten.tsx — tabs: Konten (current year) | Bankkonten | Haushaltsjahre. Year-end actions: "Jahr abschließen" (manual, locks year) + "Neues Haushaltsjahr" (from approved plan or copy previous).
BuchhaltungEinstellungen.tsx (or AdminDashboard tab) — Kontentypen list, default alert threshold, recurring templates.
Haushaltsplan.tsx — plan management. Pre-populated from current accounts. Add/edit/remove plankonten and planpositionen. Approve plan. From approved items → create Freigaben (manual flow, amounts editable).
Booking Dialog
Used from pending transaction list:
- Pre-filled from Bestellung (amount, description, reference)
- "Split" button adds a row
- Each row: betrag, budget konto, bankkonto, Beleg upload
- Sum validation
- Confirm → books all rows at once
Widget
BuchhaltungWidget.tsx — compact card in Status group. Shows pending transaction count. Tap → filtered pending list. Only shown with buchhaltung:widget.
6. PDF Export
Uses existing addPdfHeader / addPdfFooter from frontend/src/utils/pdfExport (same red header, logo, org name as Bestellungen and Kalender).
Content of annual report TBD (separate discussion).
7. Fiscal Year Rollover
- Close current year — manual action by
manage_settingsuser → setsabgeschlossen = true→ all transactions locked - Create new year — choose source:
- From approved plan — plankonten become new konten, plankonto totals (sum of planpositionen) become budgets
- Copy previous year — all active konten copied with same budget amounts (editable before confirming)
- Bank accounts carry over automatically (not year-scoped)
- Old year remains readable/exportable forever
8. Future: Sub-Budget Flow from Plan Items
(Planned feature — not in initial implementation)
From an approved plan's items, a manual UI flow to create Freigaben:
- User selects plan items
- Sets/adjusts amount per Freigabe (editable at creation and later — plan may change mid-year)
- Assigns a user
- Creates the Freigabe linked to
planposition_id
This closes the full loop: Plan item → Freigabe → Bestellung → Pending transaction → Booked transaction.