Files
Mieterportal/beispieldaten.py
ah-admin 311153b3c3 v1.3: Serverseitige Datenbank (PostgreSQL + Node-API) ersetzt localStorage
- server/: Express-API (Auth, Bootstrap, Tickets, Threads, Benutzerverwaltung, Codes)
  mit pg-Schema (11 Tabellen), idempotem Seed (50 Wohnungen, 552 Buchungen)
- mp-db (PostgreSQL 16, Volume mp_pgdata) + mp-api im Compose-Stack,
  nginx proxiet /api/ an mp-api; deploy.sh baut API-Image auf dem Server
- Frontend: Store laedt /api/bootstrap, Schreibzugriff async, kein localStorage
  mehr (ausser Theme), Ladebildschirm beim Session-Check, vite-Proxy fuer Dev
- Rollenrechte serverseitig: Mieter nur eigene Wohnung (getestet, 403-Verweis)
- Mail-Benachrichtigungen komplett serverseitig (server/src/mail.js)
- beispieldaten.py schreibt server/seed/generated.json; Verwaltungs-Mail = axel@hocks.eu
- E2E live verifiziert: Login, Scoping, Ticket + Zustellung (status=sent),
  Registrierung, Codes; Testdaten bereinigt; Doku nachgefuehrt
2026-10-02 12:54:44 +02:00

127 lines
4.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Hocks Mieterportal – Beispieldaten-Generator.
Erzeugt deterministisch die Demo-Daten des Portals und schreibt sie nach
``server/seed/generated.json`` (Units + Transaktionen) – die Datenbank wird
daraus beim ersten Start des mp-api-Containers gespeist. Die handschriftlichen
Inhalte (Tickets, Dokumente, Nachrichten, Ankündigungen, Benutzer) liegen in
``server/src/seed-data.js``.
Aufruf: python beispieldaten.py (Standard-Seed 20261002)
python beispieldaten.py 42 (eigener Seed zum Experimentieren)
Achtung: Der Demo-Mieter „Anna Berger" (Whg. Hauptstraße 12 Nr. 7) und die
Algorithmen sind synchron zu server/src/seed-data.js zu halten.
"""
import json
import sys
from pathlib import Path
SEED = int(sys.argv[1]) if len(sys.argv) > 1 else 20261002
BUILDINGS = [
{"name": "Hauptstraße 12", "prefix": "HS", "count": 20, "baseRent": 9.8},
{"name": "Ringweg 3", "prefix": "RW", "count": 15, "baseRent": 8.9},
{"name": "Am Park 5", "prefix": "AP", "count": 15, "baseRent": 10.4},
]
FIRST_NAMES = [
"Anna", "Jonas", "Marta", "Leon", "Sofia", "Felix", "Lena", "Paul",
"Emma", "Noah", "Clara", "Ben", "Nora", "Max", "Julia", "Tim",
"Sarah", "Finn", "Laura", "Jan", "Mia", "Tom", "Ela", "Kim",
]
LAST_NAMES = [
"Berger", "Klein", "Wolf", "Neumann", "Schäfer", "Krüger", "Brandt",
"Vogel", "Otto", "Horn", "Busch", "Thomas", "Pohl", "Sander", "Lindner",
]
DEMO_TENANT = "Anna Berger"
DEMO_UNIT_ID = "hs-7"
MONTHS = [
"Januar", "Februar", "März", "April", "Mai", "Juni",
"Juli", "August", "September", "Oktober", "November", "Dezember",
]
def gen_units() -> list[dict]:
units: list[dict] = []
name_idx = 0
def next_name() -> str:
nonlocal name_idx
n = f"{FIRST_NAMES[name_idx % len(FIRST_NAMES)]} {LAST_NAMES[name_idx % len(LAST_NAMES)]}"
name_idx += 1
return n
for b in BUILDINGS:
for i in range(b["count"]):
num = i + 1
size = 38 + ((num * 7 + len(b["prefix"]) * 3) % 78) # 38–115 m²
cold = round(size * b["baseRent"])
warm = cold + round(2.4 * size) + (35 if num % 3 == 0 else 0)
floor = 0 if num <= 2 else min(4, (num - 1) // 4)
occupied = num % 9 != 0 # ~89 % Vermietung
if b["prefix"] == "HS" and num == 7:
tenant: str | None = DEMO_TENANT
elif occupied:
tenant = next_name()
else:
tenant = None
units.append({
"id": f"{b['prefix'].lower()}-{num}",
"building": b["name"],
"number": str(num),
"floor": floor,
"size": size,
"coldRent": cold,
"warmRent": warm,
"tenant": tenant,
"status": "vermietet" if occupied else "frei",
})
return units
def gen_transactions(units: list[dict]) -> list[dict]:
txs: list[dict] = []
for u in units:
if u["status"] != "vermietet":
continue
for m in range(12):
due = m + 1
if due <= 9:
status = "überfällig" if (ord(u["id"][3]) + m) % 11 == 0 else "bezahlt"
else:
status = "offen"
txs.append({
"id": f"tx-{u['id']}-{m}",
"unitId": u["id"],
"tenant": u["tenant"],
"month": MONTHS[m],
"year": 2026,
"amount": u["warmRent"],
"type": "Miete",
"status": status,
**({"paidAt": f"0{(m % 8) + 1}.0{due}.2026"} if status == "bezahlt" else {}),
})
return txs
def main() -> None:
units = gen_units()
data = {
"meta": {"seed": SEED, "units": len(units),
"rented": sum(1 for u in units if u["status"] == "vermietet")},
"units": units,
"transactions": gen_transactions(units),
}
out = Path(__file__).parent / "server" / "seed" / "generated.json"
out.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"{out} geschrieben: {data['meta']['units']} Wohnungen "
f"({data['meta']['rented']} vermietet), {len(data['transactions'])} Buchungen")
if __name__ == "__main__":
main()