from datetime import date from decimal import Decimal from app.engine.breakdown import Filters from app.models.tables import (Category, Loan, PlannedItem, RecurringItem, Scenario, ScenarioLoan, ScenarioModifier, ScenarioPlannedItem) from app.services.breakdown import PLAN_NAME, compute_breakdown M = date(2030, 3, 1) D = Decimal def _basis(db): wohnen = Category(name="Wohnen") db.add(wohnen) db.flush() miete = RecurringItem(name="Miete", amount=D("-1000.00"), rhythm="monthly", due_day=1, category_id=wohnen.id) gehalt = RecurringItem(name="Gehalt", amount=D("3000.00"), rhythm="monthly", due_day=1) reise = PlannedItem(name="Reise", amount=D("-600.00"), due=date(2030, 3, 20)) k1 = Loan(name="K1", principal=D("1200.00"), annual_rate_pct=D("0"), term_months=12, payout_date=date(2030, 1, 15), repayment_type="annuity") k2 = Loan(name="K2", principal=D("2400.00"), annual_rate_pct=D("0"), term_months=12, payout_date=date(2030, 1, 15), repayment_type="annuity") db.add_all([miete, gehalt, reise, k1, k2]) db.commit() return wohnen, miete, k1, k2 def test_plan_name(): assert PLAN_NAME == "System-Plan" def test_system_plan_alle_kredite_ohne_modifikatoren(db): wohnen, miete, k1, k2 = _basis(db) b = compute_breakdown(db, date(2030, 3, 17), None, Filters()) assert b.month == M assert {r.name: r.amount for r in b.rows} == { "Miete": D("-1000.00"), "Gehalt": D("3000.00"), "Reise": D("-600.00"), "K1": D("-100.00"), "K2": D("-200.00")} assert {r.name: r.category for r in b.rows}["Miete"] == "Wohnen" assert {r.name: r.category for r in b.rows}["Reise"] == "unkategorisiert" assert {r.name: r.category for r in b.rows}["K1"] == "System-Kreditraten" assert b.summary.income == D("3000.00") and b.summary.costs == D("1900.00") def test_szenario_nur_zugeordnete_kredite_und_flags(db): wohnen, miete, k1, k2 = _basis(db) sc = Scenario(name="S", description="", include_recurring=True, include_planned=False) db.add(sc) db.flush() db.add_all([ ScenarioLoan(scenario_id=sc.id, loan_id=k1.id), ScenarioModifier(scenario_id=sc.id, target_type="category", target_id=wohnen.id, kind="percent", value=D("50")), ScenarioPlannedItem(scenario_id=sc.id, name="Szenario-Kauf", amount=D("-90.00"), due=date(2030, 3, 5)), ]) db.commit() b = compute_breakdown(db, M, sc, Filters()) got = {r.name: (r.amount, r.category) for r in b.rows} assert got == {"Miete": (D("-500.00"), "Wohnen"), "Gehalt": (D("3000.00"), "unkategorisiert"), "Szenario-Kauf": (D("-90.00"), "unkategorisiert"), "K1": (D("-100.00"), "System-Kreditraten")} def test_szenario_ohne_fixposten(db): _basis(db) sc = Scenario(name="S", description="", include_recurring=False, include_planned=True) db.add(sc) db.commit() assert [r.name for r in compute_breakdown(db, M, sc, Filters()).rows] == ["Reise"] def test_konflikt_nur_wenn_beide_zeilen_enthalten(db): _basis(db) rate = RecurringItem(name="Autorate", amount=D("-100.00"), rhythm="monthly", due_day=15) db.add(rate) db.commit() b = compute_breakdown(db, M, None, Filters()) assert [(k.posten_name, k.kredit_name) for k in b.conflicts] == [("Autorate", "K1")] assert compute_breakdown(db, M, None, Filters(loans=False)).conflicts == [] assert compute_breakdown(db, M, None, Filters(recurring=False)).conflicts == [] sc = Scenario(name="Ohne K1", description="") db.add(sc) db.commit() assert compute_breakdown(db, M, sc, Filters()).conflicts == []