feat: recurring budget entries (lazy materialise) + preferred Satispay badge
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import uuid
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, Cookie, Depends, HTTPException, Request
|
||||
@@ -36,9 +37,44 @@ def _save(data: dict) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _materialise_recurring(data: dict) -> bool:
|
||||
"""Auto-add a copy of each recurring entry for the current month if absent.
|
||||
|
||||
Returns True if data was modified (caller should save).
|
||||
Copies reference the template via recurring_from so they're not re-generated.
|
||||
"""
|
||||
current = date.today().strftime("%Y-%m")
|
||||
templates = [e for e in data.get("entries", []) if e.get("recurring")]
|
||||
if not templates:
|
||||
return False
|
||||
modified = False
|
||||
for t in templates:
|
||||
if t["month"] == current:
|
||||
continue # template is already this month
|
||||
already = any(
|
||||
e.get("recurring_from") == t["id"] and e["month"] == current
|
||||
for e in data["entries"]
|
||||
)
|
||||
if not already:
|
||||
data["entries"].append({
|
||||
"id": str(uuid.uuid4())[:8],
|
||||
"type": t["type"],
|
||||
"label": t["label"],
|
||||
"amount_eur": t["amount_eur"],
|
||||
"month": current,
|
||||
"note": t.get("note", ""),
|
||||
"recurring_from": t["id"],
|
||||
})
|
||||
modified = True
|
||||
return modified
|
||||
|
||||
|
||||
@router.get("/api/budget")
|
||||
async def get_budget() -> JSONResponse:
|
||||
return JSONResponse(_load())
|
||||
data = _load()
|
||||
if _materialise_recurring(data):
|
||||
_save(data)
|
||||
return JSONResponse(data)
|
||||
|
||||
|
||||
@router.post("/api/budget/settings")
|
||||
@@ -76,7 +112,7 @@ async def add_entry(
|
||||
raise HTTPException(400, "month must be YYYY-MM")
|
||||
note = str(body.get("note", "")).strip()
|
||||
|
||||
entry = {
|
||||
entry: dict = {
|
||||
"id": str(uuid.uuid4())[:8],
|
||||
"type": entry_type,
|
||||
"label": label,
|
||||
@@ -84,6 +120,8 @@ async def add_entry(
|
||||
"month": month,
|
||||
"note": note,
|
||||
}
|
||||
if body.get("recurring"):
|
||||
entry["recurring"] = True
|
||||
data = _load()
|
||||
data.setdefault("entries", []).append(entry)
|
||||
_save(data)
|
||||
@@ -113,6 +151,11 @@ async def update_entry(
|
||||
entry["month"] = str(body["month"]).strip()
|
||||
if "note" in body:
|
||||
entry["note"] = str(body["note"]).strip()
|
||||
if "recurring" in body:
|
||||
if body["recurring"]:
|
||||
entry["recurring"] = True
|
||||
else:
|
||||
entry.pop("recurring", None)
|
||||
_save(data)
|
||||
return JSONResponse(entry)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user