feat(objekt): letzte abgeschlossene Kontrolle je Objekt anzeigen
CI / backend-tests (push) Failing after 1m28s
CI / frontend-build (push) Successful in 17s

Bisher nirgends vermerkt/abrufbar, wann ein Objekt zuletzt komplett
kontrolliert wurde (nur roh in kontrolle.beendet_am ableitbar). Neues Feld
ObjektRead.letzte_kontrolle_am (jüngste Kontrolle mit status=abgeschlossen),
angezeigt in Objektliste und Admin-Objektsektion ("noch nie kontrolliert"
falls keine).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KC8HYvv6UkCVYheYiTw9DD
This commit is contained in:
2026-09-05 11:00:49 +02:00
co-authored by Claude Sonnet 5
parent 69d6956d23
commit c3f141f7b0
10 changed files with 6599 additions and 14 deletions
+202
View File
@@ -3696,3 +3696,205 @@ Keine Commits in dieser Session.
- frontend/src/pages/admin/ObjektSection.tsx | 94 ++++++++++++++++++++++++++++++++++++---------------------------------- - frontend/src/pages/admin/ObjektSection.tsx | 94 ++++++++++++++++++++++++++++++++++++----------------------------------
--- ---
## 2026-09-05 10:32 10:33 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** backend
### Commits
- 54fc296 feat(objekt): Zugfahrzeug-Flag am Objekttyp + Leitungs-Dashboard-Kacheln
### Geänderte Dateien
- DEVLOG.md | 21 ++++
- backend/DEVLOG.md | 18 ++++
- backend/alembic/versions/0011_objekttyp_zugfahrzeug.py | 31 ++++++
- backend/app/api/v1/endpoints/dashboard.py | 33 ++++++-
- backend/app/api/v1/endpoints/objekte.py | 25 +++--
- backend/app/models/stammdaten.py | 4 +
- backend/app/schemas/dashboard.py | 31 ++++++
- backend/app/schemas/stammdaten.py | 2 +
- backend/app/services/dashboard.py | 138 ++++++++++++++++++++++++++
- backend/tests/conftest.py | 4 +-
- backend/tests/test_dashboard.py | 130 +++++++++++++++++++++++++
- backend/tests/test_objekte.py | 66 ++++++++++++-
- frontend/src/App.tsx | 18 ++++
- frontend/src/api/types.ts | 18 ++++
- frontend/src/components/AppShell.tsx | 10 ++
- frontend/src/pages/AdminPage.tsx | 1 +
- frontend/src/pages/DashboardPage.tsx | 161 +++++++++++++++++++++++++++++++
- frontend/src/pages/FehlbestandListePage.tsx | 152 +++++++++++++++++++++++++++++
- frontend/src/pages/admin/ObjektSection.tsx | 16 ++-
- frontend/src/pages/admin/ObjekttypSection.tsx | 32 +++++-
---
## 2026-09-05 10:33 10:33 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 21 ++++
- backend/DEVLOG.md | 18 ++++
- backend/alembic/versions/0011_objekttyp_zugfahrzeug.py | 31 ++++++
- backend/app/api/v1/endpoints/dashboard.py | 33 ++++++-
- backend/app/api/v1/endpoints/objekte.py | 25 +++--
- backend/app/models/stammdaten.py | 4 +
- backend/app/schemas/dashboard.py | 31 ++++++
- backend/app/schemas/stammdaten.py | 2 +
- backend/app/services/dashboard.py | 138 ++++++++++++++++++++++++++
- backend/tests/conftest.py | 4 +-
- backend/tests/test_dashboard.py | 130 +++++++++++++++++++++++++
- backend/tests/test_objekte.py | 66 ++++++++++++-
- frontend/src/App.tsx | 18 ++++
- frontend/src/api/types.ts | 18 ++++
- frontend/src/components/AppShell.tsx | 10 ++
- frontend/src/pages/AdminPage.tsx | 1 +
- frontend/src/pages/DashboardPage.tsx | 161 +++++++++++++++++++++++++++++++
- frontend/src/pages/FehlbestandListePage.tsx | 152 +++++++++++++++++++++++++++++
- frontend/src/pages/admin/ObjektSection.tsx | 16 ++-
- frontend/src/pages/admin/ObjekttypSection.tsx | 32 +++++-
---
## 2026-09-05 10:33 10:35 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 21 ++++
- backend/DEVLOG.md | 18 ++++
- backend/alembic/versions/0011_objekttyp_zugfahrzeug.py | 31 ++++++
- backend/app/api/v1/endpoints/dashboard.py | 33 ++++++-
- backend/app/api/v1/endpoints/objekte.py | 25 +++--
- backend/app/models/stammdaten.py | 4 +
- backend/app/schemas/dashboard.py | 31 ++++++
- backend/app/schemas/stammdaten.py | 2 +
- backend/app/services/dashboard.py | 138 ++++++++++++++++++++++++++
- backend/tests/conftest.py | 4 +-
- backend/tests/test_dashboard.py | 130 +++++++++++++++++++++++++
- backend/tests/test_objekte.py | 66 ++++++++++++-
- frontend/src/App.tsx | 18 ++++
- frontend/src/api/types.ts | 18 ++++
- frontend/src/components/AppShell.tsx | 10 ++
- frontend/src/pages/AdminPage.tsx | 1 +
- frontend/src/pages/DashboardPage.tsx | 161 +++++++++++++++++++++++++++++++
- frontend/src/pages/FehlbestandListePage.tsx | 152 +++++++++++++++++++++++++++++
- frontend/src/pages/admin/ObjektSection.tsx | 16 ++-
- frontend/src/pages/admin/ObjekttypSection.tsx | 32 +++++-
---
## 2026-09-05 10:35 10:35 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 21 ++++
- backend/DEVLOG.md | 18 ++++
- backend/alembic/versions/0011_objekttyp_zugfahrzeug.py | 31 ++++++
- backend/app/api/v1/endpoints/dashboard.py | 33 ++++++-
- backend/app/api/v1/endpoints/objekte.py | 25 +++--
- backend/app/models/stammdaten.py | 4 +
- backend/app/schemas/dashboard.py | 31 ++++++
- backend/app/schemas/stammdaten.py | 2 +
- backend/app/services/dashboard.py | 138 ++++++++++++++++++++++++++
- backend/tests/conftest.py | 4 +-
- backend/tests/test_dashboard.py | 130 +++++++++++++++++++++++++
- backend/tests/test_objekte.py | 66 ++++++++++++-
- frontend/src/App.tsx | 18 ++++
- frontend/src/api/types.ts | 18 ++++
- frontend/src/components/AppShell.tsx | 10 ++
- frontend/src/pages/AdminPage.tsx | 1 +
- frontend/src/pages/DashboardPage.tsx | 161 +++++++++++++++++++++++++++++++
- frontend/src/pages/FehlbestandListePage.tsx | 152 +++++++++++++++++++++++++++++
- frontend/src/pages/admin/ObjektSection.tsx | 16 ++-
- frontend/src/pages/admin/ObjekttypSection.tsx | 32 +++++-
---
## 2026-09-05 10:36 10:36 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 21 ++++
- backend/DEVLOG.md | 18 ++++
- backend/alembic/versions/0011_objekttyp_zugfahrzeug.py | 31 ++++++
- backend/app/api/v1/endpoints/dashboard.py | 33 ++++++-
- backend/app/api/v1/endpoints/objekte.py | 25 +++--
- backend/app/models/stammdaten.py | 4 +
- backend/app/schemas/dashboard.py | 31 ++++++
- backend/app/schemas/stammdaten.py | 2 +
- backend/app/services/dashboard.py | 138 ++++++++++++++++++++++++++
- backend/tests/conftest.py | 4 +-
- backend/tests/test_dashboard.py | 130 +++++++++++++++++++++++++
- backend/tests/test_objekte.py | 66 ++++++++++++-
- frontend/src/App.tsx | 18 ++++
- frontend/src/api/types.ts | 18 ++++
- frontend/src/components/AppShell.tsx | 10 ++
- frontend/src/pages/AdminPage.tsx | 1 +
- frontend/src/pages/DashboardPage.tsx | 161 +++++++++++++++++++++++++++++++
- frontend/src/pages/FehlbestandListePage.tsx | 152 +++++++++++++++++++++++++++++
- frontend/src/pages/admin/ObjektSection.tsx | 16 ++-
- frontend/src/pages/admin/ObjekttypSection.tsx | 32 +++++-
---
## 2026-09-05 10:37 10:37 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 21 ++++
- backend/DEVLOG.md | 18 ++++
- backend/alembic/versions/0011_objekttyp_zugfahrzeug.py | 31 ++++++
- backend/app/api/v1/endpoints/dashboard.py | 33 ++++++-
- backend/app/api/v1/endpoints/objekte.py | 25 +++--
- backend/app/models/stammdaten.py | 4 +
- backend/app/schemas/dashboard.py | 31 ++++++
- backend/app/schemas/stammdaten.py | 2 +
- backend/app/services/dashboard.py | 138 ++++++++++++++++++++++++++
- backend/tests/conftest.py | 4 +-
- backend/tests/test_dashboard.py | 130 +++++++++++++++++++++++++
- backend/tests/test_objekte.py | 66 ++++++++++++-
- frontend/src/App.tsx | 18 ++++
- frontend/src/api/types.ts | 18 ++++
- frontend/src/components/AppShell.tsx | 10 ++
- frontend/src/pages/AdminPage.tsx | 1 +
- frontend/src/pages/DashboardPage.tsx | 161 +++++++++++++++++++++++++++++++
- frontend/src/pages/FehlbestandListePage.tsx | 152 +++++++++++++++++++++++++++++
- frontend/src/pages/admin/ObjektSection.tsx | 16 ++-
- frontend/src/pages/admin/ObjekttypSection.tsx | 32 +++++-
---
## 2026-09-05 10:38 10:39 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
- 69d6956 feat(login): Verantwortliche landen nach Login auf /dashboard
### Geänderte Dateien
- frontend/src/pages/LoginPage.tsx | 11 ++++++++++-
---
## 2026-09-05 10:40 10:40 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/src/pages/LoginPage.tsx | 11 ++++++++++-
---
+34 -12
View File
@@ -24,12 +24,27 @@ from app.schemas.objekt import (
) )
from app.services.objekte import ( from app.services.objekte import (
dupliziere_objekt, dupliziere_objekt,
letzte_kontrollen_map,
naechster_freier_code, naechster_freier_code,
objekt_aus_vorlage, objekt_aus_vorlage,
sollmenge_effektiv, sollmenge_effektiv,
sollmenge_map, sollmenge_map,
) )
def _objekt_read(objekt: Objekt, letzte_kontrolle_am=None) -> ObjektRead:
return ObjektRead(
id=objekt.id,
code=objekt.code,
name=objekt.name,
objekttyp_id=objekt.objekttyp_id,
vorlage_id=objekt.vorlage_id,
standort_id=objekt.standort_id,
status=objekt.status,
fahrzeug_id=objekt.fahrzeug_id,
letzte_kontrolle_am=letzte_kontrolle_am,
)
router = APIRouter() router = APIRouter()
_admin_only = require_roles(RolleTyp.administration) _admin_only = require_roles(RolleTyp.administration)
@@ -57,25 +72,28 @@ async def liste_objekte(
standort_id: int | None = None, standort_id: int | None = None,
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
_=Depends(get_current_user), _=Depends(get_current_user),
) -> list[Objekt]: ) -> list[ObjektRead]:
stmt = select(Objekt) stmt = select(Objekt)
if standort_id is not None: if standort_id is not None:
stmt = stmt.where(Objekt.standort_id == standort_id) stmt = stmt.where(Objekt.standort_id == standort_id)
result = await db.execute(stmt) result = await db.execute(stmt)
return list(result.scalars().all()) objekte = list(result.scalars().all())
letzte_map = await letzte_kontrollen_map(db, [o.id for o in objekte])
return [_objekt_read(o, letzte_map.get(o.id)) for o in objekte]
@router.get("/objekte/code/{code}", response_model=ObjektRead) @router.get("/objekte/code/{code}", response_model=ObjektRead)
async def hole_objekt_per_code( async def hole_objekt_per_code(
code: str, db: AsyncSession = Depends(get_db), _=Depends(get_current_user) code: str, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)
) -> Objekt: ) -> ObjektRead:
"""Lookup per QR/Barcode-Code (Karte 10). Muss VOR /objekte/{id} registriert """Lookup per QR/Barcode-Code (Karte 10). Muss VOR /objekte/{id} registriert
sein, sonst greift FastAPI/Starlette-Routing zuerst die generische ID-Route.""" sein, sonst greift FastAPI/Starlette-Routing zuerst die generische ID-Route."""
result = await db.execute(select(Objekt).where(Objekt.code == code)) result = await db.execute(select(Objekt).where(Objekt.code == code))
objekt = result.scalar_one_or_none() objekt = result.scalar_one_or_none()
if objekt is None: if objekt is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Objekt nicht gefunden") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Objekt nicht gefunden")
return objekt letzte_map = await letzte_kontrollen_map(db, [objekt.id])
return _objekt_read(objekt, letzte_map.get(objekt.id))
@router.get("/objekte/naechster-code") @router.get("/objekte/naechster-code")
@@ -129,11 +147,12 @@ async def hole_objektposition_per_code(
@router.get("/objekte/{objekt_id}", response_model=ObjektRead) @router.get("/objekte/{objekt_id}", response_model=ObjektRead)
async def hole_objekt( async def hole_objekt(
objekt_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user) objekt_id: int, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)
) -> Objekt: ) -> ObjektRead:
objekt = await db.get(Objekt, objekt_id) objekt = await db.get(Objekt, objekt_id)
if objekt is None: if objekt is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Objekt nicht gefunden") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Objekt nicht gefunden")
return objekt letzte_map = await letzte_kontrollen_map(db, [objekt.id])
return _objekt_read(objekt, letzte_map.get(objekt.id))
@router.get("/objekte/{objekt_id}/label.pdf") @router.get("/objekte/{objekt_id}/label.pdf")
@@ -205,7 +224,7 @@ async def liste_objektpositionen(
@router.post("/objekte", response_model=ObjektRead, status_code=status.HTTP_201_CREATED) @router.post("/objekte", response_model=ObjektRead, status_code=status.HTTP_201_CREATED)
async def erstelle_objekt( async def erstelle_objekt(
payload: ObjektCreate, db: AsyncSession = Depends(get_db), _=Depends(_admin_only) payload: ObjektCreate, db: AsyncSession = Depends(get_db), _=Depends(_admin_only)
) -> Objekt: ) -> ObjektRead:
"""Prompt 09: neues Objekt aus einer Beladungsvorlage, startet "nicht kontrolliert" """Prompt 09: neues Objekt aus einer Beladungsvorlage, startet "nicht kontrolliert"
(Ist-Menge 0 in allen übernommenen Positionen).""" (Ist-Menge 0 in allen übernommenen Positionen)."""
vorlage = await db.get(Beladungsvorlage, payload.vorlage_id) vorlage = await db.get(Beladungsvorlage, payload.vorlage_id)
@@ -218,7 +237,7 @@ async def erstelle_objekt(
zustaendiger_server_id = hauptserver.scalar_one() zustaendiger_server_id = hauptserver.scalar_one()
try: try:
return await objekt_aus_vorlage( objekt = await objekt_aus_vorlage(
db, db,
code=payload.code, code=payload.code,
name=payload.name, name=payload.name,
@@ -229,12 +248,13 @@ async def erstelle_objekt(
) )
except IntegrityError as exc: except IntegrityError as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Code bereits vergeben") from exc raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Code bereits vergeben") from exc
return _objekt_read(objekt)
@router.patch("/objekte/{objekt_id}", response_model=ObjektRead) @router.patch("/objekte/{objekt_id}", response_model=ObjektRead)
async def aendere_objekt( async def aendere_objekt(
objekt_id: int, payload: ObjektUpdate, db: AsyncSession = Depends(get_db), _=Depends(_admin_only) objekt_id: int, payload: ObjektUpdate, db: AsyncSession = Depends(get_db), _=Depends(_admin_only)
) -> Objekt: ) -> ObjektRead:
"""Fahrzeug-Zuordnung nachträglich setzen/lösen: jedes Objekt gehört zu """Fahrzeug-Zuordnung nachträglich setzen/lösen: jedes Objekt gehört zu
jedem Zeitpunkt höchstens einem Fahrzeug (1:n). Zielobjekt-Existenz und jedem Zeitpunkt höchstens einem Fahrzeug (1:n). Zielobjekt-Existenz und
direkter Zyklus (A↔B) werden geprüft, mehrstufige Ketten sind fachlich direkter Zyklus (A↔B) werden geprüft, mehrstufige Ketten sind fachlich
@@ -259,7 +279,8 @@ async def aendere_objekt(
await db.flush() await db.flush()
except IntegrityError as exc: except IntegrityError as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Ungültige Zuordnung") from exc raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Ungültige Zuordnung") from exc
return objekt letzte_map = await letzte_kontrollen_map(db, [objekt.id])
return _objekt_read(objekt, letzte_map.get(objekt.id))
@router.post( @router.post(
@@ -270,7 +291,7 @@ async def dupliziere(
payload: ObjektDuplizierenRequest, payload: ObjektDuplizierenRequest,
db: AsyncSession = Depends(get_db), db: AsyncSession = Depends(get_db),
_=Depends(_admin_only), _=Depends(_admin_only),
) -> Objekt: ) -> ObjektRead:
"""Prompt 09: Struktur/Sollmengen kopiert, Ist-Menge/SN/Ablauf/Charge NICHT, """Prompt 09: Struktur/Sollmengen kopiert, Ist-Menge/SN/Ablauf/Charge NICHT,
neuer eindeutiger Code Pflicht.""" neuer eindeutiger Code Pflicht."""
quelle = await db.get(Objekt, objekt_id) quelle = await db.get(Objekt, objekt_id)
@@ -278,7 +299,7 @@ async def dupliziere(
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Quell-Objekt nicht gefunden") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Quell-Objekt nicht gefunden")
try: try:
return await dupliziere_objekt( neues_objekt = await dupliziere_objekt(
db, db,
quelle=quelle, quelle=quelle,
neuer_code=payload.neuer_code, neuer_code=payload.neuer_code,
@@ -288,6 +309,7 @@ async def dupliziere(
) )
except IntegrityError as exc: except IntegrityError as exc:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Code bereits vergeben") from exc raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Code bereits vergeben") from exc
return _objekt_read(neues_objekt)
@router.patch("/objekte/{objekt_id}/positionen/{position_id}", response_model=ObjektpositionRead) @router.patch("/objekte/{objekt_id}/positionen/{position_id}", response_model=ObjektpositionRead)
+2 -1
View File
@@ -1,5 +1,5 @@
import uuid import uuid
from datetime import date from datetime import date, datetime
from decimal import Decimal from decimal import Decimal
from pydantic import BaseModel, ConfigDict from pydantic import BaseModel, ConfigDict
@@ -27,6 +27,7 @@ class ObjektRead(BaseModel):
standort_id: int standort_id: int
status: ObjektStatus status: ObjektStatus
fahrzeug_id: int | None fahrzeug_id: int | None
letzte_kontrolle_am: datetime | None = None
class ObjektUpdate(BaseModel): class ObjektUpdate(BaseModel):
+19 -1
View File
@@ -1,9 +1,11 @@
import re import re
from datetime import datetime
from decimal import Decimal from decimal import Decimal
from sqlalchemy import select from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.models.kontrolle import Kontrolle, KontrollStatus
from app.models.objekt import Objekt from app.models.objekt import Objekt
from app.models.objektposition import Objektposition, ObjektpositionStatus from app.models.objektposition import Objektposition, ObjektpositionStatus
from app.models.vorlage import Beladungsvorlage, Vorlagenposition from app.models.vorlage import Beladungsvorlage, Vorlagenposition
@@ -29,6 +31,22 @@ async def naechster_freier_code(db: AsyncSession, spalte, praefix: str) -> str:
return f"{praefix}-{hoechste + 1:0{breite}d}" return f"{praefix}-{hoechste + 1:0{breite}d}"
async def letzte_kontrollen_map(db: AsyncSession, objekt_ids: list[int]) -> dict[int, datetime]:
"""Nutzer-Frage: "wann war die letzte komplette Kontrolle vom Objekt" - bisher
nirgends vermerkt (kein Cache-Feld am Objekt, nur ableitbar aus kontrolle.
beendet_am). Liefert je Objekt den jüngsten Abschluss-Zeitpunkt einer
Kontrolle mit status=abgeschlossen; Objekte ohne abgeschlossene Kontrolle
fehlen im Ergebnis (Aufrufer behandelt das als "noch nie kontrolliert")."""
if not objekt_ids:
return {}
result = await db.execute(
select(Kontrolle.objekt_id, func.max(Kontrolle.beendet_am))
.where(Kontrolle.objekt_id.in_(objekt_ids), Kontrolle.status == KontrollStatus.abgeschlossen)
.group_by(Kontrolle.objekt_id)
)
return {objekt_id: beendet_am for objekt_id, beendet_am in result.all()}
async def sollmenge_map(db: AsyncSession, vorlage_id: int | None) -> dict[int, Decimal]: async def sollmenge_map(db: AsyncSession, vorlage_id: int | None) -> dict[int, Decimal]:
"""material_id -> Sollmenge laut Vorlage (Prompt 10: Basis für die dynamische """material_id -> Sollmenge laut Vorlage (Prompt 10: Basis für die dynamische
Auflösung, wenn eine Objektposition keinen Override hat).""" Auflösung, wenn eine Objektposition keinen Override hat)."""
+32
View File
@@ -5,6 +5,38 @@ from app.schemas.vorlage import VorlagenpositionCreate
from tests.conftest import auth_header, login from tests.conftest import auth_header, login
@pytest.mark.asyncio
async def test_letzte_kontrolle_am_wird_erst_nach_abschluss_gesetzt(
client, objekt_mit_position, mitarbeiter_user
):
"""Nutzer-Frage: wann war die letzte KOMPLETTE Kontrolle - vorher (None/
"noch nie"), nach starte_kontrolle allein noch nicht (erst status=abgeschlossen
zählt), erst nach /abschliessen gesetzt."""
objekt, material = objekt_mit_position
token = await login(client, "mitarbeiter1")
vor_kontrolle = await client.get(f"/api/v1/objekte/{objekt.id}", headers=auth_header(token))
assert vor_kontrolle.json()["letzte_kontrolle_am"] is None
start = await client.post(
f"/api/v1/objekte/{objekt.id}/kontrollen", json={"uebernehmen": False}, headers=auth_header(token)
)
kontrolle_id = start.json()["id"]
await client.put(
f"/api/v1/kontrollen/{kontrolle_id}/positionen/{material.id}",
json={"istmenge": "10"},
headers=auth_header(token),
)
waehrend = await client.get(f"/api/v1/objekte/{objekt.id}", headers=auth_header(token))
assert waehrend.json()["letzte_kontrolle_am"] is None # noch nicht abgeschlossen
await client.post(f"/api/v1/kontrollen/{kontrolle_id}/abschliessen", headers=auth_header(token))
nach_abschluss = await client.get(f"/api/v1/objekte/{objekt.id}", headers=auth_header(token))
assert nach_abschluss.json()["letzte_kontrolle_am"] is not None
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_objekt_aus_vorlage_startet_unkontrolliert( async def test_objekt_aus_vorlage_startet_unkontrolliert(
client, db_session, objekttyp_factory, material_factory, standort_factory, admin_user client, db_session, objekttyp_factory, material_factory, standort_factory, admin_user
+33
View File
@@ -191,3 +191,36 @@ Keine Commits in dieser Session.
- frontend/src/pages/admin/ObjektSection.tsx | 29 ++++++++++++++++++++++++----- - frontend/src/pages/admin/ObjektSection.tsx | 29 ++++++++++++++++++++++++-----
--- ---
## 2026-09-05 10:40 10:47 (6m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/src/pages/LoginPage.tsx | 11 ++++++++++-
---
## 2026-09-05 10:50 10:50 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** frontend
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/src/pages/LoginPage.tsx | 11 ++++++++++-
---
## 2026-09-05 10:51 10:51 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** frontend
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/src/pages/LoginPage.tsx | 11 ++++++++++-
---
+6264
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -7,6 +7,7 @@ export interface Objekt {
standort_id: number; standort_id: number;
status: string; status: string;
fahrzeug_id: number | null; fahrzeug_id: number | null;
letzte_kontrolle_am: string | null;
} }
export interface Objektposition { export interface Objektposition {
+5
View File
@@ -76,6 +76,11 @@ export function ObjektListPage() {
<span className="badge badge-neutral">{objekt.status}</span> <span className="badge badge-neutral">{objekt.status}</span>
</div> </div>
<div className="card-subtitle">{objekt.code}</div> <div className="card-subtitle">{objekt.code}</div>
<div className="text-muted" style={{ fontSize: "0.85rem" }}>
{objekt.letzte_kontrolle_am
? `Letzte Kontrolle: ${new Date(objekt.letzte_kontrolle_am).toLocaleDateString("de-DE")}`
: "Noch nie kontrolliert"}
</div>
</Link> </Link>
</li> </li>
))} ))}
@@ -277,6 +277,13 @@ export function ObjektSection({
{o.fahrzeug_id !== null && ( {o.fahrzeug_id !== null && (
<span className="text-muted"> Fahrzeug: {objektName(o.fahrzeug_id)}</span> <span className="text-muted"> Fahrzeug: {objektName(o.fahrzeug_id)}</span>
)} )}
<span className="text-muted">
{" "}
{" "}
{o.letzte_kontrolle_am
? `zuletzt kontrolliert ${new Date(o.letzte_kontrolle_am).toLocaleDateString("de-DE")}`
: "noch nie kontrolliert"}
</span>
</span> </span>
<div className="row"> <div className="row">
<button <button