feat(api): Core-API mit Authentifizierung, CRUD und Monatsreport
- Anmeldung über Argon2id und JWT in httpOnly-Cookies, Refresh mit echter Rotation über die neue Tabelle refresh_token - AuthProvider-Protokoll als Vorbereitung für OIDC, Administrator-Anlage beim Erststart mit erzwungenem Passwortwechsel - CRUD für Konten, Kategorien (zweistufiger Baum), Firmen, Recurrences, Preisversionen, Buchungen, Budgets, Vorlagen und Sparziele - Fälligkeiten mit Overlay-Logik: abrufen, bestätigen, auslassen, zurücksetzen - Kontosalden zum Stichtag, Monatsübersicht mit Plan-Ist-Vergleich - SECRET_KEY jetzt mindestens 32 Zeichen; Platzhalter in Produktion abgelehnt - 61 neue Integrationstests, insgesamt 148 grün Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014e7t8UpmoVNMtWivY5LiSH
This commit is contained in:
@@ -17,12 +17,12 @@ from datetime import date, datetime, timedelta
|
||||
from decimal import ROUND_HALF_UP, Decimal
|
||||
from functools import lru_cache
|
||||
from typing import Protocol, runtime_checkable
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
import holidays
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from dateutil.rrule import rrulestr
|
||||
|
||||
from app.core.clock import today
|
||||
from app.models.enums import BusinessDayShift, EntryKind, OccurrenceStatus
|
||||
|
||||
ZERO = Decimal("0.00")
|
||||
@@ -247,7 +247,7 @@ def next_dates(
|
||||
after: date | None = None,
|
||||
) -> list[date]:
|
||||
"""Die nächsten `count` nominalen Termine nach `after` (einschließlich)."""
|
||||
start = after or _today()
|
||||
start = after or today()
|
||||
end = start
|
||||
results: list[date] = []
|
||||
# Fenster schrittweise vergrößern, bis genug Termine gefunden sind.
|
||||
@@ -391,7 +391,7 @@ def installments_remaining(
|
||||
if not total:
|
||||
return None
|
||||
|
||||
reference = as_of or _today()
|
||||
reference = as_of or today()
|
||||
schedule = expand(
|
||||
recurrence,
|
||||
recurrence.dtstart,
|
||||
@@ -437,7 +437,7 @@ def contract_term(recurrence: RecurrenceLike, as_of: date | None = None) -> Cont
|
||||
return None
|
||||
|
||||
start = recurrence.contract_start or recurrence.dtstart
|
||||
reference = as_of or _today()
|
||||
reference = as_of or today()
|
||||
|
||||
term_start = start
|
||||
term_end = start + relativedelta(months=recurrence.contract_min_term_months) - timedelta(days=1)
|
||||
@@ -492,7 +492,7 @@ def annual_burden(
|
||||
Bewusst kein fester Intervallfaktor: eine halbjährliche Zahlung, die im Fenster
|
||||
nur einmal fällt, ergibt auch nur eine Belastung.
|
||||
"""
|
||||
start = as_of or _today()
|
||||
start = as_of or today()
|
||||
end = start + relativedelta(years=1) - timedelta(days=1)
|
||||
schedule = expand(recurrence, start, end, amount_versions=amount_versions)
|
||||
return _sum(item.amount for item in schedule)
|
||||
@@ -518,11 +518,6 @@ def monthly_reserve(
|
||||
# --- Interne Helfer -----------------------------------------------------------
|
||||
|
||||
|
||||
def _today() -> date:
|
||||
"""Heutiges Datum in der fachlichen Zeitzone."""
|
||||
return datetime.now(ZoneInfo("Europe/Berlin")).date()
|
||||
|
||||
|
||||
def _as_datetime(day: date) -> datetime:
|
||||
"""dateutil rechnet intern mit datetime; die Uhrzeit ist fachlich bedeutungslos."""
|
||||
return datetime(day.year, day.month, day.day)
|
||||
|
||||
Reference in New Issue
Block a user