feat(pruefung): INSP-002 Phase 3 - Objekttyp-Intervall als Fallback
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:
1 parent
4a0d0fd5f1
commit
c0c484cf8a
6 files changed
+250
-10
No files matched your search
@@ -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 +++++++++---
|
||||
|
||||
---
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
*,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user