diff --git a/DEVLOG.md b/DEVLOG.md index 58d62c0..6e1b075 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -4358,3 +4358,33 @@ Keine Commits in dieser Session. - arbeitskacheln/11_defects.md | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --- +## 2026-09-05 15:35 – 15:37 (2m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- 27c2499 feat(fahrzeug): Funkkenner-Feld ergänzt (BOS-Digitalfunk-Kennung) + +### Geänderte Dateien +- DEVLOG.md | 137 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +- arbeitskacheln/05_fleet.md | 15 +++++--- +- backend/alembic/versions/0018_funkkenner.py | 25 ++++++++++++++ +- backend/app/models/fahrzeugdetails.py | 3 ++ +- backend/app/schemas/objekt.py | 2 ++ +- backend/tests/test_fahrzeugdetails.py | 8 ++++- +- frontend/src/api/types.ts | 1 + +- frontend/src/pages/admin/ObjektSection.tsx | 7 ++++ + +--- +## 2026-09-05 15:39 – 15:41 (1m) +**Beschreibung:** Claude Code Session +**Projekt:** asb-material + +### Commits +- 286227b docs: Personnel-Epic komplettiert (PERS-001..007) + +### Geänderte Dateien +- arbeitskacheln/00_index.md | 5 +-- +- arbeitskacheln/12_personnel.md | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +--- diff --git a/backend/app/schemas/dashboard.py b/backend/app/schemas/dashboard.py index f31d197..b13b528 100644 --- a/backend/app/schemas/dashboard.py +++ b/backend/app/schemas/dashboard.py @@ -31,6 +31,7 @@ class ObjektMangelRead(BaseModel): class EinsatzbereitschaftRead(BaseModel): einsatzbereit: int eingeschraenkt_einsatzbereit: int + unbekannt: int nicht_einsatzbereit: int gesamt: int details: list[ObjektMangelRead] diff --git a/backend/app/services/dashboard.py b/backend/app/services/dashboard.py index 0980491..dab17a9 100644 --- a/backend/app/services/dashboard.py +++ b/backend/app/services/dashboard.py @@ -14,6 +14,7 @@ from app.models.objektposition import Objektposition from app.models.personal import BenutzerQualifikation, Qualifikationstyp from app.models.stammdaten import Material from app.services.mangel import objekt_ids_mit_kritischem_offenem_mangel +from app.services.objekte import letzte_kontrollen_map # Prompt 12.2: offen/in_bearbeitung/nachgefuellt_teilweise zählen gemeinsam als "offen" # (technischer Substatus ist keine Erledigung, Prompt 03/20). @@ -171,6 +172,11 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict: objekt_ids_kritischer_mangel = await objekt_ids_mit_kritischem_offenem_mangel(db) + # Nutzer-Fund (Readiness-Review): ein nie kontrolliertes Objekt hatte bisher als + # "einsatzbereit" gegolten, weil keine Kriterien verletzt waren - fachlich falsch, + # "nie geprüft" ist nicht dasselbe wie "geprüft und in Ordnung". + letzte_kontrolle_map = await letzte_kontrollen_map(db, [o.id for o in objekte]) + objekt_ids_hu_uvv_ueberfaellig = set( ( await db.execute( @@ -190,7 +196,7 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict: .all() ) - bereit = eingeschraenkt = 0 + bereit = eingeschraenkt = unbekannt = 0 details: list[dict] = [] for objekt in objekte: gruende: list[str] = [] @@ -216,6 +222,11 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict: details.append( {"objekt_id": objekt.id, "code": objekt.code, "name": objekt.name, "gruende": ["mindermenge_genehmigt"]} ) + elif not gruende and letzte_kontrolle_map.get(objekt.id) is None: + unbekannt += 1 + details.append( + {"objekt_id": objekt.id, "code": objekt.code, "name": objekt.name, "gruende": ["noch_nie_kontrolliert"]} + ) elif not gruende: bereit += 1 else: @@ -224,7 +235,8 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict: return { "einsatzbereit": bereit, "eingeschraenkt_einsatzbereit": eingeschraenkt, - "nicht_einsatzbereit": len(objekte) - bereit - eingeschraenkt, + "unbekannt": unbekannt, + "nicht_einsatzbereit": len(objekte) - bereit - eingeschraenkt - unbekannt, "gesamt": len(objekte), "details": details, } diff --git a/backend/tests/test_dashboard.py b/backend/tests/test_dashboard.py index 9b05a15..d62ca80 100644 --- a/backend/tests/test_dashboard.py +++ b/backend/tests/test_dashboard.py @@ -153,19 +153,41 @@ async def test_abgelaufenes_material_wird_als_abgelaufen_markiert( @pytest.mark.asyncio -async def test_objekt_ohne_maengel_ist_einsatzbereit( - client, objekt_mit_position, materialverantwortlicher_user +async def test_objekt_ohne_maengel_und_kontrolliert_ist_einsatzbereit( + client, objekt_mit_position, mitarbeiter_user, materialverantwortlicher_user ): - objekt, _material = objekt_mit_position + """Kontrolliert (Ist=Soll, kein Fehlbestand) und ohne sonstige Mängel -> bereit. + Unterscheidet sich bewusst von test_nie_kontrolliertes_objekt_ist_unbekannt.""" + objekt, material = objekt_mit_position + mitarbeiter_token = await login(client, "mitarbeiter1") + await _fehlbestand(client, mitarbeiter_token, objekt, material, "10") # = Sollmenge, kein Fehlbestand + token = await login(client, "materialverantwortlicher1") response = await client.get("/api/v1/dashboard/einsatzbereitschaft", headers=auth_header(token)) body = response.json() assert body["einsatzbereit"] == 1 assert body["eingeschraenkt_einsatzbereit"] == 0 + assert body["unbekannt"] == 0 assert body["nicht_einsatzbereit"] == 0 assert body["details"] == [] +@pytest.mark.asyncio +async def test_nie_kontrolliertes_objekt_ist_unbekannt( + client, objekt_mit_position, materialverantwortlicher_user +): + """Nutzer-Fund: ein nie kontrolliertes Objekt darf NICHT als einsatzbereit zählen + (unterscheidet "nie geprüft" von "geprüft und in Ordnung").""" + objekt, _material = objekt_mit_position + token = await login(client, "materialverantwortlicher1") + response = await client.get("/api/v1/dashboard/einsatzbereitschaft", headers=auth_header(token)) + body = response.json() + assert body["einsatzbereit"] == 0 + assert body["unbekannt"] == 1 + assert body["nicht_einsatzbereit"] == 0 + assert body["details"][0]["gruende"] == ["noch_nie_kontrolliert"] + + @pytest.mark.asyncio async def test_offener_fehlbestand_macht_objekt_nicht_einsatzbereit( client, objekt_mit_position, mitarbeiter_user, materialverantwortlicher_user diff --git a/backend/tests/test_mangel.py b/backend/tests/test_mangel.py index 910d25c..92e5ddf 100644 --- a/backend/tests/test_mangel.py +++ b/backend/tests/test_mangel.py @@ -100,8 +100,22 @@ async def test_kritischer_offener_mangel_macht_objekt_nicht_einsatzbereit( async def test_niedrige_prioritaet_blockiert_einsatzbereitschaft_nicht( client, objekt_mit_position, mitarbeiter_user, materialverantwortlicher_user ): - objekt, _material = objekt_mit_position + objekt, material = objekt_mit_position mitarbeiter_token = await login(client, "mitarbeiter1") + # kontrolliert (Ist=Soll), damit dieser Test unabhängig vom "nie kontrolliert"-Fall + # (siehe test_dashboard.py: test_nie_kontrolliertes_objekt_ist_unbekannt) nur die + # Priorität isoliert prüft. + start = await client.post( + f"/api/v1/objekte/{objekt.id}/kontrollen", json={"uebernehmen": False}, headers=auth_header(mitarbeiter_token) + ) + kontrolle_id = start.json()["id"] + await client.put( + f"/api/v1/kontrollen/{kontrolle_id}/positionen/{material.id}", + json={"istmenge": "10"}, + headers=auth_header(mitarbeiter_token), + ) + await client.post(f"/api/v1/kontrollen/{kontrolle_id}/abschliessen", headers=auth_header(mitarbeiter_token)) + await client.post( "/api/v1/maengel", json={"objekt_id": objekt.id, "beschreibung": "Kratzer im Lack", "prioritaet": "niedrig"}, diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index 2aaa786..666095d 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -34,6 +34,7 @@ interface ObjektMangel { interface Einsatzbereitschaft { einsatzbereit: number; eingeschraenkt_einsatzbereit: number; + unbekannt: number; nicht_einsatzbereit: number; gesamt: number; details: ObjektMangel[]; @@ -64,6 +65,7 @@ const GRUND_TEXT: Record = { mangel_kritisch_offen: "Kritischer Mangel offen", in_wartung: "In Wartung", hu_uvv_ueberfaellig: "HU/UVV überfällig", + noch_nie_kontrolliert: "Noch nie kontrolliert", mindermenge_genehmigt: "Mindermenge genehmigt", }; @@ -126,6 +128,11 @@ export function DashboardPage() { Mindermengen-Genehmigung)

)} + {bereitschaft.unbekannt > 0 && ( +

+ {bereitschaft.unbekannt} noch nie kontrolliert (Status unbekannt) +

+ )} {bereitschaft.nicht_einsatzbereit > 0 && (

{bereitschaft.nicht_einsatzbereit} nicht einsatzbereit