diff --git a/web/src/api.js b/web/src/api.js index 0722401..aa63329 100644 --- a/web/src/api.js +++ b/web/src/api.js @@ -162,6 +162,15 @@ export const api = { updateItem: (id, body) => request(`/items/${id}`, { method: "PATCH", body }), removeItem: (id, body) => request(`/items/${id}/remove`, { method: "POST", body }), deleteItem: (id) => request(`/items/${id}`, { method: "DELETE" }), + // Belege je Einzelstück (Rechnung/Garantieschein). Der Upload liefert einen + // vorgeschlagenen "Garantie bis"-Wert zurück (nur aus PDFs). + uploadItemDocument: (itemId, file) => { + const fd = new FormData(); + fd.append("file", file); + return request(`/items/${itemId}/documents`, { method: "POST", formData: fd }); + }, + deleteItemDocument: (itemId, docId) => + request(`/items/${itemId}/documents/${docId}`, { method: "DELETE" }), // Bestand checkIn: (body) => request("/stock/checkin", { method: "POST", body }), diff --git a/web/src/components/Einzelstuecke.jsx b/web/src/components/Einzelstuecke.jsx index 722ead9..3cb5825 100644 --- a/web/src/components/Einzelstuecke.jsx +++ b/web/src/components/Einzelstuecke.jsx @@ -1,6 +1,6 @@ -import { useEffect, useState } from "react"; +import { Fragment, useEffect, useState } from "react"; import QRCode from "qrcode"; -import { api } from "../api"; +import { api, authorizedObjectUrl } from "../api"; import { useConfirm } from "../confirm"; import { useToast } from "../toast"; import Icon from "./Icon"; @@ -37,6 +37,8 @@ export default function Einzelstuecke({ product, locations, shops, isAdmin, onCh const [items, setItems] = useState([]); const [addForm, setAddForm] = useState(EMPTY_ADD); const [remove, setRemove] = useState(null); // { item, reason, note } | null + const [expanded, setExpanded] = useState(null); // Item-ID mit offenem Detail (Preis/Belege) + const [suggestion, setSuggestion] = useState(null); // { itemId, date } aus PDF-Analyse const origin = window.location.origin; async function load() { @@ -68,6 +70,44 @@ export default function Einzelstuecke({ product, locations, shops, isAdmin, onCh } catch (err) { onError(err.message); } } + // Kaufpreis in Hauptwährungseinheit eingeben, als Rappen/Cent speichern. + function savePrice(item, value) { + const roh = value.trim().replace(",", "."); + const cents = roh === "" ? null : Math.round(parseFloat(roh) * 100); + if (cents !== null && Number.isNaN(cents)) return; + if (cents === (item.price_cents ?? null)) return; + patchItem(item, { price_cents: cents, currency: cents == null ? null : (item.currency || "CHF") }); + } + + async function uploadDoc(item, file) { + try { + const res = await api.uploadItemDocument(item.id, file); + await load(); + if (res && (res.suggested_warranty_until || res.suggested_price_cents != null)) { + setSuggestion({ + itemId: item.id, + date: res.suggested_warranty_until || null, + priceCents: res.suggested_price_cents ?? null, + }); + } + toast("Beleg hochgeladen."); + } catch (err) { onError(err.message); } + } + + async function deleteDoc(item, doc) { + try { + await api.deleteItemDocument(item.id, doc.id); + await load(); + } catch (err) { onError(err.message); } + } + + async function viewDoc(item, doc) { + try { + const url = await authorizedObjectUrl(`/items/${item.id}/documents/${doc.id}`); + if (url) window.open(url, "_blank", "noopener"); + } catch (err) { onError(err.message); } + } + async function patchItem(item, body) { try { await api.updateItem(item.id, body); @@ -144,7 +184,8 @@ export default function Einzelstuecke({ product, locations, shops, isAdmin, onCh
{items.map((it) => ( -