feat(wartung): MAINT-005 Ersatzteile/Kosten je Wartungsauftrag
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:
@@ -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 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
|
||||
|
||||
---
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user