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
+32
View File
@@ -0,0 +1,32 @@
import { useEffect, useState } from "react";
import { centsToInput, parseAmountToCents } from "../format";
// Kleines, editierbares Betragsfeld (Cent). Speichert bei Verlassen/Enter,
// aber nur bei Änderung.
export default function EditableAmount({ cents, onSave, placeholder = "0,00" }) {
const [val, setVal] = useState(centsToInput(cents));
useEffect(() => { setVal(centsToInput(cents)); }, [cents]);
function commit() {
const c = parseAmountToCents(val) ?? 0;
if (c !== cents) onSave(c);
}
return (
<input
className="amount-input"
inputMode="decimal"
value={val}
placeholder={placeholder}
onChange={(e) => setVal(e.target.value)}
onBlur={commit}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
commit();
e.target.blur();
}
}}
/>
);
}