Neue Stammdaten-Tabelle einsatzfunktion (Migration 0031), benutzer bekommt optionales einsatzfunktion_id-Feld - analog zu einheit_id modelliert, eine Person hat im Regelfall genau eine primäre Einsatzfunktion. Bewusst getrennt von den technischen App-Rollen (RolleTyp), die nur steuern, was jemand in MABEA tun darf, nicht welche Funktion er im Einsatz hat. CRUD-Endpunkt /einsatzfunktionen nach Kategorie-Muster, Duplikat-Prüfung vorab (Projekt-Konvention). Admin-UI: Auswahl + Inline-Neuanlage in der Benutzerverwaltung (BenutzerSection). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
276 lines
9.6 KiB
Python
276 lines
9.6 KiB
Python
from datetime import date, timedelta
|
|
|
|
import pytest
|
|
|
|
from tests.conftest import auth_header, login
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_einheit_anlegen_und_hierarchie(client, admin_user):
|
|
token = await login(client, "admin1")
|
|
zug = await client.post("/api/v1/einheiten", json={"name": "Sanitätszug 1"}, headers=auth_header(token))
|
|
assert zug.status_code == 201
|
|
zug_id = zug.json()["id"]
|
|
|
|
gruppe = await client.post(
|
|
"/api/v1/einheiten",
|
|
json={"name": "Gruppe 1", "uebergeordnete_einheit_id": zug_id},
|
|
headers=auth_header(token),
|
|
)
|
|
assert gruppe.status_code == 201
|
|
assert gruppe.json()["uebergeordnete_einheit_id"] == zug_id
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_mitarbeiter_darf_einheit_nicht_anlegen(client, mitarbeiter_user):
|
|
token = await login(client, "mitarbeiter1")
|
|
response = await client.post("/api/v1/einheiten", json={"name": "X"}, headers=auth_header(token))
|
|
assert response.status_code == 403
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_qualifikationstyp_anlegen(client, admin_user):
|
|
token = await login(client, "admin1")
|
|
response = await client.post(
|
|
"/api/v1/qualifikationstypen",
|
|
json={"name": "Führerschein C1", "kategorie": "fuehrerschein"},
|
|
headers=auth_header(token),
|
|
)
|
|
assert response.status_code == 201
|
|
assert response.json()["gueltigkeit_monate"] is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_wer_darf_fahrzeug_fahren(
|
|
client, db_session, objekttyp_factory, standort_factory, admin_user, mitarbeiter_user, hauptserver_id
|
|
):
|
|
"""Nutzer-Beispiel Personal-Modul: Objekttyp verlangt Führerschein C1,
|
|
Benutzer ohne gültigen Nachweis darf nicht, mit gültigem Nachweis schon."""
|
|
from app.services.vorlagen import erstelle_vorlage
|
|
from app.services.objekte import objekt_aus_vorlage
|
|
|
|
objekttyp = await objekttyp_factory("GW-San", ist_zugfahrzeug=True)
|
|
standort = await standort_factory("Wache-Personal")
|
|
vorlage = await erstelle_vorlage(db_session, objekttyp_id=objekttyp.id, name="GW-San Standard", positionen=[])
|
|
fahrzeug = await objekt_aus_vorlage(
|
|
db_session,
|
|
code="GW-SAN-01",
|
|
name="GW-San 01",
|
|
standort_id=standort.id,
|
|
vorlage=vorlage,
|
|
zustaendiger_server_id=hauptserver_id,
|
|
)
|
|
|
|
token = await login(client, "admin1")
|
|
quali = await client.post(
|
|
"/api/v1/qualifikationstypen",
|
|
json={"name": "Führerschein C1", "kategorie": "fuehrerschein"},
|
|
headers=auth_header(token),
|
|
)
|
|
quali_id = quali.json()["id"]
|
|
|
|
await client.post(
|
|
f"/api/v1/objekttypen/{objekttyp.id}/qualifikationsanforderungen",
|
|
json={"objekttyp_id": objekttyp.id, "qualifikationstyp_id": quali_id},
|
|
headers=auth_header(token),
|
|
)
|
|
|
|
mitarbeiter_id = mitarbeiter_user.id
|
|
|
|
# ohne Nachweis: nicht berechtigt
|
|
pruefung = await client.get(
|
|
f"/api/v1/objekte/{fahrzeug.id}/berechtigung/{mitarbeiter_id}", headers=auth_header(token)
|
|
)
|
|
assert pruefung.json()["berechtigt"] is False
|
|
assert len(pruefung.json()["fehlende_qualifikationstypen"]) == 1
|
|
|
|
# abgelaufener Nachweis zählt nicht
|
|
await client.post(
|
|
f"/api/v1/benutzer/{mitarbeiter_id}/qualifikationen",
|
|
json={
|
|
"benutzer_id": mitarbeiter_id,
|
|
"qualifikationstyp_id": quali_id,
|
|
"erworben_am": (date.today() - timedelta(days=1000)).isoformat(),
|
|
"gueltig_bis": (date.today() - timedelta(days=1)).isoformat(),
|
|
},
|
|
headers=auth_header(token),
|
|
)
|
|
abgelaufen = await client.get(
|
|
f"/api/v1/objekte/{fahrzeug.id}/berechtigung/{mitarbeiter_id}", headers=auth_header(token)
|
|
)
|
|
assert abgelaufen.json()["berechtigt"] is False
|
|
|
|
# gültiger Nachweis: berechtigt
|
|
await client.post(
|
|
f"/api/v1/benutzer/{mitarbeiter_id}/qualifikationen",
|
|
json={
|
|
"benutzer_id": mitarbeiter_id,
|
|
"qualifikationstyp_id": quali_id,
|
|
"erworben_am": date.today().isoformat(),
|
|
"gueltig_bis": None,
|
|
},
|
|
headers=auth_header(token),
|
|
)
|
|
berechtigt = await client.get(
|
|
f"/api/v1/objekte/{fahrzeug.id}/berechtigung/{mitarbeiter_id}", headers=auth_header(token)
|
|
)
|
|
assert berechtigt.json()["berechtigt"] is True
|
|
assert berechtigt.json()["fehlende_qualifikationstypen"] == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_benutzer_einheit_zuordnung(client, admin_user):
|
|
token = await login(client, "admin1")
|
|
einheit = await client.post("/api/v1/einheiten", json={"name": "Zug 2"}, headers=auth_header(token))
|
|
einheit_id = einheit.json()["id"]
|
|
|
|
benutzer = await client.post(
|
|
"/api/v1/benutzer",
|
|
json={"name": "Neuer Helfer", "login": "helfer1", "passwort": "test-passwort-123", "einheit_id": einheit_id},
|
|
headers=auth_header(token),
|
|
)
|
|
assert benutzer.status_code == 201
|
|
assert benutzer.json()["einheit_id"] == einheit_id
|
|
|
|
benutzer_id = benutzer.json()["id"]
|
|
geloest = await client.patch(
|
|
f"/api/v1/benutzer/{benutzer_id}", json={"einheit_id": None}, headers=auth_header(token)
|
|
)
|
|
assert geloest.json()["einheit_id"] is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_einsatzfunktion_anlegen_und_benutzer_zuordnen(client, admin_user):
|
|
"""PERS-004: Einsatzfunktion unabhängig von der technischen App-Rolle."""
|
|
token = await login(client, "admin1")
|
|
|
|
funktion = await client.post(
|
|
"/api/v1/einsatzfunktionen", json={"name": "Gruppenführer"}, headers=auth_header(token)
|
|
)
|
|
assert funktion.status_code == 201
|
|
funktion_id = funktion.json()["id"]
|
|
|
|
duplikat = await client.post(
|
|
"/api/v1/einsatzfunktionen", json={"name": "Gruppenführer"}, headers=auth_header(token)
|
|
)
|
|
assert duplikat.status_code == 409
|
|
|
|
benutzer = await client.post(
|
|
"/api/v1/benutzer",
|
|
json={
|
|
"name": "Neuer Gruppenführer",
|
|
"login": "gf1",
|
|
"passwort": "test-passwort-123",
|
|
"einsatzfunktion_id": funktion_id,
|
|
},
|
|
headers=auth_header(token),
|
|
)
|
|
assert benutzer.status_code == 201
|
|
assert benutzer.json()["einsatzfunktion_id"] == funktion_id
|
|
|
|
benutzer_id = benutzer.json()["id"]
|
|
geloest = await client.patch(
|
|
f"/api/v1/benutzer/{benutzer_id}", json={"einsatzfunktion_id": None}, headers=auth_header(token)
|
|
)
|
|
assert geloest.json()["einsatzfunktion_id"] is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_mitarbeiter_darf_einsatzfunktion_nicht_anlegen(client, mitarbeiter_user):
|
|
token = await login(client, "mitarbeiter1")
|
|
response = await client.post(
|
|
"/api/v1/einsatzfunktionen", json={"name": "Melder"}, headers=auth_header(token)
|
|
)
|
|
assert response.status_code == 403
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_bald_ablaufende_qualifikation_erscheint_im_dashboard(
|
|
client, admin_user, mitarbeiter_user
|
|
):
|
|
from datetime import date, timedelta
|
|
|
|
token = await login(client, "admin1")
|
|
quali = await client.post(
|
|
"/api/v1/qualifikationstypen",
|
|
json={"name": "Führerschein C1", "kategorie": "fuehrerschein"},
|
|
headers=auth_header(token),
|
|
)
|
|
quali_id = quali.json()["id"]
|
|
|
|
await client.post(
|
|
f"/api/v1/benutzer/{mitarbeiter_user.id}/qualifikationen",
|
|
json={
|
|
"benutzer_id": mitarbeiter_user.id,
|
|
"qualifikationstyp_id": quali_id,
|
|
"erworben_am": (date.today() - timedelta(days=365)).isoformat(),
|
|
"gueltig_bis": (date.today() + timedelta(days=10)).isoformat(),
|
|
},
|
|
headers=auth_header(token),
|
|
)
|
|
|
|
response = await client.get("/api/v1/dashboard/qualifikationsablaeufe", headers=auth_header(token))
|
|
body = response.json()
|
|
assert len(body) == 1
|
|
assert body[0]["status"] == "bald_ablaufend"
|
|
assert body[0]["benutzer_id"] == mitarbeiter_user.id
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unbefristete_qualifikation_erscheint_nicht_im_dashboard(client, admin_user, mitarbeiter_user):
|
|
from datetime import date, timedelta
|
|
|
|
token = await login(client, "admin1")
|
|
quali = await client.post(
|
|
"/api/v1/qualifikationstypen",
|
|
json={"name": "Sanitätshelfer", "kategorie": "lehrgang"},
|
|
headers=auth_header(token),
|
|
)
|
|
quali_id = quali.json()["id"]
|
|
|
|
await client.post(
|
|
f"/api/v1/benutzer/{mitarbeiter_user.id}/qualifikationen",
|
|
json={
|
|
"benutzer_id": mitarbeiter_user.id,
|
|
"qualifikationstyp_id": quali_id,
|
|
"erworben_am": (date.today() - timedelta(days=365)).isoformat(),
|
|
"gueltig_bis": None,
|
|
},
|
|
headers=auth_header(token),
|
|
)
|
|
|
|
response = await client.get("/api/v1/dashboard/qualifikationsablaeufe", headers=auth_header(token))
|
|
assert response.json() == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_qualifikation_erfassen_steht_in_historie(client, admin_user, mitarbeiter_user):
|
|
from datetime import date
|
|
|
|
token = await login(client, "admin1")
|
|
quali = await client.post(
|
|
"/api/v1/qualifikationstypen",
|
|
json={"name": "Maschinist", "kategorie": "lehrgang"},
|
|
headers=auth_header(token),
|
|
)
|
|
quali_id = quali.json()["id"]
|
|
|
|
erfasst = await client.post(
|
|
f"/api/v1/benutzer/{mitarbeiter_user.id}/qualifikationen",
|
|
json={
|
|
"benutzer_id": mitarbeiter_user.id,
|
|
"qualifikationstyp_id": quali_id,
|
|
"erworben_am": date.today().isoformat(),
|
|
},
|
|
headers=auth_header(token),
|
|
)
|
|
qualifikation_id = erfasst.json()["id"]
|
|
|
|
historie = await client.get(
|
|
f"/api/v1/historie?entitaet_typ=benutzer_qualifikation&entitaet_id={qualifikation_id}",
|
|
headers=auth_header(token),
|
|
)
|
|
body = historie.json()
|
|
assert len(body) == 1
|
|
assert body[0]["ereignistyp"] == "qualifikation_erfasst"
|