Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8bf37c82b6 | |||
| 35af65dd25 | |||
| b9212d9c41 |
+16
-1
@@ -10,10 +10,25 @@ def _default_data_dir() -> Path:
|
||||
return Path(os.getenv("SALDO_DATA_DIR", Path.cwd() / "instance")).resolve()
|
||||
|
||||
|
||||
def _secret_key(data_dir: Path) -> str:
|
||||
configured = os.getenv("SECRET_KEY")
|
||||
if configured:
|
||||
return configured
|
||||
|
||||
secret_file = data_dir / ".secret_key"
|
||||
if secret_file.exists():
|
||||
return secret_file.read_text(encoding="utf-8").strip()
|
||||
|
||||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
generated = secrets.token_hex(32)
|
||||
secret_file.write_text(generated, encoding="utf-8")
|
||||
return generated
|
||||
|
||||
|
||||
class Config:
|
||||
APP_NAME = "Saldo"
|
||||
SECRET_KEY = os.getenv("SECRET_KEY") or secrets.token_hex(32)
|
||||
DATA_DIR = _default_data_dir()
|
||||
SECRET_KEY = _secret_key(DATA_DIR)
|
||||
AVATAR_UPLOAD_DIR = DATA_DIR / "avatars"
|
||||
SQLALCHEMY_DATABASE_URI = os.getenv(
|
||||
"DATABASE_URL",
|
||||
|
||||
+5
-14
@@ -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,7 +404,6 @@ 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,
|
||||
|
||||
+72
-2
@@ -130,6 +130,19 @@ ENTRY_TARGET_CATEGORY = {
|
||||
"Kreditrate 2": "finanzen",
|
||||
}
|
||||
|
||||
EXAMPLE_ENTRY_NAMES = {
|
||||
entry_name
|
||||
for account_data in ACCOUNT_TREE.values()
|
||||
for entries in account_data["categories"].values()
|
||||
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 (
|
||||
@@ -143,7 +156,54 @@ def slugify(value: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
def seed_data() -> None:
|
||||
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()
|
||||
for entry in example_entries:
|
||||
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.
|
||||
community_accounts = {}
|
||||
@@ -196,6 +256,8 @@ def seed_data() -> 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))
|
||||
@@ -293,6 +355,7 @@ def seed_data() -> None:
|
||||
|
||||
gemeinschaft = Account.query.filter_by(slug="gemeinschaftskonto").first()
|
||||
if gemeinschaft:
|
||||
if include_example_entries:
|
||||
target_categories = account_categories["gemeinschaftskonto"]
|
||||
with db.session.no_autoflush:
|
||||
for category in gemeinschaft.categories:
|
||||
@@ -329,12 +392,19 @@ def seed_data() -> None:
|
||||
"Person 1",
|
||||
"Person 2",
|
||||
}
|
||||
else:
|
||||
_deactivate_placeholder_categories()
|
||||
_deactivate_placeholder_entries()
|
||||
for category in gemeinschaft.categories:
|
||||
if category.slug not in ACCOUNT_TREE["gemeinschaftskonto"]["categories"]:
|
||||
category.is_active = False
|
||||
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()
|
||||
|
||||
|
||||
@@ -342,7 +412,7 @@ def seed_demo_data() -> None:
|
||||
from datetime import date
|
||||
from flask import current_app
|
||||
|
||||
seed_data()
|
||||
seed_data(include_example_entries=True)
|
||||
|
||||
admin = User.query.filter_by(username="admin").first()
|
||||
if admin is None:
|
||||
|
||||
@@ -117,7 +117,6 @@
|
||||
|
||||
<section class="account-board">
|
||||
{% for account_data in planning_accounts %}
|
||||
{% if account_data.categories %}
|
||||
<article class="panel account-panel premium-panel">
|
||||
<div class="panel-head account-head">
|
||||
<div>
|
||||
@@ -146,6 +145,7 @@
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if account_data.categories %}
|
||||
<div class="category-summary-grid">
|
||||
{% 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 }}">
|
||||
@@ -184,8 +184,18 @@
|
||||
</button>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</article>
|
||||
{% else %}
|
||||
<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 %}
|
||||
</section>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user