Ablauf/Charge/SN gehören zur Kontroll-Erfassung, nicht nur zur Admin-Pflege
Nutzer-Vorgabe: "Ablauf/Charge/SN gehört mit zu den Kontrollen wie auch die
Menge". PUT .../positionen/{material_id} akzeptiert jetzt optional
ablaufdatum/chargennummer/seriennummer und schreibt sie auf die
Objektposition (Vier-Kernbegriffe bleiben unberührt: nur diese drei Felder,
niemals istmenge - das bleibt exklusiv Nachfüllung vorbehalten). Offline-
Queue und PositionCard erweitert: Felder erscheinen nur passend zum
Materialtyp (ablauf_charge -> Datum+Charge, geraet_sn -> SN). Admin-Panel
(ObjektPositionenPanel) bleibt zusätzlich für nachträgliche Korrekturen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L85hmKbvX7Cqkq47KnQhFt
This commit is contained in:
@@ -11,7 +11,13 @@ const store = createStore("mabea-offline-queue", "kontrollpositionen");
|
||||
|
||||
export type QueueStatus = "wartet" | "wird_uebertragen" | "fehler";
|
||||
|
||||
export interface QueueItem {
|
||||
export interface ZusatzErfassung {
|
||||
ablaufdatum?: string | null;
|
||||
chargennummer?: string | null;
|
||||
seriennummer?: string | null;
|
||||
}
|
||||
|
||||
export interface QueueItem extends ZusatzErfassung {
|
||||
key: string; // `${kontrolleId}:${materialId}`
|
||||
kontrolleId: string;
|
||||
materialId: number;
|
||||
@@ -25,13 +31,19 @@ function keyFor(kontrolleId: string, materialId: number): string {
|
||||
return `${kontrolleId}:${materialId}`;
|
||||
}
|
||||
|
||||
export async function enqueue(kontrolleId: string, materialId: number, istmenge: string): Promise<void> {
|
||||
export async function enqueue(
|
||||
kontrolleId: string,
|
||||
materialId: number,
|
||||
istmenge: string,
|
||||
zusatz: ZusatzErfassung = {}
|
||||
): Promise<void> {
|
||||
const key = keyFor(kontrolleId, materialId);
|
||||
const item: QueueItem = {
|
||||
key,
|
||||
kontrolleId,
|
||||
materialId,
|
||||
istmenge,
|
||||
...zusatz,
|
||||
status: "wartet",
|
||||
fehlerText: null,
|
||||
fehlbestandId: null,
|
||||
@@ -63,7 +75,15 @@ async function sendeEintrag(item: QueueItem): Promise<void> {
|
||||
try {
|
||||
const ergebnis = await apiRequest<Kontrollposition>(
|
||||
`/kontrollen/${item.kontrolleId}/positionen/${item.materialId}`,
|
||||
{ method: "PUT", body: { istmenge: item.istmenge } }
|
||||
{
|
||||
method: "PUT",
|
||||
body: {
|
||||
istmenge: item.istmenge,
|
||||
ablaufdatum: item.ablaufdatum ?? null,
|
||||
chargennummer: item.chargennummer ?? null,
|
||||
seriennummer: item.seriennummer ?? null,
|
||||
},
|
||||
}
|
||||
);
|
||||
// Erfolgreich übertragen (idempotent - ein erneutes Senden desselben Werts
|
||||
// durch die Queue vor einem Absturz führt zu keinem doppelten Fehlbestand,
|
||||
|
||||
@@ -3,12 +3,19 @@ import type { PositionZustand } from "./types";
|
||||
|
||||
interface Props {
|
||||
zustand: PositionZustand;
|
||||
onEingabeAendern: (materialId: number, wert: string) => void;
|
||||
onSenden: (materialId: number, istmenge: string) => void;
|
||||
onErneutVersuchen: (materialId: number, istmenge: string) => void;
|
||||
onEingabeAendern: (materialId: number, feld: keyof PositionZustand, wert: string) => void;
|
||||
onSenden: (materialId: number, zustand: PositionZustand) => void;
|
||||
onErneutVersuchen: (materialId: number, zustand: PositionZustand) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nutzer-Vorgabe: Ablauf/Charge/SN gehören zur Kontroll-Erfassung dazu, wie
|
||||
* die Menge - kein separater Admin-Schritt. Felder erscheinen nur, wenn der
|
||||
* Materialtyp sie verlangt (ablauf_charge -> Ablauf+Charge, geraet_sn -> SN).
|
||||
*/
|
||||
export function PositionCard({ zustand, onEingabeAendern, onSenden, onErneutVersuchen }: Props) {
|
||||
const materialtyp = zustand.material?.materialtyp;
|
||||
|
||||
return (
|
||||
<li className="card">
|
||||
<div className="card-title-row">
|
||||
@@ -23,17 +30,43 @@ export function PositionCard({ zustand, onEingabeAendern, onSenden, onErneutVers
|
||||
type="number"
|
||||
className="input"
|
||||
value={zustand.eingabe}
|
||||
onChange={(e) => onEingabeAendern(zustand.position.material_id, e.target.value)}
|
||||
onChange={(e) => onEingabeAendern(zustand.position.material_id, "eingabe", e.target.value)}
|
||||
style={{ width: "6rem" }}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
style={{ flex: 1 }}
|
||||
onClick={() => onSenden(zustand.position.material_id, zustand.eingabe)}
|
||||
onClick={() => onSenden(zustand.position.material_id, zustand)}
|
||||
>
|
||||
Bestätigen
|
||||
</button>
|
||||
</div>
|
||||
{materialtyp === "ablauf_charge" && (
|
||||
<div className="row" style={{ marginTop: "0.5rem" }}>
|
||||
<input
|
||||
type="date"
|
||||
className="input"
|
||||
value={zustand.ablaufdatum}
|
||||
onChange={(e) => onEingabeAendern(zustand.position.material_id, "ablaufdatum", e.target.value)}
|
||||
/>
|
||||
<input
|
||||
className="input"
|
||||
placeholder="Chargennummer"
|
||||
value={zustand.chargennummer}
|
||||
onChange={(e) => onEingabeAendern(zustand.position.material_id, "chargennummer", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{materialtyp === "geraet_sn" && (
|
||||
<div className="row" style={{ marginTop: "0.5rem" }}>
|
||||
<input
|
||||
className="input"
|
||||
placeholder="Seriennummer"
|
||||
value={zustand.seriennummer}
|
||||
onChange={(e) => onEingabeAendern(zustand.position.material_id, "seriennummer", e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{zustand.status === "fehler" && (
|
||||
<div style={{ marginTop: "0.6rem" }}>
|
||||
<p className="text-muted" style={{ color: "var(--color-danger)" }}>
|
||||
@@ -41,7 +74,7 @@ export function PositionCard({ zustand, onEingabeAendern, onSenden, onErneutVers
|
||||
</p>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
onClick={() => onErneutVersuchen(zustand.position.material_id, zustand.eingabe)}
|
||||
onClick={() => onErneutVersuchen(zustand.position.material_id, zustand)}
|
||||
>
|
||||
Erneut versuchen
|
||||
</button>
|
||||
|
||||
@@ -8,6 +8,9 @@ export interface PositionZustand {
|
||||
material: Material | undefined;
|
||||
status: PositionStatus;
|
||||
eingabe: string;
|
||||
ablaufdatum: string;
|
||||
chargennummer: string;
|
||||
seriennummer: string;
|
||||
fehlerText: string | null;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,9 @@ export function useKontrolle(objektId: string | undefined) {
|
||||
material: materialById.get(p.material_id),
|
||||
status: "unbestaetigt",
|
||||
eingabe: p.sollmenge_effektiv,
|
||||
ablaufdatum: p.ablaufdatum ?? "",
|
||||
chargennummer: p.chargennummer ?? "",
|
||||
seriennummer: p.seriennummer ?? "",
|
||||
fehlerText: null,
|
||||
}))
|
||||
);
|
||||
@@ -102,25 +105,29 @@ export function useKontrolle(objektId: string | undefined) {
|
||||
if (online) void synchronisiereQueue();
|
||||
}, [online]);
|
||||
|
||||
function eingabeAendern(materialId: number, wert: string) {
|
||||
function eingabeAendern(materialId: number, feld: keyof PositionZustand, wert: string) {
|
||||
setPositionen((bisherige) =>
|
||||
bisherige.map((z) => (z.position.material_id === materialId ? { ...z, eingabe: wert } : z))
|
||||
bisherige.map((z) => (z.position.material_id === materialId ? { ...z, [feld]: wert } : z))
|
||||
);
|
||||
}
|
||||
|
||||
async function positionSenden(materialId: number, istmenge: string) {
|
||||
async function positionSenden(materialId: number, zustand: PositionZustand) {
|
||||
if (!kontrolle) return;
|
||||
await enqueue(kontrolle.id, materialId, istmenge);
|
||||
await enqueue(kontrolle.id, materialId, zustand.eingabe, {
|
||||
ablaufdatum: zustand.ablaufdatum || null,
|
||||
chargennummer: zustand.chargennummer || null,
|
||||
seriennummer: zustand.seriennummer || null,
|
||||
});
|
||||
setPositionen((bisherige) =>
|
||||
bisherige.map((z) => (z.position.material_id === materialId ? { ...z, status: "wartet" } : z))
|
||||
);
|
||||
void synchronisiereQueue();
|
||||
}
|
||||
|
||||
async function fehlerErneutVersuchen(materialId: number, istmenge: string) {
|
||||
async function fehlerErneutVersuchen(materialId: number, zustand: PositionZustand) {
|
||||
if (!kontrolle) return;
|
||||
await entferneFehlerEintrag(kontrolle.id, materialId);
|
||||
await positionSenden(materialId, istmenge);
|
||||
await positionSenden(materialId, zustand);
|
||||
}
|
||||
|
||||
const alleUebertragen = useMemo(
|
||||
|
||||
Reference in New Issue
Block a user