Import/Export von Bestaenden (CSV + JSON), rein additiv
Export:
- GET /export/stock.csv - eine Zeile je Charge, Semikolon-getrennt mit BOM,
direkt in Excel/LibreOffice bearbeitbar; Mengen in der Artikeleinheit.
- GET /export/backup.json - vollstaendiges Backup (Einheiten, Gruppen,
Lagerorte, Produkte, Chargen), Referenzen ueber Namen statt IDs.
Import:
- POST /import/stock (Admin, Datei-Upload). Erkennt CSV oder JSON automatisch.
Arbeitet ausschliesslich additiv: unbekannte Produkte/Gruppen/Lagerorte/
Einheiten werden angelegt, Chargen ergaenzt - nichts wird geloescht.
Mengen koennen in Gebinde ("Glas") oder Einheiten ("Gramm") angegeben werden,
Datum als YYYY-MM-DD oder TT.MM.JJJJ. Fehlerhafte Zeilen werden uebersprungen
und im Ergebnis gemeldet; jeder Import wird als Bewegung protokolliert.
Frontend: neue Seite "Import / Export" (Verwaltung) mit Download-Buttons,
Datei-Upload, Ergebniszusammenfassung und einer Beschreibung der CSV-Spalten.
api.js kann jetzt Datei-Downloads mit Auth-Header und FormData-Uploads.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -20,13 +20,16 @@ export class ApiError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
async function request(path, { method = "GET", body, form } = {}) {
|
||||
async function request(path, { method = "GET", body, form, formData } = {}) {
|
||||
const headers = {};
|
||||
const token = getToken();
|
||||
if (token) headers["Authorization"] = `Bearer ${token}`;
|
||||
|
||||
let payload;
|
||||
if (form) {
|
||||
if (formData) {
|
||||
// Content-Type (inkl. boundary) setzt der Browser selbst.
|
||||
payload = formData;
|
||||
} else if (form) {
|
||||
headers["Content-Type"] = "application/x-www-form-urlencoded";
|
||||
payload = new URLSearchParams(form).toString();
|
||||
} else if (body !== undefined) {
|
||||
@@ -69,6 +72,26 @@ async function request(path, { method = "GET", body, form } = {}) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// Datei mit Auth-Header laden und im Browser als Download anbieten.
|
||||
export async function downloadFile(path, filename) {
|
||||
const token = getToken();
|
||||
const resp = await fetch(`${API_BASE}${path}`, {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw new ApiError(`Download fehlgeschlagen (${resp.status})`, resp.status);
|
||||
}
|
||||
const blob = await resp.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
export const api = {
|
||||
login: (username, password) =>
|
||||
request("/auth/login", { method: "POST", form: { username, password } }),
|
||||
@@ -107,6 +130,15 @@ export const api = {
|
||||
updateGroup: (id, body) => request(`/groups/${id}`, { method: "PATCH", body }),
|
||||
deleteGroup: (id) => request(`/groups/${id}`, { method: "DELETE" }),
|
||||
|
||||
// Export / Import
|
||||
exportCsv: () => downloadFile("/export/stock.csv", "bestand.csv"),
|
||||
exportJson: () => downloadFile("/export/backup.json", "project-good-backup.json"),
|
||||
importStock: (file) => {
|
||||
const fd = new FormData();
|
||||
fd.append("file", file);
|
||||
return request("/import/stock", { method: "POST", formData: fd });
|
||||
},
|
||||
|
||||
listUnits: () => request("/units"),
|
||||
createUnit: (body) => request("/units", { method: "POST", body }),
|
||||
deleteUnit: (id) => request(`/units/${id}`, { method: "DELETE" }),
|
||||
|
||||
Reference in New Issue
Block a user