release: publish saldo 0.1.0

This commit is contained in:
2026-04-21 21:17:36 +02:00
commit 6f5e704739
95 changed files with 9196 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
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