feat(wartung): MAINT-005 Ersatzteile/Kosten je Wartungsauftrag
CI / backend-tests (push) Successful in 2m22s
CI / frontend-build (push) Successful in 25s

Neue Tabelle wartungsauftrag_teil (Migration 0033), CRUD-Endpunkte
/wartungsauftraege/{id}/teile, UI-Panel in WartungSection.tsx mit
Summenanzeige. Backlog Epic 22 (UI-Redesign Runde 2, Kacheln UI2-001..005)
als geplant ergänzt, Umsetzung folgt separat.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-08 22:06:02 +02:00
co-authored by Claude Sonnet 5
parent 3b5cf8feab
commit 847c303a97
14 changed files with 5232 additions and 6 deletions
+176
View File
@@ -627,3 +627,179 @@ Keine Commits in dieser Session.
- frontend/src/styles/global.css | 2 ++
---
## 2026-09-07 22:33 22:35 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- .claude/agents/ui-designer.md | 174 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-09-07 23:25 23:25 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- .claude/agents/accessibility-expert.md | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- .claude/agents/ui-visual-validator.md | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-09-07 23:26 23:26 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** frontend
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- .claude/agents/accessibility-expert.md | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- .claude/agents/ui-visual-validator.md | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-09-07 23:27 23:29 (2m)
**Beschreibung:** Claude Code Session
**Projekt:** frontend
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- .claude/agents/accessibility-expert.md | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- .claude/agents/ui-visual-validator.md | 192 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
---
## 2026-09-08 00:39 00:43 (3m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/src/pages/KontrollPage.tsx | 27 ++++++++++++++++-----------
- frontend/src/pages/kontrolle/NachfuellDialog.tsx | 36 +++++++++++++++++++++++++++++++++---
- frontend/src/pages/kontrolle/PositionCard.tsx | 35 +++++++++++++++++++++++++++++++++--
- frontend/src/styles/global.css | 19 ++++++++++++++++++-
---
## 2026-09-08 00:52 00:58 (6m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- .gitignore | 1 +
- backend/alembic/versions/0023_suchindex.py | 26 ++++++++++++++++++++++++++
- backend/app/api/v1/api.py | 2 ++
- backend/app/api/v1/endpoints/suche.py | 38 ++++++++++++++++++++++++++++++++++++++
- backend/app/schemas/suche.py | 7 +++++++
- backend/tests/test_suche.py | 32 ++++++++++++++++++++++++++++++++
- frontend/src/components/CommandPalette.tsx | 59 +++++++++++++++++++++++++++++++++++++++++------------------
---
## 2026-09-08 01:03 01:06 (3m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- backend/alembic/versions/0024_dokument_hash.py | 30 ++++++++++++++++++++++++++++++
- backend/app/api/v1/endpoints/dokument.py | 10 ++++++++++
- backend/app/models/dokument.py | 6 ++++++
- backend/app/services/dokument.py | 22 ++++++++++++++++++++++
- backend/tests/test_dokument.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++
- frontend/src/api/client.ts | 36 +++++++++++++++++++++++++++---------
- frontend/src/components/DokumentePanel.tsx | 11 ++++++++---
---
## 2026-09-08 01:09 01:10 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/src/pages/ObjektListPage.tsx | 131 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
- frontend/src/styles/global.css | 26 +++++++++++++++++++++++++
---
## 2026-09-08 01:14 01:16 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/src/hooks/useIsDesktop.ts | 20 ++++++++++++++++++++
- frontend/src/pages/ObjektListPage.tsx | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
- frontend/src/styles/global.css | 18 ++----------------
---
## 2026-09-08 10:13 10:15 (1m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- backend/tests/test_stammdaten.py | 17 +++++++++++------
---
## 2026-09-08 10:19 10:25 (5m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- frontend/package-lock.json | 64 ++++++++++++++++++++++++++++++++++++++++------------------------
- frontend/package.json | 2 +-
---
## 2026-09-08 10:49 11:02 (12m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- arbeitskacheln/00_index.md | 2 +-
---
## 2026-09-08 22:01 22:04 (3m)
**Beschreibung:** Claude Code Session
**Projekt:** asb-material
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- arbeitskacheln/00_index.md | 2 +-
- backend/alembic/versions/0032_nummernschema.py | 31 +++++++++++++++++
- backend/app/api/v1/endpoints/objekte.py | 29 +++++++++++++---
- backend/app/api/v1/endpoints/stammdaten.py | 52 +++++++++++++++++++++++++++-
- backend/app/models/stammdaten.py | 20 ++++++++++-
- backend/app/schemas/stammdaten.py | 16 +++++++++
- backend/app/services/objekte.py | 10 +++---
- backend/tests/test_objekte.py | 65 +++++++++++++++++++++++++++++++++++
- frontend/src/api/types.ts | 7 ++++
- frontend/src/pages/AdminPage.tsx | 8 +++--
- frontend/src/pages/admin/ObjektAnlegenFormular.tsx | 23 +++++++++++--
- frontend/src/pages/admin/ObjekttypSection.tsx | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
---
+8
View File
@@ -80,6 +80,14 @@ export interface FaelligeWartung {
faellig_bei_betriebsstunden: string | null;
}
export interface WartungsauftragTeil {
id: string;
auftrag_id: string;
bezeichnung: string;
menge: string;
kosten: string;
}
export interface Wartungsauftrag {
id: string;
objekt_id: number;
+110 -1
View File
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import { apiRequest } from "../api/client";
import { useAuth } from "../auth/AuthContext";
import type { FaelligeWartung, Wartungsauftrag } from "../api/types";
import type { FaelligeWartung, Wartungsauftrag, WartungsauftragTeil } from "../api/types";
import { DokumentePanel } from "./DokumentePanel";
interface Props {
@@ -27,6 +27,11 @@ export function WartungSection({ objektId, onFehler }: Props) {
const [faellig, setFaellig] = useState<FaelligeWartung[]>([]);
const [auftraege, setAuftraege] = useState<Wartungsauftrag[]>([]);
const [dokumenteOffenId, setDokumenteOffenId] = useState<string | null>(null);
const [teileOffenId, setTeileOffenId] = useState<string | null>(null);
const [teile, setTeile] = useState<WartungsauftragTeil[]>([]);
const [teilBezeichnung, setTeilBezeichnung] = useState("");
const [teilMenge, setTeilMenge] = useState("1");
const [teilKosten, setTeilKosten] = useState("");
const [neuArt, setNeuArt] = useState("");
const [neuExtern, setNeuExtern] = useState(false);
@@ -96,6 +101,52 @@ export function WartungSection({ objektId, onFehler }: Props) {
}
}
async function teileLaden(auftragId: string) {
try {
const t = await apiRequest<WartungsauftragTeil[]>(`/wartungsauftraege/${auftragId}/teile`);
setTeile(t);
} catch {
onFehler("Ersatzteile konnten nicht geladen werden.");
}
}
async function teileUmschalten(auftragId: string) {
if (teileOffenId === auftragId) {
setTeileOffenId(null);
return;
}
setTeileOffenId(auftragId);
await teileLaden(auftragId);
}
async function teilAnlegen(auftragId: string) {
if (!teilBezeichnung || !teilKosten) {
onFehler("Bezeichnung und Kosten angeben.");
return;
}
try {
await apiRequest(`/wartungsauftraege/${auftragId}/teile`, {
method: "POST",
body: { bezeichnung: teilBezeichnung, menge: teilMenge, kosten: teilKosten },
});
setTeilBezeichnung("");
setTeilMenge("1");
setTeilKosten("");
await teileLaden(auftragId);
} catch {
onFehler("Ersatzteil konnte nicht angelegt werden.");
}
}
async function teilLoeschen(auftragId: string, teilId: string) {
try {
await apiRequest(`/wartungsauftraege/${auftragId}/teile/${teilId}`, { method: "DELETE" });
await teileLaden(auftragId);
} catch {
onFehler("Ersatzteil konnte nicht gelöscht werden.");
}
}
const offeneAuftraege = auftraege.filter((a) => a.status !== "erledigt");
const erledigteAuftraege = auftraege.filter((a) => a.status === "erledigt");
const faelligeOhneOffenenAuftrag = faellig.filter((f) => f.faellig);
@@ -176,12 +227,70 @@ export function WartungSection({ objektId, onFehler }: Props) {
>
{dokumenteOffenId === a.id ? "Dokumente schließen" : "Dokumente/Rechnung"}
</button>
<button className="btn btn-secondary" onClick={() => teileUmschalten(a.id)}>
{teileOffenId === a.id ? "Ersatzteile schließen" : "Ersatzteile/Kosten"}
</button>
</div>
{dokumenteOffenId === a.id && (
<div style={{ marginTop: "0.5rem" }}>
<DokumentePanel entitaetTyp="wartungsauftrag" entitaetId={a.id} onFehler={onFehler} />
</div>
)}
{teileOffenId === a.id && (
<div style={{ marginTop: "0.5rem" }}>
{teile.length === 0 ? (
<p className="text-muted" style={{ fontSize: "0.85rem" }}>Keine Ersatzteile erfasst.</p>
) : (
<ul className="card-list">
{teile.map((t) => (
<li key={t.id} className="row-between" style={{ padding: "0.2rem 0" }}>
<span>
{t.bezeichnung} × {t.menge} {t.kosten}
</span>
{istVerantwortlich && (
<button className="btn btn-secondary" onClick={() => teilLoeschen(a.id, t.id)}>
Löschen
</button>
)}
</li>
))}
</ul>
)}
{teile.length > 0 && (
<div className="text-muted" style={{ fontSize: "0.85rem", marginTop: "0.2rem" }}>
Summe: {teile.reduce((s, t) => s + Number(t.menge) * Number(t.kosten), 0).toFixed(2)}
</div>
)}
{istVerantwortlich && (
<div className="row" style={{ gap: "0.3rem", marginTop: "0.4rem", flexWrap: "wrap" }}>
<input
className="input"
placeholder="Bezeichnung"
value={teilBezeichnung}
onChange={(e) => setTeilBezeichnung(e.target.value)}
style={{ maxWidth: "10rem" }}
/>
<input
className="input"
placeholder="Menge"
value={teilMenge}
onChange={(e) => setTeilMenge(e.target.value)}
style={{ maxWidth: "5rem" }}
/>
<input
className="input"
placeholder="Kosten (€)"
value={teilKosten}
onChange={(e) => setTeilKosten(e.target.value)}
style={{ maxWidth: "6rem" }}
/>
<button className="btn btn-primary" onClick={() => teilAnlegen(a.id)}>
Hinzufügen
</button>
</div>
)}
</div>
)}
</li>
))}
</ul>