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>
84 lines
3.5 KiB
JavaScript
84 lines
3.5 KiB
JavaScript
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>
|
||
);
|
||
}
|