fix: start with empty budgets and savings categories
This commit is contained in:
+29
-38
@@ -266,7 +266,11 @@ def detail(label: str):
|
|||||||
)
|
)
|
||||||
month_values = {item.entry_id: item for item in month.entry_values}
|
month_values = {item.entry_id: item for item in month.entry_values}
|
||||||
distribution_bucket = {
|
distribution_bucket = {
|
||||||
"account": None,
|
"account": type(
|
||||||
|
"SyntheticAccount",
|
||||||
|
(),
|
||||||
|
{"name": "Sparen & Verteilung", "slug": "sparen-und-verteilung"},
|
||||||
|
)(),
|
||||||
"categories": [],
|
"categories": [],
|
||||||
"total": Decimal("0.00"),
|
"total": Decimal("0.00"),
|
||||||
}
|
}
|
||||||
@@ -337,12 +341,6 @@ def detail(label: str):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
if account.slug in {"sparen", "urlaub", "freizeit"}:
|
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(
|
distribution_bucket["categories"].append(
|
||||||
{
|
{
|
||||||
"category": type(
|
"category": type(
|
||||||
@@ -372,12 +370,6 @@ def detail(label: str):
|
|||||||
distribution_bucket["total"] += account_total
|
distribution_bucket["total"] += account_total
|
||||||
continue
|
continue
|
||||||
if account.slug in {"persoenlich-flo", "persoenlich-desi"}:
|
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(
|
personal_category = next(
|
||||||
(item for item in distribution_bucket["categories"] if item.get("is_personal_split")),
|
(item for item in distribution_bucket["categories"] if item.get("is_personal_split")),
|
||||||
None,
|
None,
|
||||||
@@ -412,32 +404,31 @@ def detail(label: str):
|
|||||||
planning_accounts.append(
|
planning_accounts.append(
|
||||||
{"account": account, "categories": category_cards, "total": account_total}
|
{"account": account, "categories": category_cards, "total": account_total}
|
||||||
)
|
)
|
||||||
if distribution_bucket["account"] is not None:
|
personal_category = next(
|
||||||
personal_category = next(
|
(item for item in distribution_bucket["categories"] if item.get("is_personal_split")),
|
||||||
(item for item in distribution_bucket["categories"] if item.get("is_personal_split")),
|
None,
|
||||||
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_items"] = [
|
||||||
personal_category["distribution_suggestion_total"] = max(
|
{
|
||||||
-summary.allocation_total,
|
"slug": slug,
|
||||||
summary.remainder,
|
"label": personal_label_map.get(slug, slug),
|
||||||
)
|
"allocation": allocations_by_slug.get(slug),
|
||||||
personal_category["distribution_items"] = [
|
"suggestion": suggestions_by_slug.get(slug),
|
||||||
{
|
"remaining_amount": max(
|
||||||
"slug": slug,
|
Decimal("0.00"),
|
||||||
"label": personal_label_map.get(slug, slug),
|
to_decimal(suggestions_by_slug.get(slug).suggested_amount if suggestions_by_slug.get(slug) else 0)
|
||||||
"allocation": allocations_by_slug.get(slug),
|
- to_decimal(allocations_by_slug.get(slug).amount if allocations_by_slug.get(slug) else 0),
|
||||||
"suggestion": suggestions_by_slug.get(slug),
|
),
|
||||||
"remaining_amount": max(
|
"auto_amount": to_decimal(allocations_by_slug.get(slug).amount if allocations_by_slug.get(slug) else 0),
|
||||||
Decimal("0.00"),
|
}
|
||||||
to_decimal(suggestions_by_slug.get(slug).suggested_amount if suggestions_by_slug.get(slug) else 0)
|
for slug in ("persoenlich-flo", "persoenlich-desi")
|
||||||
- to_decimal(allocations_by_slug.get(slug).amount if allocations_by_slug.get(slug) else 0),
|
]
|
||||||
),
|
planning_accounts.insert(0, distribution_bucket)
|
||||||
"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)
|
previous_month = month_service.previous_month(month.year, month.month)
|
||||||
budget_categories = db.session.scalars(
|
budget_categories = db.session.scalars(
|
||||||
select(Category)
|
select(Category)
|
||||||
|
|||||||
+54
-18
@@ -137,6 +137,12 @@ EXAMPLE_ENTRY_NAMES = {
|
|||||||
for entry_name in entries
|
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:
|
def slugify(value: str) -> str:
|
||||||
return (
|
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:
|
def _deactivate_placeholder_entries() -> None:
|
||||||
example_entries = (
|
example_entries = Entry.query.filter(
|
||||||
Entry.query.filter(Entry.name.in_(sorted(EXAMPLE_ENTRY_NAMES)), Entry.is_active.is_(True)).all()
|
Entry.name.in_(sorted(EXAMPLE_ENTRY_NAMES)),
|
||||||
)
|
Entry.is_active.is_(True),
|
||||||
|
).all()
|
||||||
for entry in example_entries:
|
for entry in example_entries:
|
||||||
has_user_data = False
|
if _entry_has_user_data(entry):
|
||||||
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:
|
|
||||||
continue
|
continue
|
||||||
entry.is_active = False
|
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:
|
def seed_data(include_example_entries: bool = False) -> None:
|
||||||
# Basisdaten nur für die fachliche Grundstruktur, ohne Demo-Benutzer,
|
# Basisdaten nur für die fachliche Grundstruktur, ohne Demo-Benutzer,
|
||||||
# Beispiel-Personen oder vorausgefüllte Monatsdaten.
|
# Beispiel-Personen oder vorausgefüllte Monatsdaten.
|
||||||
@@ -225,6 +256,8 @@ def seed_data(include_example_entries: bool = False) -> None:
|
|||||||
sort_order += 1
|
sort_order += 1
|
||||||
category_sort = 1
|
category_sort = 1
|
||||||
account_categories[account_slug] = {}
|
account_categories[account_slug] = {}
|
||||||
|
if not include_example_entries:
|
||||||
|
continue
|
||||||
for category_slug, entries in account_data["categories"].items():
|
for category_slug, entries in account_data["categories"].items():
|
||||||
category = Category.query.filter_by(account_id=account.id, slug=category_slug).first()
|
category = Category.query.filter_by(account_id=account.id, slug=category_slug).first()
|
||||||
legacy_slug = LEGACY_CATEGORY_SLUGS.get((account_slug, category_slug))
|
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
|
category.community_account_id = community_accounts["hauptkonto"].id
|
||||||
account_categories[account_slug][category_slug] = category
|
account_categories[account_slug][category_slug] = category
|
||||||
category_sort += 1
|
category_sort += 1
|
||||||
if not include_example_entries:
|
|
||||||
continue
|
|
||||||
for index, entry_name in enumerate(entries, start=1):
|
for index, entry_name in enumerate(entries, start=1):
|
||||||
default_amount = Decimal("0.00")
|
default_amount = Decimal("0.00")
|
||||||
if entry_name == "Miete":
|
if entry_name == "Miete":
|
||||||
@@ -362,6 +393,7 @@ def seed_data(include_example_entries: bool = False) -> None:
|
|||||||
"Person 2",
|
"Person 2",
|
||||||
}
|
}
|
||||||
else:
|
else:
|
||||||
|
_deactivate_placeholder_categories()
|
||||||
_deactivate_placeholder_entries()
|
_deactivate_placeholder_entries()
|
||||||
for category in gemeinschaft.categories:
|
for category in gemeinschaft.categories:
|
||||||
if category.slug not in ACCOUNT_TREE["gemeinschaftskonto"]["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:
|
elif category.community_account_id is None:
|
||||||
category.community_account_id = community_accounts["hauptkonto"].id
|
category.community_account_id = community_accounts["hauptkonto"].id
|
||||||
|
|
||||||
|
if not include_example_entries:
|
||||||
|
_deactivate_placeholder_categories()
|
||||||
|
_deactivate_placeholder_entries()
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -117,35 +117,35 @@
|
|||||||
|
|
||||||
<section class="account-board">
|
<section class="account-board">
|
||||||
{% for account_data in planning_accounts %}
|
{% for account_data in planning_accounts %}
|
||||||
{% if account_data.categories %}
|
<article class="panel account-panel premium-panel">
|
||||||
<article class="panel account-panel premium-panel">
|
<div class="panel-head account-head">
|
||||||
<div class="panel-head account-head">
|
<div>
|
||||||
<div>
|
<h2>{{ account_data.account.name }}</h2>
|
||||||
<h2>{{ account_data.account.name }}</h2>
|
<small>
|
||||||
<small>
|
Gesamtkosten {{ account_data.total|currency }}
|
||||||
Gesamtkosten {{ account_data.total|currency }}
|
{% if account_data.account.slug == "gemeinschaftskonto" %}
|
||||||
{% if account_data.account.slug == "gemeinschaftskonto" %}
|
· Jährlich {{ (account_data.total * 12)|currency }}
|
||||||
· Jährlich {{ (account_data.total * 12)|currency }}
|
{% endif %}
|
||||||
{% endif %}
|
</small>
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
{% if account_data.account.slug == "sparen-und-verteilung" %}
|
|
||||||
<button class="ghost-button icon-button" type="button" data-open-dialog="dialog-add-category" data-area="distribution" data-placeholder="Name Sparkonto" data-dialog-label="Sparkonto">
|
|
||||||
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
|
|
||||||
Sparkonto
|
|
||||||
</button>
|
|
||||||
{% elif account_data.account.slug == "gemeinschaftskonto" %}
|
|
||||||
<button class="ghost-button icon-button" type="button" data-open-dialog="dialog-add-category" data-area="budget" data-placeholder="Name Budget">
|
|
||||||
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
|
|
||||||
Kategorie
|
|
||||||
</button>
|
|
||||||
{% else %}
|
|
||||||
<button class="ghost-button icon-button" type="button" data-open-dialog="dialog-add-category" data-account-id="{{ account_data.account.id }}">
|
|
||||||
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
|
|
||||||
Kategorie
|
|
||||||
</button>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
{% if account_data.account.slug == "sparen-und-verteilung" %}
|
||||||
|
<button class="ghost-button icon-button" type="button" data-open-dialog="dialog-add-category" data-area="distribution" data-placeholder="Name Sparkonto" data-dialog-label="Sparkonto">
|
||||||
|
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
|
||||||
|
Sparkonto
|
||||||
|
</button>
|
||||||
|
{% elif account_data.account.slug == "gemeinschaftskonto" %}
|
||||||
|
<button class="ghost-button icon-button" type="button" data-open-dialog="dialog-add-category" data-area="budget" data-placeholder="Name Budget">
|
||||||
|
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
|
||||||
|
Kategorie
|
||||||
|
</button>
|
||||||
|
{% else %}
|
||||||
|
<button class="ghost-button icon-button" type="button" data-open-dialog="dialog-add-category" data-account-id="{{ account_data.account.id }}">
|
||||||
|
<img src="{{ url_for('static', filename='icons/plus.svg') }}" alt="" class="ui-icon small-ui-icon">
|
||||||
|
Kategorie
|
||||||
|
</button>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if account_data.categories %}
|
||||||
<div class="category-summary-grid">
|
<div class="category-summary-grid">
|
||||||
{% for category_data in account_data.categories %}
|
{% for category_data in account_data.categories %}
|
||||||
<button type="button" class="summary-category-card {% if category_data.distribution_hint and category_data.distribution_hint.status %}range-status-{{ category_data.distribution_hint.status }}{% endif %}" data-open-dialog="{{ category_data.dialog_id }}">
|
<button type="button" class="summary-category-card {% if category_data.distribution_hint and category_data.distribution_hint.status %}range-status-{{ category_data.distribution_hint.status }}{% endif %}" data-open-dialog="{{ category_data.dialog_id }}">
|
||||||
@@ -184,8 +184,18 @@
|
|||||||
</button>
|
</button>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</article>
|
{% else %}
|
||||||
{% endif %}
|
<div class="empty-state">
|
||||||
|
{% if account_data.account.slug == "gemeinschaftskonto" %}
|
||||||
|
Noch keine Budgets angelegt. Lege die erste Budget-Kategorie direkt hier an.
|
||||||
|
{% elif account_data.account.slug == "sparen-und-verteilung" %}
|
||||||
|
Noch keine Sparkonten angelegt. Lege Sparen, Urlaub oder weitere Verteilungsziele erst bei Bedarf an.
|
||||||
|
{% else %}
|
||||||
|
In diesem Bereich gibt es noch keine Kategorien.
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</article>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user