feat(maintenance): MAINT-Epic (Wartungspläne/-aufträge, Werkstatt) + Akte-Redesign
Neues Wartungskonzept: Wartungsplan+Position+Intervall (zeit/km/ Betriebsstunden, kombinierbar - fällig sobald ein Kriterium erreicht ist), Wartungsauftrag mit intern/externer Werkstatt, Status offen/ in_arbeit/erledigt, Kosten, Historie, Dokument-Anhang. Ad-hoc-Aufträge ohne Plan möglich (z.B. kleinere Reparatur). Admin-Tab "Wartungspläne". Objektakte-Redesign (Epic 21, Nutzer-Feedback "wirkt nicht modern"): CSS-Grid statt Karten-Stapel, einspaltig mobil, zweispaltig ab 900px; Fahrzeug-Objekte zeigen Wartung/Fahrzeugdaten prominent, Geräte/ Rucksäcke die Beladung. Dabei gefundene Lücke: Beladung (Soll/Ist je Position) fehlte in der Akte komplett, nur über eine laufende Kontrolle sichtbar - jetzt eigene Karte. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
@@ -50,6 +50,10 @@ async def test_akte_buendelt_teilbereiche(client, db_session, objekt_mit_positio
|
||||
assert body["historie"][0]["entitaet_typ"] == "objekt"
|
||||
assert body["einsatzbereitschaft_status"] == "not-ready"
|
||||
assert "mangel_kritisch_offen" in body["einsatzbereitschaft_gruende"]
|
||||
assert len(body["beladung"]) == 1
|
||||
assert body["beladung"][0]["material_id"] == _material.id
|
||||
assert body["beladung"][0]["sollmenge"] == "10"
|
||||
assert body["beladung"][0]["istmenge"] == "0"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
from app.models.objekt import Objekt
|
||||
from app.services.datumshilfen import plus_monate
|
||||
from tests.conftest import auth_header, login
|
||||
|
||||
|
||||
async def _objekt_mit_typ(db_session, hauptserver_id, *, code, name, objekttyp, standort):
|
||||
objekt = Objekt(
|
||||
code=code,
|
||||
name=name,
|
||||
objekttyp_id=objekttyp.id,
|
||||
standort_id=standort.id,
|
||||
zustaendiger_server_id=hauptserver_id,
|
||||
)
|
||||
db_session.add(objekt)
|
||||
await db_session.flush()
|
||||
return objekt
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wartungsplan_mit_intervall_anlegen(client, admin_user, objekttyp_factory):
|
||||
token = await login(client, "admin1")
|
||||
objekttyp = await objekttyp_factory("RTW-Test")
|
||||
|
||||
response = await client.post(
|
||||
"/api/v1/wartungsplaene",
|
||||
json={
|
||||
"objekttyp_id": objekttyp.id,
|
||||
"name": "RTW Standard",
|
||||
"positionen": [
|
||||
{
|
||||
"wartungsart": "Ölwechsel",
|
||||
"intervalle": [{"typ": "zeit", "wert": 12}, {"typ": "km", "wert": 15000}],
|
||||
}
|
||||
],
|
||||
},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
assert response.status_code == 201
|
||||
body = response.json()
|
||||
assert len(body["positionen"]) == 1
|
||||
assert len(body["positionen"][0]["intervalle"]) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_nie_durchgefuehrte_wartung_ist_faellig(
|
||||
client, db_session, hauptserver_id, admin_user, objekttyp_factory, standort_factory
|
||||
):
|
||||
token = await login(client, "admin1")
|
||||
objekttyp = await objekttyp_factory("RTW-Test2")
|
||||
standort = await standort_factory("Standort-W2")
|
||||
objekt = await _objekt_mit_typ(
|
||||
db_session, hauptserver_id, code="RTW-W2", name="RTW W2", objekttyp=objekttyp, standort=standort
|
||||
)
|
||||
objekt_id = objekt.id
|
||||
|
||||
await client.post(
|
||||
"/api/v1/wartungsplaene",
|
||||
json={"objekttyp_id": objekttyp.id, "name": "Plan", "positionen": [{"wartungsart": "Ölwechsel"}]},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
|
||||
response = await client.get(f"/api/v1/objekte/{objekt_id}/faellige-wartungen", headers=auth_header(token))
|
||||
assert response.status_code == 200
|
||||
body = response.json()
|
||||
assert len(body) == 1
|
||||
assert body[0]["faellig"] is True
|
||||
assert body[0]["wartungsart"] == "Ölwechsel"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_wartungsauftrag_erledigen_macht_zeit_intervall_nicht_faellig(
|
||||
client, db_session, hauptserver_id, admin_user, objekttyp_factory, standort_factory
|
||||
):
|
||||
token = await login(client, "admin1")
|
||||
objekttyp = await objekttyp_factory("RTW-Test3")
|
||||
standort = await standort_factory("Standort-W3")
|
||||
objekt = await _objekt_mit_typ(
|
||||
db_session, hauptserver_id, code="RTW-W3", name="RTW W3", objekttyp=objekttyp, standort=standort
|
||||
)
|
||||
objekt_id = objekt.id
|
||||
|
||||
plan = await client.post(
|
||||
"/api/v1/wartungsplaene",
|
||||
json={
|
||||
"objekttyp_id": objekttyp.id,
|
||||
"name": "Plan",
|
||||
"positionen": [{"wartungsart": "Ölwechsel", "intervalle": [{"typ": "zeit", "wert": 12}]}],
|
||||
},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
position_id = plan.json()["positionen"][0]["id"]
|
||||
|
||||
auftrag = await client.post(
|
||||
"/api/v1/wartungsauftraege",
|
||||
json={"objekt_id": objekt_id, "wartungsplan_position_id": position_id, "wartungsart": "Ölwechsel"},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
assert auftrag.status_code == 201
|
||||
auftrag_id = auftrag.json()["id"]
|
||||
|
||||
erledigt = await client.post(
|
||||
f"/api/v1/wartungsauftraege/{auftrag_id}/erledigen",
|
||||
json={"erledigt_am": date.today().isoformat()},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
assert erledigt.status_code == 200
|
||||
assert erledigt.json()["status"] == "erledigt"
|
||||
|
||||
faellig = await client.get(f"/api/v1/objekte/{objekt_id}/faellige-wartungen", headers=auth_header(token))
|
||||
assert faellig.json()[0]["faellig"] is False
|
||||
assert faellig.json()[0]["faellig_am"] == plus_monate(date.today(), 12).isoformat()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_km_intervall_wird_zuerst_faellig_trotz_gueltigem_zeit_intervall(
|
||||
client, db_session, hauptserver_id, admin_user, objekttyp_factory, standort_factory
|
||||
):
|
||||
""""je nachdem was zuerst eintritt" - km-Schwelle erreicht, obwohl das
|
||||
Zeit-Intervall noch nicht abgelaufen ist."""
|
||||
from app.models.fahrzeugdetails import Fahrzeugdetails
|
||||
|
||||
token = await login(client, "admin1")
|
||||
objekttyp = await objekttyp_factory("RTW-Test4")
|
||||
standort = await standort_factory("Standort-W4")
|
||||
objekt = await _objekt_mit_typ(
|
||||
db_session, hauptserver_id, code="RTW-W4", name="RTW W4", objekttyp=objekttyp, standort=standort
|
||||
)
|
||||
objekt_id = objekt.id
|
||||
|
||||
db_session.add(Fahrzeugdetails(objekt_id=objekt_id, kilometerstand=20000))
|
||||
await db_session.flush()
|
||||
|
||||
plan = await client.post(
|
||||
"/api/v1/wartungsplaene",
|
||||
json={
|
||||
"objekttyp_id": objekttyp.id,
|
||||
"name": "Plan",
|
||||
"positionen": [
|
||||
{
|
||||
"wartungsart": "Ölwechsel",
|
||||
"intervalle": [{"typ": "zeit", "wert": 12}, {"typ": "km", "wert": 15000}],
|
||||
}
|
||||
],
|
||||
},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
position_id = plan.json()["positionen"][0]["id"]
|
||||
|
||||
auftrag = await client.post(
|
||||
"/api/v1/wartungsauftraege",
|
||||
json={"objekt_id": objekt_id, "wartungsplan_position_id": position_id, "wartungsart": "Ölwechsel"},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
auftrag_id = auftrag.json()["id"]
|
||||
await client.post(
|
||||
f"/api/v1/wartungsauftraege/{auftrag_id}/erledigen",
|
||||
json={"erledigt_am": date.today().isoformat(), "kilometerstand_bei_erledigung": 5000},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
|
||||
faellig = await client.get(f"/api/v1/objekte/{objekt_id}/faellige-wartungen", headers=auth_header(token))
|
||||
body = faellig.json()[0]
|
||||
assert body["faellig"] is True
|
||||
assert body["faellig_bei_km"] == 20000
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ad_hoc_reparatur_ohne_wartungsplan_mit_externer_werkstatt(
|
||||
client, admin_user, standort_factory, objekt_factory
|
||||
):
|
||||
token = await login(client, "admin1")
|
||||
standort = await standort_factory("Standort-W5")
|
||||
await objekt_factory(name="RTW W5", code="RTW-W5", standort=standort)
|
||||
objekte = await client.get("/api/v1/objekte", headers=auth_header(token))
|
||||
objekt_id = [o for o in objekte.json() if o["code"] == "RTW-W5"][0]["id"]
|
||||
|
||||
auftrag = await client.post(
|
||||
"/api/v1/wartungsauftraege",
|
||||
json={
|
||||
"objekt_id": objekt_id,
|
||||
"wartungsart": "Bremsleuchte defekt",
|
||||
"werkstatt_intern": False,
|
||||
"werkstatt_name": "KFZ Meyer GmbH",
|
||||
},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
assert auftrag.status_code == 201
|
||||
body = auftrag.json()
|
||||
assert body["wartungsplan_position_id"] is None
|
||||
assert body["werkstatt_intern"] is False
|
||||
assert body["werkstatt_name"] == "KFZ Meyer GmbH"
|
||||
assert body["status"] == "offen"
|
||||
|
||||
in_arbeit = await client.patch(
|
||||
f"/api/v1/wartungsauftraege/{body['id']}/status",
|
||||
json={"status": "in_arbeit"},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
assert in_arbeit.json()["status"] == "in_arbeit"
|
||||
|
||||
erledigt = await client.post(
|
||||
f"/api/v1/wartungsauftraege/{body['id']}/erledigen",
|
||||
json={"kosten": "89.90"},
|
||||
headers=auth_header(token),
|
||||
)
|
||||
assert erledigt.json()["status"] == "erledigt"
|
||||
assert erledigt.json()["kosten"] == "89.90"
|
||||
|
||||
historie = await client.get(f"/api/v1/wartungsauftraege?objekt_id={objekt_id}", headers=auth_header(token))
|
||||
assert len(historie.json()) == 1
|
||||
Reference in New Issue
Block a user