diff --git a/backend/app/schemas/dashboard.py b/backend/app/schemas/dashboard.py index b13b528..ad50041 100644 --- a/backend/app/schemas/dashboard.py +++ b/backend/app/schemas/dashboard.py @@ -34,6 +34,7 @@ class EinsatzbereitschaftRead(BaseModel): unbekannt: int nicht_einsatzbereit: int gesamt: int + nie_kontrolliert_gesamt: int details: list[ObjektMangelRead] diff --git a/backend/app/services/dashboard.py b/backend/app/services/dashboard.py index 8656abb..3e0472a 100644 --- a/backend/app/services/dashboard.py +++ b/backend/app/services/dashboard.py @@ -197,6 +197,7 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict: ) bereit = eingeschraenkt = unbekannt = 0 + nie_kontrolliert_gesamt = 0 details: list[dict] = [] for objekt in objekte: kategorie, gruende = _kategorie_und_gruende( @@ -210,6 +211,8 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict: objekt_ids_kritischer_mangel, objekt_ids_hu_uvv_ueberfaellig, ) + if "noch_nie_kontrolliert" in gruende: + nie_kontrolliert_gesamt += 1 if kategorie == "ready": bereit += 1 else: @@ -225,6 +228,10 @@ async def einsatzbereitschaft(db: AsyncSession) -> dict: "unbekannt": unbekannt, "nicht_einsatzbereit": len(objekte) - bereit - eingeschraenkt - unbekannt, "gesamt": len(objekte), + # Bug-Fund 2026-09-06: "unbekannt" zählt nur Objekte OHNE jeden anderen + # Grund - diese Zahl zählt "nie kontrolliert" unabhängig von sonstigen + # Problemen mit, damit sie beim manuellen Nachzählen stimmt. + "nie_kontrolliert_gesamt": nie_kontrolliert_gesamt, "details": details, } @@ -260,12 +267,23 @@ def _kategorie_und_gruende( if objekt.id in objekt_ids_hu_uvv_ueberfaellig: gruende.append("hu_uvv_ueberfaellig") + # Bug-Fund 2026-09-06 (Nutzer: "beim genauen Schauen sind es mehr"): + # "noch nie kontrolliert" wurde bisher nur gezählt, wenn es der EINZIGE + # Grund war - ein Objekt mit z.B. offenem Fehlbestand UND nie kontrolliert + # verlor den Hinweis komplett (fiel unter "nicht einsatzbereit" ohne + # diesen Grund zu nennen). Jetzt immer als Zusatzgrund angehängt, wenn + # zutreffend, unabhängig von anderen Gründen. + nie_kontrolliert = letzte_kontrolle is None + if not gruende and fehlbestand_ids and fehlbestand_ids <= fehlbestand_ids_genehmigt: - return "limited", ["mindermenge_genehmigt"] - if not gruende and letzte_kontrolle is None: + zusatz = ["noch_nie_kontrolliert"] if nie_kontrolliert else [] + return "limited", ["mindermenge_genehmigt"] + zusatz + if not gruende and nie_kontrolliert: return "unknown", ["noch_nie_kontrolliert"] if not gruende: return "ready", [] + if nie_kontrolliert: + gruende = gruende + ["noch_nie_kontrolliert"] return "not-ready", gruende diff --git a/backend/tests/test_dashboard.py b/backend/tests/test_dashboard.py index d62ca80..b76eee8 100644 --- a/backend/tests/test_dashboard.py +++ b/backend/tests/test_dashboard.py @@ -186,6 +186,7 @@ async def test_nie_kontrolliertes_objekt_ist_unbekannt( assert body["unbekannt"] == 1 assert body["nicht_einsatzbereit"] == 0 assert body["details"][0]["gruende"] == ["noch_nie_kontrolliert"] + assert body["nie_kontrolliert_gesamt"] == 1 @pytest.mark.asyncio @@ -203,7 +204,11 @@ async def test_offener_fehlbestand_macht_objekt_nicht_einsatzbereit( body = response.json() assert body["einsatzbereit"] == 0 assert body["nicht_einsatzbereit"] == 1 - assert body["details"][0]["gruende"] == ["fehlbestand_offen"] + # Bug-Fund 2026-09-06: "nie kontrolliert" darf nicht verloren gehen, nur + # weil das Objekt zusätzlich einen anderen Grund hat (objekt_mit_position + # hat nie eine abgeschlossene Kontrolle). + assert body["details"][0]["gruende"] == ["fehlbestand_offen", "noch_nie_kontrolliert"] + assert body["nie_kontrolliert_gesamt"] == 1 @pytest.mark.asyncio @@ -230,7 +235,8 @@ async def test_genehmigte_mindermenge_macht_objekt_eingeschraenkt_einsatzbereit( assert body["einsatzbereit"] == 0 assert body["eingeschraenkt_einsatzbereit"] == 1 assert body["nicht_einsatzbereit"] == 0 - assert body["details"][0]["gruende"] == ["mindermenge_genehmigt"] + assert body["details"][0]["gruende"] == ["mindermenge_genehmigt", "noch_nie_kontrolliert"] + assert body["nie_kontrolliert_gesamt"] == 1 @pytest.mark.asyncio @@ -257,7 +263,8 @@ async def test_ueberfaellige_pruefung_macht_objekt_nicht_einsatzbereit( response = await client.get("/api/v1/dashboard/einsatzbereitschaft", headers=auth_header(token)) body = response.json() assert body["nicht_einsatzbereit"] == 1 - assert body["details"][0]["gruende"] == ["pruefung_ueberfaellig"] + assert body["details"][0]["gruende"] == ["pruefung_ueberfaellig", "noch_nie_kontrolliert"] + assert body["nie_kontrolliert_gesamt"] == 1 prueftermine = await client.get("/api/v1/dashboard/prueftermine", headers=auth_header(token)) prueftermine_body = prueftermine.json() diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index d378f07..4a88154 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -42,6 +42,7 @@ interface Einsatzbereitschaft { unbekannt: number; nicht_einsatzbereit: number; gesamt: number; + nie_kontrolliert_gesamt: number; details: ObjektMangel[]; } @@ -130,8 +131,8 @@ export function DashboardPage() { ablaufdatenAbgelaufen > 0 ? { text: `${ablaufdatenAbgelaufen} Chargen abgelaufen`, ziel: null } : null, - bereitschaft && bereitschaft.unbekannt > 0 - ? { text: `${bereitschaft.unbekannt} Objekte noch nie kontrolliert`, ziel: "/objekte" } + bereitschaft && bereitschaft.nie_kontrolliert_gesamt > 0 + ? { text: `${bereitschaft.nie_kontrolliert_gesamt} Objekte noch nie kontrolliert`, ziel: "/objekte" } : null, ].filter((a): a is { text: string; ziel: string | null } => a !== null); @@ -176,6 +177,12 @@ export function DashboardPage() { )} + {bereitschaft.nie_kontrolliert_gesamt > bereitschaft.unbekannt && ( +

+ Davon {bereitschaft.nie_kontrolliert_gesamt} insgesamt noch nie kontrolliert (auch bei Objekten mit + weiteren Problemen). +

+ )} {bereitschaft.details.length > 0 && ( <>