feat: Phase 2 – Budget-Engine + Direktzugriff (http://IP:Port)

Budget:
- Modelle + Migration 0003: budget_template_lines, planned_income_lines,
  budget_periods, budget_period_lines.
- services/budget: Perioden-Grenzen (einstellbarer Starttag, Label nach Endmonat),
  Standard-Vorlage vs. Monats-Override (Snapshot-on-Customize), Soll/Ist inkl.
  budgetierter Sparen-Transfers, geplante Einnahmen (Soll/Ist).
- routers/budgets: Vorlage, geplante Einnahmen (CRUD), Monats-Soll/Ist,
  Monats-Override, Zurücksetzen. pytest: Perioden, Soll/Ist, Override/Reset, Einnahmen.
- Frontend: Budget-Seite (Monat + Standard-Budget), Fortschrittsbalken grün/rot,
  Monats-Navigation, editierbare Soll-Beträge; Sparen-Option im Transfer-Formular.

Deployment-Fix (Nutzer-Anforderung: http://IP:Port muss funktionieren):
- Dienst bindet an APP_BIND_HOST=0.0.0.0 (direkt im LAN erreichbar).
- APP_COOKIE_SECURE=false als Standard (sonst kein Login über reines HTTP);
  für reinen HTTPS-Betrieb umstellbar. Doku + install.sh entsprechend angepasst.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-19 19:16:27 +02:00
co-authored by Claude Opus 4.8
parent d692c04cc1
commit 8ed4bf5254
21 changed files with 1352 additions and 22 deletions
+110
View File
@@ -261,3 +261,113 @@
.nowrap {
white-space: nowrap;
}
/* Budget. */
.budget-navbar {
display: flex;
align-items: center;
gap: var(--sp-3);
}
.budget-month {
font-size: 1.05rem;
min-width: 170px;
text-align: center;
}
.flip {
transform: rotate(180deg);
}
.budget-group + .budget-group {
margin-top: var(--sp-4);
}
.budget-group-head {
display: flex;
align-items: center;
gap: var(--sp-2);
padding: var(--sp-2) 0;
margin-bottom: 4px;
border-bottom: 2px solid var(--border-strong);
}
.budget-cat {
display: flex;
align-items: center;
gap: var(--sp-3);
padding: 7px 0;
border-bottom: 1px solid var(--border);
}
.budget-cat:last-child {
border-bottom: none;
}
.budget-cat-name {
display: flex;
align-items: center;
gap: var(--sp-2);
min-width: 0;
}
.budget-cat-bar {
flex: 1;
}
.amount-input {
width: 96px;
text-align: right;
margin-top: 0;
padding: 5px 8px;
font-variant-numeric: tabular-nums;
}
.budget-actual {
width: 96px;
text-align: right;
font-variant-numeric: tabular-nums;
}
.budget-slash {
margin: 0 -4px;
}
.budget-bar {
height: 7px;
background: var(--surface-2);
border-radius: 4px;
overflow: hidden;
}
.budget-bar-fill {
height: 100%;
background: var(--ok);
border-radius: 4px;
transition: width 0.2s;
}
.budget-bar-fill.over {
background: var(--danger);
}
.budget-total-row.card-total {
display: flex;
align-items: center;
gap: var(--sp-2);
padding-top: var(--sp-3);
margin-top: var(--sp-2);
border-top: 2px solid var(--border-strong);
}
tr.budget-total-row td {
border-top: 2px solid var(--border-strong);
padding-top: var(--sp-2);
}
/* Sparen-Option im Transfer-Formular. */
.savings-opt {
padding: var(--sp-3);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
background: var(--surface-2);
margin-bottom: var(--sp-4);
}
.checkline {
display: flex;
align-items: center;
gap: var(--sp-2);
margin-bottom: var(--sp-2);
}
.checkline input[type="checkbox"] {
width: auto;
margin-top: 0;
}
.checkline span {
color: var(--text);
font-weight: 560;
}