Files
dashboard/.claude/plans/tingly-cooking-locket.md

7.8 KiB

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:

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:

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