from __future__ import annotations from decimal import Decimal from app.extensions import db from app.models import Account, AllocationSuggestion, Month from app.utils.users import personal_account_names def test_comparison_to_previous_month(app): service = app.extensions["saldo.month_service"] april = Month.query.filter_by(label="2026-04").first() may = service.copy_month(april, 2026, 5, auto_created=True) may.incomes[0].amount = Decimal("3300.00") may.entry_values[0].planned_amount = Decimal("950.00") db.session.commit() summary = service.compute_summary(may) assert summary.deltas["income_delta"] == Decimal("200.00") assert summary.deltas["cost_delta"] == Decimal("30.00") def test_suggestion_logic_reacts_to_income_change(app): service = app.extensions["saldo.month_service"] month = Month.query.filter_by(label="2026-04").first() month.incomes[0].amount = Decimal("3500.00") service.refresh_suggestions(month, "Test") db.session.commit() suggestions = AllocationSuggestion.query.filter_by(month_id=month.id).all() assert len(suggestions) == 5 sparen = next(item for item in suggestions if item.target_account.slug == "sparen") urlaub = next(item for item in suggestions if item.target_account.slug == "urlaub") freizeit = next(item for item in suggestions if item.target_account.slug == "freizeit") assert sparen.suggested_amount > Decimal("0.00") assert urlaub.suggested_amount > Decimal("0.00") assert freizeit.suggested_amount > Decimal("0.00") def test_accepting_suggestions_updates_allocations(app): service = app.extensions["saldo.month_service"] allocation_service = app.extensions["saldo.allocation_service"] month = Month.query.filter_by(label="2026-04").first() service.refresh_suggestions(month, "Test") allocation_service.accept_all(month) service.sync_distribution_entries_from_allocations(month) db.session.commit() assert all(item.source == "accepted_suggestion" for item in month.allocations if not item.is_locked) flo_allocation = next(item for item in month.allocations if item.target_account.slug == "persoenlich-flo") flo_entry_value = next( item for item in month.entry_values if item.entry.name == "Person 1" and item.entry.category.account.slug == "persoenlich-flo" ) assert flo_entry_value.planned_amount == flo_allocation.amount def test_personal_distribution_entry_updates_matching_allocation(app): service = app.extensions["saldo.month_service"] month = Month.query.filter_by(label="2026-04").first() flo_value = next( item for item in month.entry_values if item.entry.name == "Person 1" and item.entry.category.account.slug == "persoenlich-flo" ) flo_value.planned_amount = Decimal("333.33") changed = service.sync_distribution_allocation_from_entry( month, flo_value.entry, mark_manual=True, ) db.session.commit() flo_allocation = next(item for item in month.allocations if item.target_account.slug == "persoenlich-flo") personal_labels = personal_account_names() assert changed is True assert flo_allocation.amount == Decimal("333.33") assert flo_allocation.label == personal_labels["persoenlich-flo"] assert flo_allocation.source == "manual" def test_personal_split_changes_personal_suggestions(app): service = app.extensions["saldo.month_service"] month = Month.query.filter_by(label="2026-04").first() month.personal_split_desi_pct = Decimal("40.00") service.refresh_suggestions(month, "Split angepasst") db.session.commit() flo_suggestion = next(item for item in month.suggestions if item.target_account.slug == "persoenlich-flo") desi_suggestion = next(item for item in month.suggestions if item.target_account.slug == "persoenlich-desi") assert flo_suggestion.suggested_amount > desi_suggestion.suggested_amount def test_manual_priority_accounts_leave_remaining_suggestions_for_personal_only(app): service = app.extensions["saldo.month_service"] month = Month.query.filter_by(label="2026-04").first() service.refresh_suggestions(month, "Neu berechnen") db.session.commit() allocations = {item.target_account.slug: item.amount for item in month.allocations} suggestions = {item.target_account.slug: item.suggested_amount for item in month.suggestions} summary = service.compute_summary(month) assert suggestions["sparen"] >= allocations["sparen"] assert suggestions["urlaub"] >= allocations["urlaub"] assert suggestions["freizeit"] >= allocations["freizeit"] assert suggestions["persoenlich-flo"] == allocations["persoenlich-flo"] assert suggestions["persoenlich-desi"] == allocations["persoenlich-desi"] assert summary.remainder == Decimal("0.00") def test_personal_allocation_is_reduced_when_fixed_costs_rise(app): service = app.extensions["saldo.month_service"] month = Month.query.filter_by(label="2026-04").first() fixed_cost_value = next(item for item in month.entry_values if item.entry.name == "Miete") original_fixed_cost = fixed_cost_value.planned_amount original_total = sum( item.amount for item in month.allocations if item.target_account.slug in {"persoenlich-flo", "persoenlich-desi"} ) fixed_cost_value.planned_amount = original_fixed_cost + Decimal("50.00") service.refresh_suggestions(month, "Fixkosten gestiegen") db.session.commit() personal_total = sum( item.amount for item in month.allocations if item.target_account.slug in {"persoenlich-flo", "persoenlich-desi"} ) summary = service.compute_summary(month) assert personal_total == original_total - Decimal("50.00") assert summary.remainder == Decimal("0.00") def test_personal_allocation_can_fall_to_zero_before_remainder_turns_negative(app): service = app.extensions["saldo.month_service"] month = Month.query.filter_by(label="2026-04").first() fixed_cost_value = next(item for item in month.entry_values if item.entry.name == "Miete") fixed_cost_value.planned_amount = Decimal("9999.99") service.refresh_suggestions(month, "Defizit") db.session.commit() flo_allocation = next(item for item in month.allocations if item.target_account.slug == "persoenlich-flo") desi_allocation = next(item for item in month.allocations if item.target_account.slug == "persoenlich-desi") summary = service.compute_summary(month) assert flo_allocation.amount == Decimal("0.00") assert desi_allocation.amount == Decimal("0.00") assert summary.remainder < Decimal("0.00") def test_seeded_distribution_entries_are_marked_as_allocation_targets(app): month = Month.query.filter_by(label="2026-04").first() target_entries = { item.entry.name: item.entry.is_allocation_target for item in month.entry_values if item.entry.name in {"Sparziel", "Reisebudget", "Freizeitbudget", "Person 1", "Person 2"} } assert target_entries["Sparziel"] is True assert target_entries["Reisebudget"] is True assert target_entries["Freizeitbudget"] is True assert target_entries["Person 1"] is True assert target_entries["Person 2"] is True