feat: einstellbarer Monatsbeginn zum Gehaltstag
Wer nach dem Gehaltseingang plant, stellt unter Einstellungen den Tag ein, ab dem ein neuer Monat zählt. Der Zeitraum läuft dann vom Gehaltstag bis zum Vortag des nächsten und trägt den Namen des Monats, in dem er beginnt: Mit dem 25. umfasst „September 2026“ den 25.09. bis zum 24.10. Ein Starttag jenseits der Monatslänge rutscht auf den Monatsletzten, sodass 31 verlässlich den letzten Tag des Monats meint. Dashboard, Cashflow-Kalender, Budgets, Zwölf-Monats-Vorschau, die Kategorienauswertung, der Monatsexport und die Benachrichtigung über überschrittene Budgets rechnen mit diesem Zeitraum. Budgets bleiben je Monat gepflegt; der Bezeichner ist weiterhin der Monatserste, nur der Schnitt verschiebt sich. Bestandsinstallationen bleiben beim Ersten. Die Einstellung liegt in einer einzeiligen Tabelle hinter GET/PUT /api/settings; die Monatsauswertungen liefern zusätzlich period_start und period_end. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Fj7mB1PGA1aDHyfdSGHgzD
This commit is contained in:
co-authored by
Claude Opus 5
parent
998d5867df
commit
0a6261fc55
@@ -6,8 +6,8 @@ from typing import Literal
|
||||
from fastapi import APIRouter, Query, Response, status
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.api.deps import DbSession
|
||||
from app.core.clock import month_end, month_start, today
|
||||
from app.api.deps import DbSession, MonthStartDay
|
||||
from app.core.clock import today
|
||||
from app.core.errors import ValidationError
|
||||
from app.models import Account, Merchant, Transaction
|
||||
from app.models.enums import EntryKind
|
||||
@@ -15,9 +15,11 @@ from app.schemas.common import ErrorResponse
|
||||
from app.services.export import CONTENT_TYPES, filename, to_csv, to_xlsx
|
||||
from app.services.reports import (
|
||||
category_names,
|
||||
current_period,
|
||||
flow_rows,
|
||||
flows,
|
||||
month_report,
|
||||
period_of,
|
||||
recurrence_rows,
|
||||
reserve_total,
|
||||
)
|
||||
@@ -156,15 +158,16 @@ async def export_recurrences(
|
||||
)
|
||||
async def export_month(
|
||||
session: DbSession,
|
||||
start_day: MonthStartDay,
|
||||
fmt: ExportFormat = Query(default="xlsx", alias="format"),
|
||||
month: date | None = Query(default=None, description="Beliebiger Tag im Monat."),
|
||||
) -> Response:
|
||||
monat = month_start(month or today())
|
||||
zeitraum = period_of(month, start_day) if month else current_period(start_day)
|
||||
namen = await category_names(session)
|
||||
|
||||
bewegungen = await flows(session, monat, month_end(monat))
|
||||
bewegungen = await flows(session, zeitraum.start, zeitraum.end)
|
||||
zeilen = flow_rows(bewegungen, namen)
|
||||
bericht = await month_report(session, monat)
|
||||
bericht = await month_report(session, zeitraum.key, start_day)
|
||||
|
||||
kennzahlen: list[dict[str, object]] = [
|
||||
{"Kennzahl": "Einnahmen (Plan)", "Betrag": bericht.planned.income},
|
||||
@@ -175,11 +178,11 @@ async def export_month(
|
||||
{"Kennzahl": "Saldo (Ist)", "Betrag": bericht.actual.balance},
|
||||
{"Kennzahl": "Fixkosten", "Betrag": bericht.fixed_costs},
|
||||
{"Kennzahl": "Variable Kosten", "Betrag": bericht.variable_costs},
|
||||
{"Kennzahl": "Rücklagen", "Betrag": await reserve_total(session, monat)},
|
||||
{"Kennzahl": "Rücklagen", "Betrag": await reserve_total(session, zeitraum)},
|
||||
{"Kennzahl": "Verfügbar nach Fixkosten", "Betrag": bericht.available_after_fixed},
|
||||
]
|
||||
|
||||
kennung = monat.strftime("%Y-%m")
|
||||
kennung = zeitraum.key.strftime("%Y-%m")
|
||||
if fmt == "csv":
|
||||
# CSV kennt keine Blätter – die Kennzahlen folgen nach einer Leerzeile.
|
||||
inhalt = to_csv(zeilen, FLOW_COLUMNS)
|
||||
|
||||
Reference in New Issue
Block a user