- 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
127 lines
4.3 KiB
Python
127 lines
4.3 KiB
Python
#!/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()
|