provisioning-server/scripts/backfill_organisationseinheiten.py
Thomas Stallinger 25e1c5525b feat: Organisationseinheiten (OEs) + Gruppen für Konfigurationsgruppen
Neues Datenmodell (Migration 0018/0019 + Backfill-Script):
- organisationseinheiten: Baum pro Organisation, ein Gerät gehört zu
  genau einer OE (devices.oe_id). Jede Organisation bekommt automatisch
  eine geschützte Standard-OE 'Neue Geräte' als Landeplatz für neu
  aktivierte Geräte (create_organization() legt sie mit an,
  load_or_create_device() weist sie neuen Geräten zu).
- gruppen + device_gruppen: flach, M:N - ein Gerät kann in mehreren
  Gruppen gleichzeitig sein.
- oe_merkmale/gruppen_merkmale: sparsame present/absent-Overrides,
  gleiches Muster wie device_merkmale.

Resolution-Algorithmus (resolve_katalog_overrides): Workspace-Default →
OE-Kette (spezifischste OE gewinnt, klassische Vererbung) → Gruppen
('mehr gewinnt' bei Widerspruch, OE-Kettenergebnis zählt als weitere
Stimme) → Geräte-Override (gewinnt immer, wie bisher).
fetch_auftragskatalog_listing() liefert zusätzlich die Herkunft
('workspace'/'oe'/'gruppe'/'geraet') für die Kundenportal-UI.

Neue Endpunkte: CRUD für OEs/Gruppen, Merkmale-Select/Deselect/Unset je
OE/Gruppe, Geräte-OE-Verschiebung, Geräte-Gruppen-Mitgliedschaft.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-12 16:40:34 +02:00

66 lines
2.6 KiB
Python

"""
Einmaliger Backfill für Migration 0018 (siehe Konfigurationsgruppen-Plan):
legt für jede bestehende Organisation ohne Standard-OE eine "Neue Geräte"-OE
an und ordnet alle bislang unzugeordneten Geräte (oe_id IS NULL) dieser OE
ihrer Organisation zu. Danach kann Migration 0019 (oe_id NOT NULL) sicher
eingespielt werden.
Ab Migration 0018 legt create_organization() die Standard-OE für neue
Organisationen bereits selbst an - dieses Script ist nur für den
Bestandsdaten-Übergang nötig, kann aber gefahrlos mehrfach laufen
(idempotent: überspringt Organisationen, die schon eine Standard-OE haben).
Aufruf:
TUXFLOTTE_DATABASE_URL=... .venv/bin/python scripts/backfill_organisationseinheiten.py
"""
import os
import sys
from uuid import uuid4
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app import STANDARD_OE_NAME, get_database_connection # noqa: E402
def backfill():
with get_database_connection() as conn:
with conn.cursor() as cur:
cur.execute("SELECT id, name FROM organizations ORDER BY name")
organizations = cur.fetchall()
for organization_id, organization_name in organizations:
cur.execute(
"SELECT id FROM organisationseinheiten WHERE organization_id = %s AND ist_standard",
(organization_id,),
)
row = cur.fetchone()
if row is None:
standard_oe_id = uuid4()
cur.execute(
"""
INSERT INTO organisationseinheiten (id, organization_id, parent_id, name, ist_standard)
VALUES (%s, %s, NULL, %s, TRUE)
""",
(standard_oe_id, organization_id, STANDARD_OE_NAME),
)
print(f"[{organization_name}] Standard-OE '{STANDARD_OE_NAME}' angelegt ({standard_oe_id})")
else:
standard_oe_id = row[0]
print(f"[{organization_name}] Standard-OE existiert bereits ({standard_oe_id})")
cur.execute(
"UPDATE devices SET oe_id = %s WHERE organization_id = %s AND oe_id IS NULL RETURNING id",
(standard_oe_id, organization_id),
)
updated = cur.fetchall()
print(f"[{organization_name}] {len(updated)} Gerät(e) der Standard-OE zugeordnet")
if __name__ == "__main__":
if not os.environ.get("TUXFLOTTE_DATABASE_URL"):
sys.exit("TUXFLOTTE_DATABASE_URL ist nicht gesetzt.")
backfill()