183 lines
7.5 KiB
Python
183 lines
7.5 KiB
Python
from __future__ import annotations
|
|
|
|
from decimal import Decimal
|
|
|
|
from app.extensions import db
|
|
from app.models import Account, AllocationSuggestion, Month, User
|
|
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
|
|
|
|
|
|
def test_personal_account_names_fill_with_admin_if_only_one_editor_is_active(app):
|
|
editor_b = User.query.filter_by(username="mitglied2").first()
|
|
editor_b.is_active = False
|
|
db.session.commit()
|
|
|
|
personal_labels = personal_account_names()
|
|
|
|
assert personal_labels["persoenlich-flo"] == "Person A"
|
|
assert personal_labels["persoenlich-desi"] == "Admin"
|