fix(ci): CI-Testdatenbank-Encoding + 3 echte Session-/Logikfehler behoben
CI / backend-tests (push) Successful in 1m54s
CI / frontend-build (push) Successful in 17s

Root Cause der "CI rot"-Meldung gefunden und behoben:
- CI legte Testdatenbank ohne explizites ENCODING an, erbte SQL_ASCII vom
  Runner-Postgres-Template. Umlaute in JSONB (Historie-Einträge) brachen mit
  UntranslatableCharacterError - betraf auch die Produktions-DB (separat
  gemeldet, nicht Teil dieses Commits). CI legt jetzt explizit UTF8 an.

Drei echte Bugs beim Verifizieren gegen eine isolierte Testdatenbank gefunden:
- objekte.py PATCH /objekte/{id}: Selbstbezug-Check verließ sich auf den
  DB-CHECK-Constraint statt vorab zu prüfen - ein Flush-Fehlschlag hinterlässt
  die Session im Zustand DEACTIVE, jeder folgende Request in derselben Session
  crasht mit PendingRollbackError (401 statt 404 im Test). Jetzt expliziter
  Vorab-Check.
- geraet_instanz.py: gleiches Muster bei doppelter Seriennummer - jetzt
  expliziter Vorab-Check statt UNIQUE-Constraint-Exception.
- test_lagerbewegung.py: Testbug, las objekt.standort_id NACH dem POST (durch
  geteilte Session bereits auf den neuen Wert mutiert) statt vorher.

Nebenbei (Auftrag Priorität 4): "Mindermenge genehmigen"-Button direkt im
Nachfüll-Dialog der Kontroll-Erfassung (nur für materialverantwortlicher/
leitungsverantwortlicher/administration), nutzt den bereits bestehenden
POST /fehlbestaende/{id}/mindermenge Endpunkt.

Priorität 2 (Fahrzeug-Feldnamen-Mismatch) und Priorität 3 (nur Zugfahrzeuge
wählbar) waren bereits in früheren Commits erledigt (38b8ce1, 54fc296) -
Auftragsbeschreibung war auf altem Stand.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CVgbozhYmuEhiEJHffRXCV
This commit is contained in:
2026-09-05 17:57:14 +02:00
co-authored by Claude Sonnet 5
parent c7dd47f5bf
commit fca9ee8fd8
8 changed files with 104 additions and 7 deletions
+4
View File
@@ -25,6 +25,7 @@ export function KontrollPage() {
abschliessen,
abbrechen,
jetztNachfuellen,
jetztMindermengeGenehmigen,
nachfuellDialogSchliessen,
} = useKontrolle(objektId);
@@ -116,6 +117,9 @@ export function KontrollPage() {
Number(zustand.position.sollmenge_effektiv) - Number(zustand.eingabe)
)}
onNachfuellen={(menge) => jetztNachfuellen(nachfuellDialog.fehlbestandId, menge)}
onMindermengeGenehmigen={(begruendung) =>
jetztMindermengeGenehmigen(nachfuellDialog.fehlbestandId, begruendung)
}
onSchliessen={nachfuellDialogSchliessen}
/>
)}
@@ -1,15 +1,29 @@
import { useState } from "react";
import { useAuth } from "../../auth/AuthContext";
interface Props {
materialName: string;
vorschlagMenge: string;
onNachfuellen: (menge: string) => void;
onMindermengeGenehmigen: (begruendung: string) => void;
onSchliessen: () => void;
}
/** Karte 07: nach einer Abweichung direkt "jetzt nachfüllen?" statt Umweg über Dashboard. */
export function NachfuellDialog({ materialName, vorschlagMenge, onNachfuellen, onSchliessen }: Props) {
/** Karte 07: nach einer Abweichung direkt "jetzt nachfüllen?" statt Umweg über Dashboard.
* Mindermenge genehmigen (Prompt 04) darf hier direkt inline erfolgen, nicht nur über
* die separate Fehlbestandsliste - aber wie dort NIEMALS durch Mitarbeiter. */
export function NachfuellDialog({
materialName,
vorschlagMenge,
onNachfuellen,
onMindermengeGenehmigen,
onSchliessen,
}: Props) {
const { istVerantwortlich } = useAuth();
const [menge, setMenge] = useState(vorschlagMenge);
const [mindermengeOffen, setMindermengeOffen] = useState(false);
const [begruendung, setBegruendung] = useState("");
return (
<div className="card" style={{ borderColor: "var(--color-warning, #b45309)", marginTop: "0.6rem" }}>
@@ -27,10 +41,36 @@ export function NachfuellDialog({ materialName, vorschlagMenge, onNachfuellen, o
<button className="btn btn-primary" onClick={() => onNachfuellen(menge)}>
Ja, nachfüllen
</button>
{istVerantwortlich && !mindermengeOffen && (
<button className="btn btn-secondary" onClick={() => setMindermengeOffen(true)}>
Mindermenge genehmigen
</button>
)}
<button className="btn btn-secondary" onClick={onSchliessen}>
Nein
</button>
</div>
{mindermengeOffen && (
<div className="row" style={{ marginTop: "0.5rem", flexWrap: "wrap" }}>
<input
className="input"
style={{ flex: 1 }}
placeholder="Begründung (Pflicht)"
value={begruendung}
onChange={(e) => setBegruendung(e.target.value)}
/>
<button
className="btn btn-primary"
disabled={!begruendung.trim()}
onClick={() => onMindermengeGenehmigen(begruendung.trim())}
>
Genehmigen
</button>
<button className="btn btn-secondary" onClick={() => setMindermengeOffen(false)}>
Abbrechen
</button>
</div>
)}
</div>
);
}
@@ -152,6 +152,17 @@ export function useKontrolle(objektId: string | undefined) {
setNachfuellDialog(null);
}
/** Prompt 04: Mindermenge direkt aus der Kontroll-Erfassung genehmigen, ohne Umweg
* über die separate Fehlbestandsliste. Backend erzwingt Rollen-Dependency ohnehin
* (nie durch Mitarbeiter), Frontend blendet den Button zusätzlich aus. */
async function jetztMindermengeGenehmigen(fehlbestandId: string, begruendung: string) {
await apiRequest(`/fehlbestaende/${fehlbestandId}/mindermenge`, {
method: "POST",
body: { begruendung },
});
setNachfuellDialog(null);
}
function nachfuellDialogSchliessen() {
setNachfuellDialog(null);
}
@@ -208,6 +219,7 @@ export function useKontrolle(objektId: string | undefined) {
abschliessen,
abbrechen,
jetztNachfuellen,
jetztMindermengeGenehmigen,
nachfuellDialogSchliessen,
};
}