# Buchhaltung — Remaining Features Implementation Plan ## Context The Buchhaltung system has core CRUD, transfers, bank statements, Haushaltsplan, and PDF export implemented. Three features from the original design doc remain: 1. **Budget Delegation Freigaben** — authorizes a user to spend against a budget account (distinct from the existing transaction-approval freigaben) 2. **Split Booking Dialog** — book a pending transaction into N parts across different budget accounts 3. **Sub-Budget Flow** — create budget delegation Freigaben from approved plan positions --- ## Task Dependency Graph ``` T1: Migration 083 (budget_freigaben) T2: Migration 084 (split_gruppe_id) | | v v T3: Backend budget freigaben CRUD T4: Backend split booking endpoint | | +------------+ v | | T7: Frontend BookingDialog v v T5: Backend plan→ T6: Frontend budget freigabe endpoint freigaben tab + sidebar | | +-----+------+ v T8: Frontend plan position → freigabe action ``` --- ## Tasks ### T1 — Migration 083: `buchhaltung_budget_freigaben` table **Teammate**: database | **blockedBy**: none Create `backend/src/database/migrations/083_buchhaltung_budget_freigaben.sql`: ```sql CREATE TABLE IF NOT EXISTS buchhaltung_budget_freigaben ( id SERIAL PRIMARY KEY, konto_id INT NOT NULL REFERENCES buchhaltung_konten(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, beschreibung TEXT, betrag_limit NUMERIC(12,2), planposition_id INT REFERENCES buchhaltung_planpositionen(id) ON DELETE SET NULL, aktiv BOOLEAN NOT NULL DEFAULT TRUE, erstellt_von UUID REFERENCES users(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX idx_budget_freigaben_konto ON buchhaltung_budget_freigaben(konto_id); CREATE INDEX idx_budget_freigaben_user ON buchhaltung_budget_freigaben(user_id); ``` ### T2 — Migration 084: Add `split_gruppe_id` to transaktionen **Teammate**: database | **blockedBy**: none Create `backend/src/database/migrations/084_buchhaltung_split_gruppe.sql`: ```sql ALTER TABLE buchhaltung_transaktionen ADD COLUMN IF NOT EXISTS split_gruppe_id UUID; CREATE INDEX IF NOT EXISTS idx_buch_trans_split_gruppe ON buchhaltung_transaktionen(split_gruppe_id) WHERE split_gruppe_id IS NOT NULL; ``` ### T3 — Backend: Budget Freigaben CRUD **Teammate**: backend | **blockedBy**: T1 **Files**: `buchhaltung.service.ts`, `buchhaltung.controller.ts`, `buchhaltung.routes.ts` Service functions (add before export block ~line 1937): - `listBudgetFreigaben(filters: { konto_id?, user_id? })` — JOIN users + konten for display names - `getMyBudgetFreigaben(userId)` — active freigaben for current user - `createBudgetFreigabe(data, erstelltVon)` — INSERT - `updateBudgetFreigabe(id, data, userId)` — UPDATE beschreibung/betrag_limit - `deactivateBudgetFreigabe(id, userId)` — SET aktiv=false Routes (add BEFORE `/:id` catch-all routes): ``` GET /budget-freigaben manage_accounts listBudgetFreigaben GET /budget-freigaben/my buchhaltung:view getMyBudgetFreigaben POST /budget-freigaben manage_accounts createBudgetFreigabe PATCH /budget-freigaben/:id manage_accounts updateBudgetFreigabe DELETE /budget-freigaben/:id manage_accounts deactivateBudgetFreigabe ``` ### T4 — Backend: Split Booking Endpoint **Teammate**: backend | **blockedBy**: T2 **Files**: `buchhaltung.service.ts`, `buchhaltung.controller.ts` Modify `bookTransaktion(id, userId, splits?)`: - No splits → existing behavior (unchanged) - With splits `Array<{ betrag, konto_id, bankkonto_id? }>`: 1. Validate sum equals original betrag 2. BEGIN transaction 3. Generate UUID `split_gruppe_id` 4. Mark original as `status='gebucht'`, set `split_gruppe_id` 5. INSERT N new booked transactions copying fields from original but with split-specific betrag/konto_id/bankkonto_id, same `split_gruppe_id` 6. Budget alert check per split konto 7. Audit: `logAudit(id, 'split_gebucht', { splits, split_gruppe_id }, userId)` Controller: read optional `req.body.splits`, pass to service. ### T5 — Backend: Plan Position → Freigabe Endpoint **Teammate**: backend | **blockedBy**: T3 **Files**: `buchhaltung.service.ts`, `buchhaltung.controller.ts`, `buchhaltung.routes.ts` New function `createBudgetFreigabeFromPlanposition(posId, { user_id, betrag_limit?, beschreibung? }, erstelltVon)`: - Fetch planposition, verify konto_id exists - Default betrag_limit to sum of budget_gwg + budget_anlagen + budget_instandhaltung - Call `createBudgetFreigabe` with `planposition_id` set Route: `POST /planung/positionen/:posId/freigabe` — manage_accounts ### T6 — Frontend: Budget Freigaben Tab + Sidebar **Teammate**: frontend | **blockedBy**: T3 **Files**: `buchhaltung.types.ts`, `buchhaltung.ts`, `Buchhaltung.tsx`, `Sidebar.tsx` Types: - `BudgetFreigabe` — id, konto_id, user_id, beschreibung, betrag_limit, planposition_id, aktiv, konto_bezeichnung, user_name - `BudgetFreigabeFormData` — konto_id, user_id, beschreibung?, betrag_limit? Service: `getBudgetFreigaben`, `getMyBudgetFreigaben`, `createBudgetFreigabe`, `updateBudgetFreigabe`, `deleteBudgetFreigabe` Buchhaltung.tsx: - Add tab 3 "Freigaben" (visible only with `manage_accounts`) - Table: Konto, Benutzer, Beschreibung, Betrag-Limit, Status, Aktionen - Create/Edit dialog with Konto select, User select, Betrag-Limit, Beschreibung - Pattern: follow TransaktionenTab Table+Dialog structure Sidebar.tsx (~line 155): add `{ text: 'Freigaben', path: '/buchhaltung?tab=3' }` to buchhaltung subItems ### T7 — Frontend: Split Booking Dialog **Teammate**: frontend | **blockedBy**: T4 **Files**: `buchhaltung.types.ts`, `buchhaltung.ts`, `Buchhaltung.tsx` Types: add `SplitRow { betrag: number; konto_id: number; bankkonto_id?: number }` Service: update `buchenTransaktion(id, splits?)` to POST body with optional splits array Buchhaltung.tsx: - Add `BookingDialog` component (inline): - Shows transaction details (betrag, beschreibung) - "Einfach buchen" button for simple booking (no splits) - "Aufteilen" toggle reveals split rows - Each row: betrag input, konto select, bankkonto select, remove button - "Zeile hinzufuegen" button - Sum validation with remaining amount display - "Buchen" button - Replace buchen IconButton click: open BookingDialog instead of direct mutation - Update `buchenMut` to accept `{ id, splits? }` ### T8 — Frontend: Plan Position → Freigabe Action **Teammate**: frontend | **blockedBy**: T5, T6 **Files**: `buchhaltung.ts`, `HaushaltsplanDetail.tsx` Service: add `createBudgetFreigabeFromPosition(posId, { user_id, betrag_limit?, beschreibung? })` HaushaltsplanDetail.tsx: - Add "Freigabe erstellen" IconButton per position row (only when canManage && pos.konto_id set) - Dialog: User select (fetch members), Betrag-Limit (pre-filled from position total), Beschreibung (pre-filled from position name) - Pattern: follow existing position edit dialog --- ## Teammate Assignments | Teammate | Tasks | Mode | |----------|-------|------| | database | T1, T2 | bypassPermissions | | backend | T3, T4, T5 | bypassPermissions | | frontend | T6, T7, T8 | bypassPermissions | ## Verification 1. Migration: check tables exist via backend startup logs 2. Backend: test budget-freigaben CRUD via curl/frontend 3. Frontend: verify Freigaben tab renders, BookingDialog opens on buchen click 4. Split booking: create pending transaction, open dialog, add split rows, book 5. Plan→Freigabe: open HaushaltsplanDetail, click freigabe button on position with konto_id