fix: align total costs with visible budgets
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
||||
from flask_login import login_required
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.extensions import db
|
||||
@@ -51,6 +52,9 @@ def index():
|
||||
entries = db.session.scalars(
|
||||
select(Entry).order_by(Entry.category_id.asc(), Entry.sort_order.asc(), Entry.name.asc())
|
||||
).all()
|
||||
inactive_accounts = [account for account in accounts if not account.is_active]
|
||||
inactive_categories = [category for category in categories if not category.is_active]
|
||||
inactive_entries = [entry for entry in entries if not entry.is_active]
|
||||
return render_template(
|
||||
"admin/index.html",
|
||||
users=users,
|
||||
@@ -58,6 +62,9 @@ def index():
|
||||
accounts=accounts,
|
||||
categories=categories,
|
||||
entries=entries,
|
||||
inactive_accounts=inactive_accounts,
|
||||
inactive_categories=inactive_categories,
|
||||
inactive_entries=inactive_entries,
|
||||
)
|
||||
|
||||
|
||||
@@ -263,6 +270,53 @@ def update_entry(entry_id: int):
|
||||
return redirect(url_for("admin.index"))
|
||||
|
||||
|
||||
@admin_bp.route("/accounts/<int:account_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_account(account_id: int):
|
||||
account = Account.query.get_or_404(account_id)
|
||||
if account.is_active:
|
||||
flash("Nur inaktive Konten können hier endgültig gelöscht werden.", "danger")
|
||||
return redirect(url_for("admin.index"))
|
||||
try:
|
||||
db.session.delete(account)
|
||||
db.session.commit()
|
||||
except IntegrityError:
|
||||
db.session.rollback()
|
||||
flash("Konto konnte nicht gelöscht werden, weil noch abhängige Daten existieren.", "danger")
|
||||
return redirect(url_for("admin.index"))
|
||||
flash("Konto endgültig gelöscht.", "success")
|
||||
return redirect(url_for("admin.index"))
|
||||
|
||||
|
||||
@admin_bp.route("/categories/<int:category_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_category(category_id: int):
|
||||
category = Category.query.get_or_404(category_id)
|
||||
if category.is_active:
|
||||
flash("Nur inaktive Kategorien können hier endgültig gelöscht werden.", "danger")
|
||||
return redirect(url_for("admin.index"))
|
||||
db.session.delete(category)
|
||||
db.session.commit()
|
||||
flash("Kategorie endgültig gelöscht.", "success")
|
||||
return redirect(url_for("admin.index"))
|
||||
|
||||
|
||||
@admin_bp.route("/entries/<int:entry_id>/delete", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
def delete_entry(entry_id: int):
|
||||
entry = Entry.query.get_or_404(entry_id)
|
||||
if entry.is_active:
|
||||
flash("Nur inaktive Einträge können hier endgültig gelöscht werden.", "danger")
|
||||
return redirect(url_for("admin.index"))
|
||||
db.session.delete(entry)
|
||||
db.session.commit()
|
||||
flash("Eintrag endgültig gelöscht.", "success")
|
||||
return redirect(url_for("admin.index"))
|
||||
|
||||
|
||||
@admin_bp.route("/entries/<int:entry_id>/share-rules", methods=["POST"])
|
||||
@login_required
|
||||
@admin_required
|
||||
|
||||
@@ -190,7 +190,7 @@ class MonthService:
|
||||
(
|
||||
self.share_service.calculate_entry_shares(item)["internal_total"]
|
||||
for item in month.entry_values
|
||||
if item.entry_id not in distribution_entry_ids
|
||||
if item.entry_id not in distribution_entry_ids and self._is_visible_monthly_value(item)
|
||||
),
|
||||
Decimal("0.00"),
|
||||
)
|
||||
@@ -368,19 +368,12 @@ class MonthService:
|
||||
def _distribution_entry_values(self, month: Month) -> dict[str, MonthlyEntryValue]:
|
||||
grouped: dict[str, list[MonthlyEntryValue]] = {}
|
||||
for value in month.entry_values:
|
||||
if not self._is_visible_monthly_value(value):
|
||||
continue
|
||||
entry = value.entry
|
||||
category = entry.category if entry else None
|
||||
account = category.account if category else None
|
||||
if (
|
||||
entry is None
|
||||
or category is None
|
||||
or account is None
|
||||
or not entry.is_active
|
||||
or not category.is_active
|
||||
or not account.is_active
|
||||
or account.slug not in self.allocation_service.TARGET_SLUGS
|
||||
or not entry.is_allocation_target
|
||||
):
|
||||
if account is None or account.slug not in self.allocation_service.TARGET_SLUGS or not entry.is_allocation_target:
|
||||
continue
|
||||
grouped.setdefault(account.slug, []).append(value)
|
||||
|
||||
@@ -397,6 +390,19 @@ class MonthService:
|
||||
preferred[account_slug] = values[0]
|
||||
return preferred
|
||||
|
||||
def _is_visible_monthly_value(self, value: MonthlyEntryValue) -> bool:
|
||||
entry = value.entry
|
||||
category = entry.category if entry else None
|
||||
account = category.account if category else None
|
||||
return bool(
|
||||
entry is not None
|
||||
and category is not None
|
||||
and account is not None
|
||||
and entry.is_active
|
||||
and category.is_active
|
||||
and account.is_active
|
||||
)
|
||||
|
||||
def _distribution_label(self, account_slug: str, fallback: str) -> str:
|
||||
personal_labels = personal_account_names()
|
||||
if account_slug in personal_labels:
|
||||
|
||||
@@ -71,4 +71,53 @@
|
||||
</form>
|
||||
</dialog>
|
||||
{% endfor %}
|
||||
|
||||
<section class="panel">
|
||||
<div class="panel-head">
|
||||
<div>
|
||||
<h2>Inaktive Elemente</h2>
|
||||
<small>Ausgeblendete Konten, Kategorien und Einträge können hier endgültig gelöscht werden.</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if inactive_accounts or inactive_categories or inactive_entries %}
|
||||
{% for account in inactive_accounts %}
|
||||
<div class="month-row">
|
||||
<div>
|
||||
<strong>Konto: {{ account.name }}</strong>
|
||||
<small>{{ account.slug }}</small>
|
||||
</div>
|
||||
<form method="post" action="{{ url_for('admin.delete_account', account_id=account.id) }}">
|
||||
<button class="ghost-button danger-button" type="submit">Endgültig löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for category in inactive_categories %}
|
||||
<div class="month-row">
|
||||
<div>
|
||||
<strong>Kategorie: {{ category.name }}</strong>
|
||||
<small>{{ category.account.name }} · {{ category.slug }}</small>
|
||||
</div>
|
||||
<form method="post" action="{{ url_for('admin.delete_category', category_id=category.id) }}">
|
||||
<button class="ghost-button danger-button" type="submit">Endgültig löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{% for entry in inactive_entries %}
|
||||
<div class="month-row">
|
||||
<div>
|
||||
<strong>Eintrag: {{ entry.name }}</strong>
|
||||
<small>{{ entry.category.name }} · {{ entry.slug }}</small>
|
||||
</div>
|
||||
<form method="post" action="{{ url_for('admin.delete_entry', entry_id=entry.id) }}">
|
||||
<button class="ghost-button danger-button" type="submit">Endgültig löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="empty-state">Aktuell gibt es keine inaktiven Konten, Kategorien oder Einträge.</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user