- Neuer schedule_for() in app/engine/loans.py: vereinheitlicht Tilgungsplan-Erstellung - Laufzeit-Validierung in LoanIn und LoanPatch: 1–1200 Monate - Aufrufstellen aktualisiert (routers/planning.py, services/breakdown.py, services/projection_service.py, services/planning_chart.py) - Keine direkten loan_schedule(Decimal(...)) Aufrufe außerhalb engine/loans.py mehr - Tests für schedule_for und Laufzeit-Grenzen (1, 1200, sowie 0/-5/1201 Ablehnung) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
46 lines
1.9 KiB
Python
46 lines
1.9 KiB
Python
"""Aufteilung (Ausbaustufe 13): Brücke DB -> engine/breakdown.py.
|
|
|
|
scenario=None ist „System-Plan" (reine Planung, alle Kredite, keine
|
|
Modifikatoren) - dieselbe Datenbasis wie load_scenario_inputs(None).
|
|
"""
|
|
from dataclasses import dataclass
|
|
from datetime import date
|
|
|
|
from sqlalchemy import select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.engine.breakdown import (BreakdownRow, Filters, PlainLoan, Summary,
|
|
breakdown_rows, summarize)
|
|
from app.engine.loans import schedule_for
|
|
from app.models.tables import Category, Scenario
|
|
from app.reserved import SYSTEM_PREFIX
|
|
from app.services.double_count import Konflikt, all_conflicts
|
|
from app.services.planning_chart import KREDITRATEN, UNKATEGORISIERT
|
|
from app.services.projection_service import load_scenario_inputs
|
|
|
|
PLAN_NAME = f"{SYSTEM_PREFIX}Plan"
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Breakdown:
|
|
month: date
|
|
rows: list[BreakdownRow]
|
|
summary: Summary
|
|
conflicts: list[Konflikt]
|
|
|
|
|
|
def compute_breakdown(session: Session, month: date, scenario: Scenario | None,
|
|
filters: Filters) -> Breakdown:
|
|
inputs = load_scenario_inputs(session, scenario)
|
|
names = {c.id: c.name for c in session.execute(select(Category)).scalars()}
|
|
loans = [PlainLoan(l.id, l.name, schedule_for(l)) for l in inputs.loans]
|
|
rows = breakdown_rows(month, inputs.recurring, inputs.planned, loans,
|
|
inputs.modifiers, filters, category_names=names,
|
|
loan_category=KREDITRATEN, uncategorized=UNKATEGORISIERT)
|
|
# Doppelzählungs-Hinweis nur, wenn Fixposten UND Kredit hier als Zeile stehen.
|
|
posten = {r.recurring_id for r in rows if r.recurring_id is not None}
|
|
kredite = {r.loan_id for r in rows if r.loan_id is not None}
|
|
conflicts = [k for k in all_conflicts(session)
|
|
if k.posten_id in posten and k.kredit_id in kredite]
|
|
return Breakdown(month.replace(day=1), rows, summarize(rows), conflicts)
|