feat(identity): IDENT-009 QR-/Code-Neuvergabe mit Historie
CI / backend-tests (push) Successful in 2m20s
CI / frontend-build (push) Successful in 27s

POST /objekte/{id}/code-neu-vergeben und /objektpositionen/{id}/code-neu-
vergeben (admin-only) - z.B. bei beschädigtem/verlorenem Etikett. Alter
Code bleibt über die bestehende Audit-Infrastruktur (Historie-Tabelle,
Prompt 13) nachvollziehbar statt eine eigene Versions-Tabelle zu brauchen:
GET /historie?entitaet_typ=objekt&entitaet_id=... zeigt alter_wert/
neuer_wert des Codes. Duplikat-Prüfung explizit vorab (Projekt-Konvention,
siehe geraet_instanz.py::erstelle).

Frontend: "Code neu vergeben"-Aktion in der Objektakte (nur Admin), inline
statt window.prompt (Accessibility-Vorgabe aus dieser Session).

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 18:13:53 +02:00
co-authored by Claude Sonnet 5
parent 08ce8efe55
commit b7d9581b90
6 changed files with 262 additions and 2 deletions
@@ -17,5 +17,7 @@ export const EREIGNIS_LABEL: Record<string, string> = {
mindermenge_genehmigt: "Mindermenge genehmigt",
nachfuellung_erfasst: "Nachfüllung erfasst",
objekt_geaendert: "Objekt geändert",
objekt_code_neu_vergeben: "Objekt-Code neu vergeben",
objektposition_code_neu_vergeben: "Geräte-Code neu vergeben",
qualifikation_erfasst: "Qualifikation erfasst",
};
+53 -1
View File
@@ -25,10 +25,12 @@ const MANGEL_PRIORITAET_LABEL: Record<string, string> = {
export function AktePage() {
const { objektId } = useParams<{ objektId: string }>();
const { istVerantwortlich } = useAuth();
const { istVerantwortlich, istAdmin } = useAuth();
const [akte, setAkte] = useState<Akte | null>(null);
const [fehler, setFehler] = useState<string | null>(null);
const [laedt, setLaedt] = useState(true);
const [codeNeuVergebenOffen, setCodeNeuVergebenOffen] = useState(false);
const [neuerCode, setNeuerCode] = useState("");
function laden() {
if (!objektId) return;
@@ -50,6 +52,21 @@ export function AktePage() {
}
}
async function codeNeuVergeben() {
if (!objektId || !neuerCode.trim()) return;
try {
await apiRequest(`/objekte/${objektId}/code-neu-vergeben`, {
method: "POST",
body: { neuer_code: neuerCode.trim(), begruendung: "Etikett neu ausgestellt" },
});
setCodeNeuVergebenOffen(false);
setNeuerCode("");
laden();
} catch {
setFehler("Code konnte nicht neu vergeben werden (evtl. bereits vergeben).");
}
}
async function pruefungDurchgefuehrt(geraetId: string) {
try {
await apiRequest(`/geraete/${geraetId}`, {
@@ -110,6 +127,41 @@ export function AktePage() {
<div className="akte-stamm card">
<h3 style={{ marginTop: 0 }}>Stammdaten</h3>
<div className="stack">
{istAdmin && (
<div>
{!codeNeuVergebenOffen ? (
<button
className="btn btn-secondary"
onClick={() => {
setNeuerCode(objekt.code);
setCodeNeuVergebenOffen(true);
}}
title="Neuen Code vergeben, z.B. bei beschädigtem Etikett - der alte Code bleibt in der Historie sichtbar"
>
Code neu vergeben
</button>
) : (
<div className="row" style={{ gap: "0.4rem", alignItems: "center", flexWrap: "wrap" }}>
<label htmlFor="neuer-code" className="sr-only">
Neuer Code
</label>
<input
id="neuer-code"
className="input"
style={{ maxWidth: "12rem" }}
value={neuerCode}
onChange={(e) => setNeuerCode(e.target.value)}
/>
<button className="btn btn-primary" onClick={codeNeuVergeben}>
Speichern
</button>
<button className="btn btn-secondary" onClick={() => setCodeNeuVergebenOffen(false)}>
Abbrechen
</button>
</div>
)}
</div>
)}
<div>Standort: {akte.standort_name}</div>
<div>
Letzte Kontrolle:{" "}