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>
This commit is contained in:
Scarriffle
2026-07-30 08:43:16 +02:00
parent 8cf7cef6e1
commit 231d86895f
7 changed files with 184 additions and 0 deletions

View File

@@ -4,6 +4,7 @@ import { api } from "../api";
import { useAuth } from "../auth";
import Icon from "../components/Icon";
import DataTable from "../components/DataTable";
import SplitLotDialog from "../components/SplitLotDialog";
import { ProduktThumb } from "../components/ProduktBild";
import { useSettings } from "../settings";
import { locationOptions, locationPathById } from "../locationPath";
@@ -34,6 +35,7 @@ export default function Charges() {
const [bulkLoc, setBulkLoc] = useState("");
const [selected, setSelected] = useState(() => new Set());
const [visible, setVisible] = useState([]); // aktuell gefiltert sichtbare Chargen
const [splitting, setSplitting] = useState(null); // Charge, die gerade aufgeteilt wird
async function load() {
setLoading(true);
@@ -130,6 +132,11 @@ export default function Charges() {
{ key: "created", header: "Eingelagert", width: 130,
sortValue: (l) => new Date(l.created_at).getTime(),
render: (l) => <span className="muted">{new Date(l.created_at).toLocaleDateString("de-DE")}</span> },
{ key: "split", header: "", label: "Aufteilen", fixed: true, width: 118, align: "num",
render: (l) => (isAdmin ? (
<button type="button" className="btn sm ghost" title="Charge aufteilen und Teil umlagern"
onClick={() => setSplitting(l)}><Icon name="split" size={15} />Aufteilen</button>
) : null) },
{ key: "details", header: "", label: "Artikel", fixed: true, width: 84, align: "num",
render: (l) => <Link to={`/products/${l.product_id}`}>Artikel</Link> },
];
@@ -192,6 +199,20 @@ export default function Charges() {
}}
getRowKey={(l) => l.id} empty="Keine Chargen im Bestand." />
</div>
{splitting && (
<SplitLotDialog
lot={splitting}
factor={artFactor(splitting)}
labelFor={(q) => (splitting.package_size && splitting.package_size > 0
? gebinde(q, splitting.package_label)
: (splitting.unit_name || unitShort(splitting.base_unit)))}
locations={locations}
onClose={() => setSplitting(null)}
onDone={load}
onError={setError}
/>
)}
</div>
);
}