Files
Vorrania/web/src/components/SplitLotDialog.jsx
Scarriffle 231d86895f Charge aufteilen: Teilmenge mit gleichem MHD an anderen Lagerort umlagern
Bisher liess sich nur die ganze Charge umlagern. Neu: POST /lots/{id}/split zweigt
eine Teilmenge (Basiseinheiten) als neue Charge mit gleichem MHD an einen anderen
Lagerort ab; der Rest bleibt. Reine Umbuchung ohne Bewegungseintrag, Gesamtbestand
unveraendert. Schutz gegen zu grosse Menge und gleichen Zielort.

Web: gemeinsamer SplitLotDialog (Menge in Artikeleinheit + Zielort, zeigt Rest),
Aufteilen-Knopf auf der Chargen-Seite und in der Chargen-Tabelle der Artikelseite.
Neues Split-Icon.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 08:43:16 +02:00

84 lines
3.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import { api } from "../api";
import Icon from "./Icon";
import { fmt } from "../units";
import { locationOptions, locationPathById } from "../locationPath";
/**
* Charge aufteilen: einen Teil der Menge (in Artikeleinheiten Packungen/Stück/g)
* mit gleichem MHD an einen anderen Lagerort verschieben. Die Restmenge bleibt in
* der ursprünglichen Charge; der Gesamtbestand ändert sich nicht.
*
* `factor` Basiseinheiten je Artikeleinheit (z.B. 415 ml pro Packung).
* `labelFor` (menge) => Einheitentext, mengenabhängig ("1 Packung"/"3 Packungen").
*/
export default function SplitLotDialog({ lot, factor, labelFor, locations, onClose, onDone, onError }) {
const total = lot.quantity / factor; // Gesamtmenge in Artikeleinheiten
const [menge, setMenge] = useState("");
const [locId, setLocId] = useState("");
const [busy, setBusy] = useState(false);
const num = Number(String(menge).trim().replace(",", "."));
const gueltig = menge !== "" && !Number.isNaN(num) && num > 0 && num < total;
const rest = gueltig ? total - num : null;
const zielGleich = gueltig && (locId || null) === (lot.location_id || null);
async function submit() {
if (!gueltig || zielGleich || busy) return;
setBusy(true);
try {
await api.splitLot(lot.id, { quantity: num * factor, location_id: locId || null });
onClose();
await onDone();
} catch (err) {
onError?.(err.message);
} finally {
setBusy(false);
}
}
return (
<div className="modal-backdrop" onClick={(e) => e.target === e.currentTarget && onClose()}>
<div className="modal" role="dialog" aria-modal="true">
<div className="modal-head"><Icon name="split" size={18} /><h2>Charge aufteilen</h2></div>
<p className="modal-body mt-0">
Aktuell {fmt(total)} {labelFor(total)}
{lot.location_id ? ` in ${locationPathById(lot.location_id, locations) || "?"}` : " ohne Lagerort"}.
Ein Teil davon wird mit gleichem MHD an einen anderen Lagerort verschoben.
</p>
<div className="modal-body" style={{ display: "grid", gap: "var(--sp-3)" }}>
<label>
Menge abteilen (in {labelFor(2)})
{/* eslint-disable-next-line jsx-a11y/no-autofocus */}
<input type="number" step="any" min="0" max={total} value={menge} autoFocus
onChange={(e) => setMenge(e.target.value)} placeholder={`max. ${fmt(total)}`} />
</label>
<label>
nach Lagerort
<select value={locId} onChange={(e) => setLocId(e.target.value)}>
<option value=""> ohne Lagerort </option>
{locationOptions(locations).map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
</select>
</label>
{rest != null && !zielGleich && (
<p className="muted small mt-0">
Danach: {fmt(num)} {labelFor(num)} am neuen Ort, {fmt(rest)} {labelFor(rest)} bleiben.
</p>
)}
{zielGleich && (
<p className="small mt-0" style={{ color: "var(--danger)" }}>
Bitte einen anderen Lagerort als den aktuellen wählen.
</p>
)}
</div>
<div className="btn-pair">
<button type="button" className="btn" onClick={onClose}>Abbrechen</button>
<button type="button" className="btn primary" disabled={!gueltig || zielGleich || busy} onClick={submit}>
<Icon name="split" size={16} />Aufteilen
</button>
</div>
</div>
</div>
);
}