fix(dashboard): nie kontrollierte Objekte gelten nicht mehr als einsatzbereit
Nutzer-Fund (Readiness-Backlog-Review): einsatzbereitschaft() zählte ein Objekt ohne jede Kontrolle bisher als "einsatzbereit", weil keine Kriterien verletzt waren - fachlich falsch, "nie geprüft" ist nicht dasselbe wie "geprüft und in Ordnung". Neuer vierter Zustand "unbekannt": Objekte ohne abgeschlossene Kontrolle UND ohne sonstige Blocker fallen jetzt hierunter statt unter "einsatzbereit". Objekte mit anderen Gründen (Fehlbestand/Mangel/Prüfung/...) bleiben davon unberührt, die zählten schon vorher korrekt. Tests angepasst: zwei bestehende Tests nutzten ungeprüfte Objekte und erwarteten fälschlich "einsatzbereit" - jetzt entweder explizit kontrolliert (Ist=Soll) oder auf "unbekannt" korrigiert, plus neuer Test für den Kernfall. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KC8HYvv6UkCVYheYiTw9DD
This commit is contained in:
@@ -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 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
---
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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<string, string> = {
|
||||
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)
|
||||
</p>
|
||||
)}
|
||||
{bereitschaft.unbekannt > 0 && (
|
||||
<p className="text-muted">
|
||||
{bereitschaft.unbekannt} noch nie kontrolliert (Status unbekannt)
|
||||
</p>
|
||||
)}
|
||||
{bereitschaft.nicht_einsatzbereit > 0 && (
|
||||
<p className="text-muted" style={{ color: "var(--color-danger)" }}>
|
||||
{bereitschaft.nicht_einsatzbereit} nicht einsatzbereit
|
||||
|
||||
Reference in New Issue
Block a user