From 8bf37c82b6438cd6edb9ec7729d69b18283cde3c Mon Sep 17 00:00:00 2001 From: Florian Heinz Date: Tue, 21 Apr 2026 21:37:04 +0200 Subject: [PATCH] fix: start with empty budgets and savings categories --- app/planning/routes.py | 67 ++++++++++++--------------- app/seed.py | 72 ++++++++++++++++++++++-------- app/templates/planning/detail.html | 70 ++++++++++++++++------------- 3 files changed, 123 insertions(+), 86 deletions(-) diff --git a/app/planning/routes.py b/app/planning/routes.py index b7309e6..2d2b490 100644 --- a/app/planning/routes.py +++ b/app/planning/routes.py @@ -266,7 +266,11 @@ def detail(label: str): ) month_values = {item.entry_id: item for item in month.entry_values} distribution_bucket = { - "account": None, + "account": type( + "SyntheticAccount", + (), + {"name": "Sparen & Verteilung", "slug": "sparen-und-verteilung"}, + )(), "categories": [], "total": Decimal("0.00"), } @@ -337,12 +341,6 @@ def detail(label: str): } ) if account.slug in {"sparen", "urlaub", "freizeit"}: - if distribution_bucket["account"] is None: - distribution_bucket["account"] = type( - "SyntheticAccount", - (), - {"name": "Sparen & Verteilung", "slug": "sparen-und-verteilung"}, - )() distribution_bucket["categories"].append( { "category": type( @@ -372,12 +370,6 @@ def detail(label: str): distribution_bucket["total"] += account_total continue if account.slug in {"persoenlich-flo", "persoenlich-desi"}: - if distribution_bucket["account"] is None: - distribution_bucket["account"] = type( - "SyntheticAccount", - (), - {"name": "Sparen & Verteilung", "slug": "sparen-und-verteilung"}, - )() personal_category = next( (item for item in distribution_bucket["categories"] if item.get("is_personal_split")), None, @@ -412,32 +404,31 @@ def detail(label: str): planning_accounts.append( {"account": account, "categories": category_cards, "total": account_total} ) - if distribution_bucket["account"] is not None: - personal_category = next( - (item for item in distribution_bucket["categories"] if item.get("is_personal_split")), - None, + personal_category = next( + (item for item in distribution_bucket["categories"] if item.get("is_personal_split")), + None, + ) + if personal_category is not None: + personal_category["distribution_suggestion_total"] = max( + -summary.allocation_total, + summary.remainder, ) - if personal_category is not None: - personal_category["distribution_suggestion_total"] = max( - -summary.allocation_total, - summary.remainder, - ) - personal_category["distribution_items"] = [ - { - "slug": slug, - "label": personal_label_map.get(slug, slug), - "allocation": allocations_by_slug.get(slug), - "suggestion": suggestions_by_slug.get(slug), - "remaining_amount": max( - Decimal("0.00"), - to_decimal(suggestions_by_slug.get(slug).suggested_amount if suggestions_by_slug.get(slug) else 0) - - to_decimal(allocations_by_slug.get(slug).amount if allocations_by_slug.get(slug) else 0), - ), - "auto_amount": to_decimal(allocations_by_slug.get(slug).amount if allocations_by_slug.get(slug) else 0), - } - for slug in ("persoenlich-flo", "persoenlich-desi") - ] - planning_accounts.insert(0, distribution_bucket) + personal_category["distribution_items"] = [ + { + "slug": slug, + "label": personal_label_map.get(slug, slug), + "allocation": allocations_by_slug.get(slug), + "suggestion": suggestions_by_slug.get(slug), + "remaining_amount": max( + Decimal("0.00"), + to_decimal(suggestions_by_slug.get(slug).suggested_amount if suggestions_by_slug.get(slug) else 0) + - to_decimal(allocations_by_slug.get(slug).amount if allocations_by_slug.get(slug) else 0), + ), + "auto_amount": to_decimal(allocations_by_slug.get(slug).amount if allocations_by_slug.get(slug) else 0), + } + for slug in ("persoenlich-flo", "persoenlich-desi") + ] + planning_accounts.insert(0, distribution_bucket) previous_month = month_service.previous_month(month.year, month.month) budget_categories = db.session.scalars( select(Category) diff --git a/app/seed.py b/app/seed.py index ca35b8d..fc12ca7 100644 --- a/app/seed.py +++ b/app/seed.py @@ -137,6 +137,12 @@ EXAMPLE_ENTRY_NAMES = { for entry_name in entries } +EXAMPLE_CATEGORY_KEYS = { + (account_slug, category_slug) + for account_slug, account_data in ACCOUNT_TREE.items() + for category_slug in account_data["categories"].keys() +} + def slugify(value: str) -> str: return ( @@ -150,28 +156,53 @@ def slugify(value: str) -> str: ) +def _entry_has_user_data(entry: Entry) -> bool: + for value in entry.monthly_values: + if ( + to_decimal(value.planned_amount) != Decimal("0.00") + or value.note + or value.created_by is not None + or value.updated_by is not None + ): + return True + if entry.share_rules: + return True + return False + + def _deactivate_placeholder_entries() -> None: - example_entries = ( - Entry.query.filter(Entry.name.in_(sorted(EXAMPLE_ENTRY_NAMES)), Entry.is_active.is_(True)).all() - ) + example_entries = Entry.query.filter( + Entry.name.in_(sorted(EXAMPLE_ENTRY_NAMES)), + Entry.is_active.is_(True), + ).all() for entry in example_entries: - has_user_data = False - for value in entry.monthly_values: - if ( - to_decimal(value.planned_amount) != Decimal("0.00") - or value.note - or value.created_by is not None - or value.updated_by is not None - ): - has_user_data = True - break - if entry.share_rules: - has_user_data = True - if has_user_data: + if _entry_has_user_data(entry): continue entry.is_active = False +def _deactivate_placeholder_categories() -> None: + categories = ( + Category.query.join(Account) + .filter(Category.is_active.is_(True), Account.is_active.is_(True)) + .all() + ) + for category in categories: + account = category.account + if account is None or (account.slug, category.slug) not in EXAMPLE_CATEGORY_KEYS: + continue + active_entries = [entry for entry in category.entries if entry.is_active] + if not active_entries: + category.is_active = False + category.community_account_id = None + continue + if all(entry.name in EXAMPLE_ENTRY_NAMES and not _entry_has_user_data(entry) for entry in active_entries): + for entry in active_entries: + entry.is_active = False + category.is_active = False + category.community_account_id = None + + def seed_data(include_example_entries: bool = False) -> None: # Basisdaten nur für die fachliche Grundstruktur, ohne Demo-Benutzer, # Beispiel-Personen oder vorausgefüllte Monatsdaten. @@ -225,6 +256,8 @@ def seed_data(include_example_entries: bool = False) -> None: sort_order += 1 category_sort = 1 account_categories[account_slug] = {} + if not include_example_entries: + continue for category_slug, entries in account_data["categories"].items(): category = Category.query.filter_by(account_id=account.id, slug=category_slug).first() legacy_slug = LEGACY_CATEGORY_SLUGS.get((account_slug, category_slug)) @@ -254,8 +287,6 @@ def seed_data(include_example_entries: bool = False) -> None: category.community_account_id = community_accounts["hauptkonto"].id account_categories[account_slug][category_slug] = category category_sort += 1 - if not include_example_entries: - continue for index, entry_name in enumerate(entries, start=1): default_amount = Decimal("0.00") if entry_name == "Miete": @@ -362,6 +393,7 @@ def seed_data(include_example_entries: bool = False) -> None: "Person 2", } else: + _deactivate_placeholder_categories() _deactivate_placeholder_entries() for category in gemeinschaft.categories: if category.slug not in ACCOUNT_TREE["gemeinschaftskonto"]["categories"]: @@ -369,6 +401,10 @@ def seed_data(include_example_entries: bool = False) -> None: elif category.community_account_id is None: category.community_account_id = community_accounts["hauptkonto"].id + if not include_example_entries: + _deactivate_placeholder_categories() + _deactivate_placeholder_entries() + db.session.commit() diff --git a/app/templates/planning/detail.html b/app/templates/planning/detail.html index d24709e..6a9ca99 100644 --- a/app/templates/planning/detail.html +++ b/app/templates/planning/detail.html @@ -117,35 +117,35 @@
{% for account_data in planning_accounts %} - {% if account_data.categories %} - {% endfor %}