Ausgegebene Medien löschen, Gerät löschen deprovisioniert zusätzlich

DELETE /api/v1/organizations/<org>/enrollment-sessions/<id> entfernt nur
nicht mehr nutzbare Medien (widerrufen, abgelaufen, aufgebraucht).
archive_device() entzieht jetzt auch das Agent-Secret: ein gelöschtes Gerät
checkte bisher weiter ein (Liebherr-47, 03.10.2026).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-23 07:49:53 +02:00
1 parent 5284bb470b
commit 2f6dfd585b
1 file changed
+61 -1
+61 -1
View File
@@ -3830,6 +3830,11 @@ def archive_device(device_id: str) -> bool:
aus fetch_devices_for_organization()/fetch_all_devices(), Datensatz und
Historie (hardware_snapshots, device_merkmal_events) bleiben in der DB
erhalten.
Seit 23.10.2026 zugleich deprovisioniert (wie deprovision_device()):
Vorher checkte ein gelöschtes Gerät weiter ein und wurde unsichtbar
weiterverwaltet (Liebherr-47, gelöscht 03.10. 13:56, Check-in 16:24).
Eine Neuinstallation (/api/v1/activate) setzt beides wieder zurück.
"""
with get_database_connection() as conn:
@@ -3837,7 +3842,9 @@ def archive_device(device_id: str) -> bool:
cur.execute(
"""
UPDATE devices
SET archived_at = CURRENT_TIMESTAMP
SET archived_at = CURRENT_TIMESTAMP,
deprovisioned_at = COALESCE(deprovisioned_at, CURRENT_TIMESTAMP),
agent_secret_hash = NULL, agent_secret_issued_at = NULL
WHERE id = %s AND archived_at IS NULL
RETURNING id
""",
@@ -5736,6 +5743,35 @@ def revoke_enrollment_session(session_id: str):
)
return cur.fetchone() is not None
def delete_enrollment_session(organization_id: str, session_id: str) -> str:
"""
GUI-Runde 23.10.2026: "Ausgegebene Medien" verwalten. Gelöscht wird nur,
was ohnehin nicht mehr installieren kann (widerrufen, abgelaufen oder
Kontingent aufgebraucht) - ein noch gültiges Medium muss erst
deaktiviert werden. Nichts verweist per Fremdschlüssel auf die Session;
installierte Geräte bleiben unberührt. Rückgabe: "geloescht",
"nicht_gefunden" oder "noch_gueltig".
"""
with get_database_connection() as conn:
with conn.cursor() as cur:
cur.execute(
"""
DELETE FROM enrollment_sessions
WHERE id = %s AND organization_id = %s
AND (revoked_at IS NOT NULL OR expires_at <= CURRENT_TIMESTAMP OR devices_used >= max_devices)
RETURNING id
""",
(session_id, organization_id),
)
if cur.fetchone() is not None:
return "geloescht"
cur.execute(
"SELECT 1 FROM enrollment_sessions WHERE id = %s AND organization_id = %s",
(session_id, organization_id),
)
return "noch_gueltig" if cur.fetchone() is not None else "nicht_gefunden"
def recharge_enrollment_session(session_id: str, additional_devices: int, expires_at: str | None):
"""
Phase 5 (Kundenplattform-Selfservice): eine bereits ausgeteilte,
@@ -8829,6 +8865,30 @@ def revoke_enrollment_session_endpoint(session_id: str, authorization: str | Non
return {"success": True}
@app.delete("/api/v1/organizations/{organization_id}/enrollment-sessions/{session_id}")
def delete_enrollment_session_endpoint(
organization_id: str, session_id: str, authorization: str | None = Header(default=None)
):
if not require_service_token(authorization):
return {"success": False, "error": "unauthorized", "message": "Fehlendes oder ungültiges Service-Token."}
try:
UUID(session_id)
except ValueError:
return {"success": False, "error": "not_found", "message": "Installationsmedium wurde nicht gefunden."}
ergebnis = delete_enrollment_session(organization_id, session_id)
if ergebnis == "nicht_gefunden":
return {"success": False, "error": "not_found", "message": "Installationsmedium wurde nicht gefunden."}
if ergebnis == "noch_gueltig":
return {
"success": False, "error": "noch_gueltig",
"message": "Das Medium ist noch gültig. Deaktiviere es zuerst, dann kannst du es löschen.",
}
return {"success": True}
@app.patch("/api/v1/enrollment-sessions/{session_id}")
def recharge_enrollment_session_endpoint(
session_id: str,