feat(pruefung): INSP-002 Phase 3 - Objekttyp-Intervall als Fallback
CI / backend-tests (push) Successful in 3m18s
CI / frontend-build (push) Successful in 21s

Prüftermine sind sicherheits-/haftungsrelevant (Nutzer-Vorgabe) - Änderung
bewusst additiv statt Ersetzung: Objektposition.pruefintervall_monate bleibt
Override wie bisher, NUR bei fehlendem Override greift jetzt der
Objekttyp-Standard (objekttyp_pruefart) statt naechste_pruefung leer zu
lassen. Kein Migrations-/Schema-Change, Regressionstest für bestehenden
Pfad ergänzt. Außerdem: WH-006-Ist-Stand-Notiz korrigiert (war als "fehlt
komplett" markiert, ist laut eigenem DoD seit 2026-09-10 fertig).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VDMvneg6wKfXdXvTYNd3dN
This commit is contained in:
patrickandClaude Sonnet 5 committed 2026-09-12 23:04:38 +02:00
1 parent 4a0d0fd5f1
commit c0c484cf8a
6 files changed
+250 -10

No files matched your search

+136
View File
@@ -15109,3 +15109,139 @@ Keine Commits in dieser Session.
- arbeitskacheln/04_assets.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-09-12 22:43 – 22:47 (4m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
- 4a0d0fd feat(verleih): Übergabeliste zum Unterschreiben (ASSET-013 Verleih-Teil)
### Geänderte Dateien
- DEVLOG.md | 12 ++++++
- arbeitskacheln/04_assets.md | 29 ++++++++++----
- backend/app/api/v1/endpoints/verleih.py | 42 ++++++++++++++++++++-
- backend/app/services/uebergabeliste.py | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- backend/tests/test_verleih.py | 57 ++++++++++++++++++++++++++++
- frontend/src/api/client.ts | 5 +++
- frontend/src/pages/admin/VerleihSection.tsx | 25 +++++++++---
---
## 2026-09-12 22:48 – 22:48 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 12 ++++++
- arbeitskacheln/04_assets.md | 29 ++++++++++----
- backend/app/api/v1/endpoints/verleih.py | 42 ++++++++++++++++++++-
- backend/app/services/uebergabeliste.py | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- backend/tests/test_verleih.py | 57 ++++++++++++++++++++++++++++
- frontend/src/api/client.ts | 5 +++
- frontend/src/pages/admin/VerleihSection.tsx | 25 +++++++++---
---
## 2026-09-12 22:48 – 22:48 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 12 ++++++
- arbeitskacheln/04_assets.md | 29 ++++++++++----
- backend/app/api/v1/endpoints/verleih.py | 42 ++++++++++++++++++++-
- backend/app/services/uebergabeliste.py | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- backend/tests/test_verleih.py | 57 ++++++++++++++++++++++++++++
- frontend/src/api/client.ts | 5 +++
- frontend/src/pages/admin/VerleihSection.tsx | 25 +++++++++---
---
## 2026-09-12 22:48 – 22:49 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 12 ++++++
- arbeitskacheln/04_assets.md | 29 ++++++++++----
- backend/app/api/v1/endpoints/verleih.py | 42 ++++++++++++++++++++-
- backend/app/services/uebergabeliste.py | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- backend/tests/test_verleih.py | 57 ++++++++++++++++++++++++++++
- frontend/src/api/client.ts | 5 +++
- frontend/src/pages/admin/VerleihSection.tsx | 25 +++++++++---
---
## 2026-09-12 22:50 – 22:50 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 12 ++++++
- arbeitskacheln/04_assets.md | 29 ++++++++++----
- backend/app/api/v1/endpoints/verleih.py | 42 ++++++++++++++++++++-
- backend/app/services/uebergabeliste.py | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- backend/tests/test_verleih.py | 57 ++++++++++++++++++++++++++++
- frontend/src/api/client.ts | 5 +++
- frontend/src/pages/admin/VerleihSection.tsx | 25 +++++++++---
---
## 2026-09-12 22:51 – 22:51 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 12 ++++++
- arbeitskacheln/04_assets.md | 29 ++++++++++----
- backend/app/api/v1/endpoints/verleih.py | 42 ++++++++++++++++++++-
- backend/app/services/uebergabeliste.py | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- backend/tests/test_verleih.py | 57 ++++++++++++++++++++++++++++
- frontend/src/api/client.ts | 5 +++
- frontend/src/pages/admin/VerleihSection.tsx | 25 +++++++++---
---
## 2026-09-12 22:52 – 22:52 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 12 ++++++
- arbeitskacheln/04_assets.md | 29 ++++++++++----
- backend/app/api/v1/endpoints/verleih.py | 42 ++++++++++++++++++++-
- backend/app/services/uebergabeliste.py | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- backend/tests/test_verleih.py | 57 ++++++++++++++++++++++++++++
- frontend/src/api/client.ts | 5 +++
- frontend/src/pages/admin/VerleihSection.tsx | 25 +++++++++---
---
## 2026-09-12 22:53 – 22:53 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 12 ++++++
- arbeitskacheln/04_assets.md | 29 ++++++++++----
- backend/app/api/v1/endpoints/verleih.py | 42 ++++++++++++++++++++-
- backend/app/services/uebergabeliste.py | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- backend/tests/test_verleih.py | 57 ++++++++++++++++++++++++++++
- frontend/src/api/client.ts | 5 +++
- frontend/src/pages/admin/VerleihSection.tsx | 25 +++++++++---
---
+6 -6
View File
@@ -178,11 +178,11 @@ Details zu WH-001 … WH-007 (vollständig).
---
**MABEA-Ist-Stand-Abgleich (aktualisiert 2026-09-06):** WH-001..005+007 sind umgesetzt
— `lagerort` (Hierarchie), `lagerplatz`, `bestand` je Lagerplatz+Material, Ein-/
**MABEA-Ist-Stand-Abgleich (aktualisiert 2026-09-12):** WH-001..007 sind alle
umgesetzt — `lagerort` (Hierarchie), `lagerplatz`, `bestand` je Lagerplatz+Material, Ein-/
Auslagerung sowie atomare Umlagerung (`POST /lagerplaetze/{id}/umlagern`) mit
lückenlosem `materialbewegung`-Protokoll (eine Zeile mit Von+Nach statt zwei
Einzelbuchungen). **WH-006 (Inventur)** fehlt weiterhin komplett — MABEA hat kein
Inventurlauf-Konzept mit Korrekturbuchung, nur objektbezogene Kontrolle.
`Lagerbewegung` (Objekt-Ebene) bleibt unverändert parallel bestehen, keine Vermischung
mit dem Material-/Lagerplatz-Bewegungsprotokoll.
Einzelbuchungen). **WH-006 (Inventur) ist seit 2026-09-10 fertig** (siehe DoD oben,
diese Zusammenfassung war veraltet stehengeblieben — Einzelkachel-DoD gilt, nicht
diese Zeile). `Lagerbewegung` (Objekt-Ebene) bleibt unverändert parallel bestehen,
keine Vermischung mit dem Material-/Lagerplatz-Bewegungsprotokoll.
+12
View File
@@ -88,6 +88,18 @@ Details zu INSP-001 … INSP-006 (vollständig).
(`geraet_instanz.py:aendere_status()`) mit Regressionsrisiko für
bestehende, produktive Prüftermine — explizit als eigener, künftiger
Schritt zurückgestellt, nicht Teil dieser Phase-2-Runde.
**Phase 3 (2026-09-12, Nutzer-Vorgabe: Prüftermine sind sicherheits-/
haftungsrelevant, siehe Memory `project_pruefttermine_kritisch`):** rein
additiver Fallback statt Ersetzung — `hole_effektives_intervall_monate()`
(`app/services/pruefung.py`) nutzt `Objektposition.pruefintervall_monate`
weiterhin als Override (unverändertes Verhalten für bereits konfigurierte
Objekte), fällt NUR wenn dieser `NULL` ist auf `objekttyp_pruefart` zurück.
`aendere_status()` ruft diesen Helper jetzt statt direkt
`objektposition.pruefintervall_monate` zu lesen. Kein Migrations-/Schema-
Change. Tests: Regressionstest (Override greift weiter unverändert),
neuer Fallback-Test (Objekttyp-Standard greift bei fehlendem Override),
Nullfall-Test (weder Override noch Objekttyp-Standard → weiterhin kein
`naechste_pruefung`, wie bisher). `backend/tests/test_geraet_instanz.py`.
## INSP-003 — Prüfdurchführung/-ergebnis
+9 -4
View File
@@ -81,13 +81,18 @@ async def aendere_status(
if pruefdatum is not None:
instanz.pruefdatum = pruefdatum
objektposition = await db.get(Objektposition, instanz.objektposition_id)
if objektposition is not None and objektposition.pruefintervall_monate:
instanz.naechste_pruefung = _plus_monate(pruefdatum, objektposition.pruefintervall_monate)
pruefart_id = (
await db.execute(select(Pruefart.id).where(Pruefart.name == GERAETESICHERHEITSPRUEFUNG))
).scalar_one()
objektposition = await db.get(Objektposition, instanz.objektposition_id)
if objektposition is not None:
intervall_monate = await pruefung_service.hole_effektives_intervall_monate(
db, objektposition=objektposition, pruefart_id=pruefart_id
)
if intervall_monate:
instanz.naechste_pruefung = _plus_monate(pruefdatum, intervall_monate)
await pruefung_service.erstelle_pruefung_fuer_geraet(
db,
geraet_instanz_id=instanz.id,
+20
View File
@@ -5,6 +5,8 @@ from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.geraet_instanz import GeraetInstanz
from app.models.objekt import Objekt
from app.models.objektposition import Objektposition
from app.models.pruefung import ObjekttypPruefart, Pruefart, Pruefung, PruefungErgebnis
from app.services.fristen import ampel_aus_tagen
@@ -58,6 +60,24 @@ async def entferne_objekttyp_pruefart(db: AsyncSession, *, objekttyp_id: int, pr
return True
async def hole_effektives_intervall_monate(
db: AsyncSession, *, objektposition: Objektposition, pruefart_id: int
) -> int | None:
"""INSP-002 Phase 3: individuelle Objektposition-Angabe (Override) sticht
weiterhin, wie bisher - additiver Fallback auf den Objekttyp-Standard
(`objekttyp_pruefart`), NUR wenn kein Override gesetzt ist. Reine
Ergänzung, ändert das Ergebnis für bereits konfigurierte Objekte (mit
gesetztem `pruefintervall_monate`) nicht."""
if objektposition.pruefintervall_monate is not None:
return objektposition.pruefintervall_monate
objekt = await db.get(Objekt, objektposition.objekt_id)
if objekt is None:
return None
zuordnung = await db.get(ObjekttypPruefart, (objekt.objekttyp_id, pruefart_id))
return zuordnung.intervall_monate if zuordnung else None
async def erstelle_pruefung_fuer_geraet(
db: AsyncSession,
*,
+67
View File
@@ -149,6 +149,73 @@ async def test_endpunkte_crud_und_pruefdatum(client, db_session, objekt_mit_posi
assert leer.json() == []
@pytest.mark.asyncio
async def test_naechste_pruefung_faellt_auf_objekttyp_intervall_zurueck(
client, db_session, objekt_mit_position, materialverantwortlicher_user
):
"""INSP-002 Phase 3: OHNE individuelles pruefintervall_monate an der
Objektposition wird jetzt der Objekttyp-Standard (objekttyp_pruefart)
als Fallback genutzt statt naechste_pruefung einfach leer zu lassen."""
from app.models.pruefung import GERAETESICHERHEITSPRUEFUNG, Pruefart
from app.services.pruefung import setze_objekttyp_pruefart
objekt, material = objekt_mit_position
pruefart_id = (
await db_session.execute(select(Pruefart.id).where(Pruefart.name == GERAETESICHERHEITSPRUEFUNG))
).scalar_one()
await setze_objekttyp_pruefart(
db_session, objekttyp_id=objekt.objekttyp_id, pruefart_id=pruefart_id, intervall_monate=6
)
await db_session.commit()
token = await login(client, "materialverantwortlicher1")
positionen = await client.get(f"/api/v1/objekte/{objekt.id}/positionen", headers=auth_header(token))
position_id = positionen.json()[0]["id"]
assert positionen.json()[0]["pruefintervall_monate"] is None # kein Override gesetzt
erstellt = await client.post(
f"/api/v1/objektpositionen/{position_id}/geraete",
json={"seriennummer": "SN-FALLBACK"},
headers=auth_header(token),
)
geraet_id = erstellt.json()["id"]
geprueft = await client.patch(
f"/api/v1/geraete/{geraet_id}",
json={"status": "einsatzbereit", "pruefdatum": "2026-01-15"},
headers=auth_header(token),
)
assert geprueft.status_code == 200
assert geprueft.json()["naechste_pruefung"] == "2026-07-15"
@pytest.mark.asyncio
async def test_naechste_pruefung_bleibt_leer_ohne_override_und_ohne_objekttyp_intervall(
client, objekt_mit_position, materialverantwortlicher_user
):
"""Regressionsschutz: weder Override noch Objekttyp-Standard gesetzt ->
unverändertes bisheriges Verhalten (kein naechste_pruefung)."""
objekt, material = objekt_mit_position
token = await login(client, "materialverantwortlicher1")
positionen = await client.get(f"/api/v1/objekte/{objekt.id}/positionen", headers=auth_header(token))
position_id = positionen.json()[0]["id"]
erstellt = await client.post(
f"/api/v1/objektpositionen/{position_id}/geraete",
json={"seriennummer": "SN-KEIN-INTERVALL"},
headers=auth_header(token),
)
geraet_id = erstellt.json()["id"]
geprueft = await client.patch(
f"/api/v1/geraete/{geraet_id}",
json={"status": "einsatzbereit", "pruefdatum": "2026-01-15"},
headers=auth_header(token),
)
assert geprueft.status_code == 200
assert geprueft.json()["naechste_pruefung"] is None
@pytest.mark.asyncio
async def test_defekt_kann_fehlbestand_erzeugen(client, objekt_mit_position, materialverantwortlicher_user):
objekt, material = objekt_mit_position