import { useRef, useState } from "react"; import { api } from "../api"; import { useConfirm } from "../confirm"; import Icon from "../components/Icon"; export default function Transfer() { const confirm = useConfirm(); const fileRef = useRef(null); const [file, setFile] = useState(null); const [mode, setMode] = useState("add"); const [result, setResult] = useState(null); const [error, setError] = useState(null); const [busy, setBusy] = useState(false); const MODES = { add: "Nur hinzufügen – es wird nichts gelöscht.", replace_listed: "Bestand der Produkte ersetzen, die in der Datei vorkommen. Ideal für eine Inventur: exportieren, korrigieren, zurückspielen.", replace_all: "Alle Bestände vorher leeren und komplett neu aufbauen. Für die vollständige Wiederherstellung aus einem Backup.", }; async function download(kind) { setError(null); try { if (kind === "csv") await api.exportCsv(); else await api.exportJson(); } catch (err) { setError(err.message); } } async function doImport(e) { e.preventDefault(); if (!file) return; if (mode !== "add") { const warning = mode === "replace_all" ? "ALLE Bestände werden vorher gelöscht und aus der Datei neu aufgebaut. Fortfahren?" : "Für alle Produkte in der Datei werden die vorhandenen Chargen gelöscht und ersetzt. Fortfahren?"; if (!(await confirm({ title: "Vorhandene Bestände ersetzen?", message: warning, confirmLabel: "Ersetzen", danger: true, }))) return; } setError(null); setResult(null); setBusy(true); try { setResult(await api.importStock(file, mode)); setFile(null); if (fileRef.current) fileRef.current.value = ""; } catch (err) { setError(err.message); } finally { setBusy(false); } } return (

Import / Export

Bestände samt Chargen und MHD sichern oder einspielen
{error &&
{error}
}

Export

CSV enthält eine Zeile je Charge und lässt sich in Excel oder LibreOffice bearbeiten. JSON ist ein vollständiges Backup inklusive Einheiten, Gruppen und Lagerorten.

Import

CSV oder JSON auswählen. Unbekannte Produkte, Gruppen und Lagerorte werden immer angelegt – was mit vorhandenen Beständen passiert, bestimmt der Modus.

{MODES[mode]}

{result && (
Import abgeschlossen ({result.format?.toUpperCase()}):{" "} {result.products_created} Produkt(e) angelegt, {result.lots_added} Charge(n) ergänzt {result.products_cleared ? `, ${result.products_cleared} Produkt(e) vorher geleert` : ""} {result.units_created ? `, ${result.units_created} Einheit(en) angelegt` : ""}.
)} {result?.errors?.length > 0 && (
{result.errors.length} Zeile(n) übersprungen:
    {result.errors.slice(0, 10).map((e, i) =>
  • {e}
  • )}
{result.errors.length > 10 &&
… und {result.errors.length - 10} weitere.
}
)}

CSV-Aufbau

SpalteBedeutung
barcodeoptional; dient zum Wiedererkennen vorhandener Produkte
nameProduktname (Pflicht, wenn kein Barcode passt)
markeoptional
einheitProdukteinheit, z.B. Gramm, Kilogramm, Stück
packungsgroesseBasiseinheiten je Gebinde, z.B. 195
gebindeBezeichnung, z.B. Glas, Tüte, Flasche
gruppeoptional; wird bei Bedarf angelegt
mindestbestandoptional, in Basiseinheiten
mengeMenge dieser Charge
menge_einheitEinheit der Menge: Gebinde-Bezeichnung oder z.B. Gramm
mhdDatum, z.B. 2026-12-23 oder 23.12.2026 – leer erlaubt
lagerortoptional; wird bei Bedarf angelegt

Am einfachsten: einmal exportieren, die Datei als Vorlage nehmen und ergänzen. Trennzeichen ist ein Semikolon; Komma wird ebenfalls erkannt.

); }