@flowselections/floriday-voorraad 1.0.139 → 1.0.143
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/NieuwArtikelPage.js +22 -3
- package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockMutationHistoryCard.js +3 -2
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +2 -0
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.js +28 -60
- package/dist-lib/hooks/useEnsureVoorraadDefaults.d.ts +7 -0
- package/dist-lib/hooks/useEnsureVoorraadDefaults.d.ts.map +1 -0
- package/dist-lib/hooks/useEnsureVoorraadDefaults.js +25 -0
- package/dist-lib/hooks/useTradeItemStock.d.ts +4 -1
- package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemStock.js +31 -44
- package/dist-lib/integrations/supabase/types.d.ts +421 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/__tests__/release-140.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/release-140.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/release-140.test.js +42 -0
- package/dist-lib/lib/db-error-message.d.ts +7 -0
- package/dist-lib/lib/db-error-message.d.ts.map +1 -0
- package/dist-lib/lib/db-error-message.js +36 -0
- package/dist-lib/lib/engine-carrier.d.ts +15 -0
- package/dist-lib/lib/engine-carrier.d.ts.map +1 -0
- package/dist-lib/lib/engine-carrier.js +27 -0
- package/dist-lib/lib/simple-packing.d.ts +2 -4
- package/dist-lib/lib/simple-packing.d.ts.map +1 -1
- package/dist-lib/lib/simple-packing.js +5 -12
- package/dist-lib/lib/stock-save.d.ts +11 -0
- package/dist-lib/lib/stock-save.d.ts.map +1 -1
- package/dist-lib/lib/stock-save.js +17 -0
- package/package.json +4 -2
- package/sql/AGENTS.md +4 -0
- package/sql/floriday-voorraad-schema.sql +441 -82
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AA8BA,wBAAgB,gBAAgB,gCA4Y/B"}
|
|
@@ -19,6 +19,7 @@ import { useLoadCarriers } from "../../hooks/useLoadCarriers.js";
|
|
|
19
19
|
import { saveOwnPackings } from "../../lib/product-packings.js";
|
|
20
20
|
import { assertArticleCode } from "../../lib/article-code.js";
|
|
21
21
|
import { EMPTY_SIMPLE_PACKING, engineOordeelRegel, missingPackingFields, simplePackingToRow, } from "../../lib/simple-packing.js";
|
|
22
|
+
import { resolveEngineCarrier } from "../../lib/engine-carrier.js";
|
|
22
23
|
const STEPS = ["Naam en code", "Voorraad", "Prijs", "Belading"];
|
|
23
24
|
export function NieuwArtikelPage() {
|
|
24
25
|
const carriers = useLoadCarriers();
|
|
@@ -41,6 +42,22 @@ export function NieuwArtikelPage() {
|
|
|
41
42
|
const [fusts, setFusts] = useState([]);
|
|
42
43
|
const [oordeel, setOordeel] = useState(null);
|
|
43
44
|
const missingPacking = missingPackingFields(packing);
|
|
45
|
+
// Vertaling naar de Engine-code: alleen via de Logistiek Engine, geen eigen lijst.
|
|
46
|
+
const [engineCarrier, setEngineCarrier] = useState({ code: null, melding: null });
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
let off = false;
|
|
49
|
+
setEngineCarrier({ code: null, melding: null });
|
|
50
|
+
if (!packing.loadCarrier)
|
|
51
|
+
return;
|
|
52
|
+
void resolveEngineCarrier(supabase, packing.loadCarrier).then((r) => {
|
|
53
|
+
if (!off)
|
|
54
|
+
setEngineCarrier(r);
|
|
55
|
+
});
|
|
56
|
+
return () => {
|
|
57
|
+
off = true;
|
|
58
|
+
};
|
|
59
|
+
}, [packing.loadCarrier]);
|
|
60
|
+
const packingReady = missingPacking.length === 0 && !!engineCarrier.code;
|
|
44
61
|
const setPack = (k, v) => setPacking((p) => ({ ...p, [k]: v }));
|
|
45
62
|
useEffect(() => {
|
|
46
63
|
let off = false;
|
|
@@ -84,7 +101,7 @@ export function NieuwArtikelPage() {
|
|
|
84
101
|
: step === 1
|
|
85
102
|
? !canSaveStock || (Number(stock) >= 0 && stock !== "")
|
|
86
103
|
: step === 3
|
|
87
|
-
?
|
|
104
|
+
? packingReady
|
|
88
105
|
: true;
|
|
89
106
|
const save = async () => {
|
|
90
107
|
if (savingRef.current)
|
|
@@ -98,6 +115,8 @@ export function NieuwArtikelPage() {
|
|
|
98
115
|
throw new Error("Vul een geldige prijs in (groter dan 0)");
|
|
99
116
|
assertArticleCode(code, name, "manual");
|
|
100
117
|
const packingRow = simplePackingToRow(packing);
|
|
118
|
+
if (!engineCarrier.code)
|
|
119
|
+
throw new Error(engineCarrier.melding ?? "De ladingdrager wordt nog gecontroleerd door de Logistiek Engine.");
|
|
101
120
|
let id = insertedIdRef.current;
|
|
102
121
|
if (!id) {
|
|
103
122
|
const { data, error } = await supabase
|
|
@@ -139,7 +158,7 @@ export function NieuwArtikelPage() {
|
|
|
139
158
|
}
|
|
140
159
|
// Controle door de Engine (alleen lezen); uitkomst tonen, niet blokkeren.
|
|
141
160
|
const { data: oo, error: ooErr } = await supabase.rpc("logistics_product_oordeel_belading", {
|
|
142
|
-
p_regels: [engineOordeelRegel(id, packing)],
|
|
161
|
+
p_regels: [engineOordeelRegel(id, packing, engineCarrier.code)],
|
|
143
162
|
});
|
|
144
163
|
const first = Array.isArray(oo) ? oo[0] : null;
|
|
145
164
|
setOordeel(ooErr
|
|
@@ -167,5 +186,5 @@ export function NieuwArtikelPage() {
|
|
|
167
186
|
? "rounded-md border p-3 text-sm"
|
|
168
187
|
: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: [_jsxs("p", { className: "font-medium", children: ["Controle karindeling: ", oordeel.tekst] }), oordeel.regels.length > 0 && (_jsx("ul", { className: "list-disc pl-5", children: oordeel.regels.map((r) => (_jsx("li", { children: r }, r))) }))] })), hasFloriday ? (_jsxs(_Fragment, { children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Wil je het artikel ook op Floriday zetten? Daarvoor is nog nodig:" }), _jsxs("ul", { className: "list-disc pl-5 text-sm", children: [!hasPrice && _jsx("li", { children: "Prijs per stuk (excl. btw)" }), missing.map((m) => (_jsx("li", { children: m }, m)))] })] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "De voorraad is opgeslagen als eigen voorraad. Die gaat niet naar Floriday." })), _jsxs("div", { className: "flex flex-wrap gap-2", children: [hasFloriday && (_jsxs(Button, { onClick: () => navigate({ to: "/voorraad/$id", params: { id: createdId } }), children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), "Op Floriday zetten"] })), _jsx(Button, { variant: "outline", asChild: true, children: _jsx(Link, { to: "/voorraad", children: "Terug naar voorraad" }) })] })] }) }));
|
|
169
188
|
}
|
|
170
|
-
return (_jsxs("main", { className: "p-6 space-y-4 max-w-2xl", children: [_jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Artikel toevoegen" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Stap ", step + 1, " van ", STEPS.length, ": ", STEPS[step]] })] }), _jsx(FloridayConnectionNotice, { status: conn.status, environment: conn.selectedEnvironment, error: conn.error }), _jsxs(Card, { className: "p-5 space-y-4", children: [step === 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-name", children: "Naam" }), _jsx(Input, { id: "na-name", value: name, onChange: (e) => setName(e.target.value), autoFocus: true })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-code", children: "Artikelcode (optioneel)" }), _jsx(Input, { id: "na-code", value: code, onChange: (e) => setCode(e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Leeg laten: er wordt automatisch een code gemaakt." })] })] })), step === 1 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-stock", children: "Voorraad (stuks)" }), _jsx(Input, { id: "na-stock", type: "number", min: 0, value: canSaveStock ? stock : "", onChange: (e) => setStock(e.target.value), disabled: !canSaveStock, "aria-describedby": "na-stock-help", autoFocus: true }), !canSaveStock && (_jsx("p", { id: "na-stock-help", className: "text-xs text-muted-foreground", children: "Koppeling wordt gecontroleerd\u2026" })), canSaveStock && !hasFloriday && (_jsx("p", { id: "na-stock-help", className: "text-xs text-muted-foreground", children: LOCAL_STOCK_TEXT }))] })), step === 2 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-price", children: "Prijs per stuk (EUR, excl. btw)" }), _jsx(Input, { id: "na-price", inputMode: "decimal", value: price, onChange: (e) => setPrice(e.target.value), placeholder: "Bijv. 1,10", autoFocus: true }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Zonder prijs komt het artikel niet in Floriday (\"Prijs ontbreekt\")." })] })), step === 3 && (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Zonder volledige belading kan de karindeling dit artikel niet plannen. Alle vijf velden zijn verplicht." }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-fust", children: "Fust (VBN-code)" }), _jsxs("select", { id: "na-fust", className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: packing.vbnPackageCode, onChange: (e) => setPack("vbnPackageCode", e.target.value), children: [_jsx("option", { value: "", children: "Kies een fust\u2026" }), fusts.map((f) => (_jsxs("option", { value: String(f.vbn), children: [f.vbn, " \u2013 ", f.name] }, f.vbn)))] }), _jsx(Input, { "aria-label": "Andere VBN-fustcode", placeholder: "Of typ een VBN-fustcode, bijv. 412", inputMode: "numeric", value: packing.vbnPackageCode, onChange: (e) => setPack("vbnPackageCode", e.target.value) })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-ppc", children: "Planten per fust" }), _jsx(Input, { id: "na-ppc", inputMode: "numeric", value: packing.piecesPerPackage, onChange: (e) => setPack("piecesPerPackage", e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-cpl", children: "Fusten per laag" }), _jsx(Input, { id: "na-cpl", inputMode: "numeric", value: packing.packagesPerLayer, onChange: (e) => setPack("packagesPerLayer", e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-lpc", children: "Lagen per kar" }), _jsx(Input, { id: "na-lpc", inputMode: "numeric", value: packing.layersPerLoadCarrier, onChange: (e) => setPack("layersPerLoadCarrier", e.target.value) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-lc", children: "Ladingdrager" }), _jsxs("select", { id: "na-lc", className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: packing.loadCarrier, onChange: (e) => setPack("loadCarrier", e.target.value), children: [_jsx("option", { value: "", children: "Kies een ladingdrager\u2026" }), carriers.items.map((c) => (_jsx("option", { value: c.code, children: c.naam }, c.code)))] }), carriers.error && _jsx("p", { className: "text-xs text-destructive", children: carriers.error })] }), missingPacking.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Nog invullen: ", missingPacking.join(", "), "."] }))] })), saveError && (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: saveError })), _jsxs("div", { className: "flex flex-wrap justify-between gap-2", children: [_jsx(Button, { variant: "outline", disabled: step === 0 || saving, onClick: () => setStep((s) => s - 1), children: "Vorige" }), step < STEPS.length - 1 ? (_jsx(Button, { disabled: !canNext, onClick: () => setStep((s) => s + 1), children: "Volgende" })) : (_jsxs(Button, { disabled: saving || !name.trim() ||
|
|
189
|
+
return (_jsxs("main", { className: "p-6 space-y-4 max-w-2xl", children: [_jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Artikel toevoegen" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Stap ", step + 1, " van ", STEPS.length, ": ", STEPS[step]] })] }), _jsx(FloridayConnectionNotice, { status: conn.status, environment: conn.selectedEnvironment, error: conn.error }), _jsxs(Card, { className: "p-5 space-y-4", children: [step === 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-name", children: "Naam" }), _jsx(Input, { id: "na-name", value: name, onChange: (e) => setName(e.target.value), autoFocus: true })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-code", children: "Artikelcode (optioneel)" }), _jsx(Input, { id: "na-code", value: code, onChange: (e) => setCode(e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Leeg laten: er wordt automatisch een code gemaakt." })] })] })), step === 1 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-stock", children: "Voorraad (stuks)" }), _jsx(Input, { id: "na-stock", type: "number", min: 0, value: canSaveStock ? stock : "", onChange: (e) => setStock(e.target.value), disabled: !canSaveStock, "aria-describedby": "na-stock-help", autoFocus: true }), !canSaveStock && (_jsx("p", { id: "na-stock-help", className: "text-xs text-muted-foreground", children: "Koppeling wordt gecontroleerd\u2026" })), canSaveStock && !hasFloriday && (_jsx("p", { id: "na-stock-help", className: "text-xs text-muted-foreground", children: LOCAL_STOCK_TEXT }))] })), step === 2 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-price", children: "Prijs per stuk (EUR, excl. btw)" }), _jsx(Input, { id: "na-price", inputMode: "decimal", value: price, onChange: (e) => setPrice(e.target.value), placeholder: "Bijv. 1,10", autoFocus: true }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Zonder prijs komt het artikel niet in Floriday (\"Prijs ontbreekt\")." })] })), step === 3 && (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Zonder volledige belading kan de karindeling dit artikel niet plannen. Alle vijf velden zijn verplicht." }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-fust", children: "Fust (VBN-code)" }), _jsxs("select", { id: "na-fust", className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: packing.vbnPackageCode, onChange: (e) => setPack("vbnPackageCode", e.target.value), children: [_jsx("option", { value: "", children: "Kies een fust\u2026" }), fusts.map((f) => (_jsxs("option", { value: String(f.vbn), children: [f.vbn, " \u2013 ", f.name] }, f.vbn)))] }), _jsx(Input, { "aria-label": "Andere VBN-fustcode", placeholder: "Of typ een VBN-fustcode, bijv. 412", inputMode: "numeric", value: packing.vbnPackageCode, onChange: (e) => setPack("vbnPackageCode", e.target.value) })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-ppc", children: "Planten per fust" }), _jsx(Input, { id: "na-ppc", inputMode: "numeric", value: packing.piecesPerPackage, onChange: (e) => setPack("piecesPerPackage", e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-cpl", children: "Fusten per laag" }), _jsx(Input, { id: "na-cpl", inputMode: "numeric", value: packing.packagesPerLayer, onChange: (e) => setPack("packagesPerLayer", e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-lpc", children: "Lagen per kar" }), _jsx(Input, { id: "na-lpc", inputMode: "numeric", value: packing.layersPerLoadCarrier, onChange: (e) => setPack("layersPerLoadCarrier", e.target.value) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-lc", children: "Ladingdrager" }), _jsxs("select", { id: "na-lc", className: "w-full rounded-md border bg-background px-3 py-2 text-sm", value: packing.loadCarrier, onChange: (e) => setPack("loadCarrier", e.target.value), children: [_jsx("option", { value: "", children: "Kies een ladingdrager\u2026" }), carriers.items.map((c) => (_jsx("option", { value: c.code, children: c.naam }, c.code)))] }), carriers.error && _jsx("p", { className: "text-xs text-destructive", children: carriers.error }), engineCarrier.melding && _jsx("p", { className: "text-xs text-destructive", children: engineCarrier.melding })] }), missingPacking.length > 0 && (_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Nog invullen: ", missingPacking.join(", "), "."] }))] })), saveError && (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: saveError })), _jsxs("div", { className: "flex flex-wrap justify-between gap-2", children: [_jsx(Button, { variant: "outline", disabled: step === 0 || saving, onClick: () => setStep((s) => s - 1), children: "Vorige" }), step < STEPS.length - 1 ? (_jsx(Button, { disabled: !canNext, onClick: () => setStep((s) => s + 1), children: "Volgende" })) : (_jsxs(Button, { disabled: saving || !name.trim() || !packingReady, onClick: () => void save(), children: [saving ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Check, { className: "mr-2 h-4 w-4" }), saving ? "Bezig met opslaan…" : "Artikel opslaan"] }))] })] }), hasFloriday && (_jsx("button", { type: "button", className: "text-sm text-muted-foreground underline hover:text-foreground", onClick: () => setFull(true), children: "Meteen alle Floriday-gegevens invullen" }))] }));
|
|
171
190
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StockMutationHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockMutationHistoryCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"StockMutationHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockMutationHistoryCard.tsx"],"names":[],"mappings":"AAYA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAiDD,wBAAgB,wBAAwB,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,KAAK,+BA4O1E"}
|
|
@@ -5,6 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
5
5
|
* de groepssleutel wanneer het artikel via een groep wordt afgeboekt).
|
|
6
6
|
*/
|
|
7
7
|
import { useEffect, useState } from "react";
|
|
8
|
+
import { dbFoutTekst } from "../../lib/db-error-message.js";
|
|
8
9
|
import { Card, CardContent, Badge, Button } from "@flowselections/core";
|
|
9
10
|
import { ChevronDown, ChevronUp, Loader2, History } from "lucide-react";
|
|
10
11
|
import { supabase } from "../../integrations/supabase/client.js";
|
|
@@ -122,7 +123,7 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
|
122
123
|
}
|
|
123
124
|
catch (e) {
|
|
124
125
|
if (!cancelled)
|
|
125
|
-
setError(e
|
|
126
|
+
setError(dbFoutTekst(e, "Geschiedenis laden"));
|
|
126
127
|
}
|
|
127
128
|
};
|
|
128
129
|
load();
|
|
@@ -135,7 +136,7 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
|
135
136
|
supabase.removeChannel(channel);
|
|
136
137
|
};
|
|
137
138
|
}, [tradeItemKey, productId]);
|
|
138
|
-
return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(History, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Afboekgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} mutatie${rows.length === 1 ? "" : "s"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (
|
|
139
|
+
return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(History, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Afboekgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} mutatie${rows.length === 1 ? "" : "s"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (_jsx("div", { className: "text-sm text-destructive", children: error })), !rows && !error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Mutaties laden\u2026"] })), rows && rows.length === 0 && !error && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen voorraadmutaties voor dit artikel." })), rows && rows.length > 0 && (_jsxs("div", { className: "overflow-x-auto rounded-md border", children: [_jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-xs uppercase text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 text-left", children: "Wanneer" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Soort" }), _jsx("th", { className: "px-3 py-2 text-right", children: "Aantal" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Van \u2192 naar" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Order" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Door" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Reden" })] }) }), _jsx("tbody", { children: (expanded ? rows : rows.slice(0, 5)).map((r) => {
|
|
139
140
|
const meta = TYPE_LABEL[r.mutation_type] ?? {
|
|
140
141
|
label: r.mutation_type || "Onbekend",
|
|
141
142
|
tone: "bg-slate-100 text-slate-700 border-slate-200",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAmF3D;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,mBAAmB,GAAG,aAAa,CAAC,CAAC;;;;;;;EAgBzG;AAED,wBAAgB,YAAY,gCAsW3B"}
|
|
@@ -7,6 +7,7 @@ import { Link } from "@tanstack/react-router";
|
|
|
7
7
|
import { Button, Card, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
|
|
8
8
|
import { AlertTriangle, CheckCircle2, FileText, PackageX, Plus, Search, } from "lucide-react";
|
|
9
9
|
import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
|
|
10
|
+
import { useEnsureVoorraadDefaults } from "../../hooks/useEnsureVoorraadDefaults.js";
|
|
10
11
|
import { FloridayConnectionNotice } from "./FloridayConnectionNotice.js";
|
|
11
12
|
import { useVoorraadData } from "../../hooks/useVoorraadData.js";
|
|
12
13
|
import { StockSyncStatus } from "./StockSyncStatus.js";
|
|
@@ -69,6 +70,7 @@ export function countStock(items) {
|
|
|
69
70
|
return { total: items.length, inStock, outOfStock, belowMin, fullyReserved, freeAvailable };
|
|
70
71
|
}
|
|
71
72
|
export function VoorraadPage() {
|
|
73
|
+
useEnsureVoorraadDefaults();
|
|
72
74
|
const conn = useFloridayConnection();
|
|
73
75
|
const { activeId, error: connError } = conn;
|
|
74
76
|
const { items, loading, error, reload } = useVoorraadData(activeId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"AA4BA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,iBAAiB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,KAAK,
|
|
1
|
+
{"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"AA4BA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,iBAAiB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,KAAK,sCA+RzF"}
|
|
@@ -4,9 +4,9 @@ import { Button, Card, CardContent, Input, Label, Textarea, toast, Dialog, Dialo
|
|
|
4
4
|
import { Loader2, Package, Check } from "lucide-react";
|
|
5
5
|
import { useTradeItemStock, stockKeysFor } from "../../hooks/useTradeItemStock.js";
|
|
6
6
|
import { supabase } from "../../integrations/supabase/client.js";
|
|
7
|
-
import { FloridaySentCard } from "./FloridaySentCard.js";
|
|
8
7
|
import { LOCAL_STOCK_TEXT } from "../../lib/stock-scope.js";
|
|
9
|
-
import { AFBOEK_REDENEN, afboekFout,
|
|
8
|
+
import { AFBOEK_REDENEN, afboekFout, opslaanMelding, OPSLAAN_MISLUKT_TEKST, valuesAfterSave } from "../../lib/stock-save.js";
|
|
9
|
+
import { dbFoutTekst } from "../../lib/db-error-message.js";
|
|
10
10
|
export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }) {
|
|
11
11
|
const keys = stockKeysFor({
|
|
12
12
|
tradeItemId,
|
|
@@ -18,33 +18,14 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
|
|
|
18
18
|
const [values, setValues] = useState(row);
|
|
19
19
|
const [saving, setSaving] = useState(false);
|
|
20
20
|
const [touched, setTouched] = useState(false);
|
|
21
|
-
const [hasBom, setHasBom] = useState(false);
|
|
22
21
|
const [afboekOpen, setAfboekOpen] = useState(false);
|
|
23
22
|
const [afboekAantal, setAfboekAantal] = useState("");
|
|
24
23
|
const [afboekReden, setAfboekReden] = useState(null);
|
|
25
24
|
const [afboekNotitie, setAfboekNotitie] = useState("");
|
|
26
25
|
const [afboekBezig, setAfboekBezig] = useState(false);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
let cancelled = false;
|
|
31
|
-
(async () => {
|
|
32
|
-
if (!productId && !tradeItemId) {
|
|
33
|
-
setHasBom(false);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
let query = supabase.from("product_bom_items").select("id").limit(1);
|
|
37
|
-
query = productId
|
|
38
|
-
? query.eq("parent_product_id", productId)
|
|
39
|
-
: query.eq("parent_trade_item_floriday_id", tradeItemId);
|
|
40
|
-
const { data } = await query;
|
|
41
|
-
if (!cancelled)
|
|
42
|
-
setHasBom((data ?? []).length > 0);
|
|
43
|
-
})();
|
|
44
|
-
return () => {
|
|
45
|
-
cancelled = true;
|
|
46
|
-
};
|
|
47
|
-
}, [productId, tradeItemId]);
|
|
26
|
+
const [wijzigingNotitie, setWijzigingNotitie] = useState("");
|
|
27
|
+
const [artikelNotitieOpen, setArtikelNotitieOpen] = useState(false);
|
|
28
|
+
const [artikelNotitieGewijzigd, setArtikelNotitieGewijzigd] = useState(false);
|
|
48
29
|
// Is dit artikel de kop van een productgroep? Dan geen voorraad invoeren.
|
|
49
30
|
const [isGroupHead, setIsGroupHead] = useState(false);
|
|
50
31
|
useEffect(() => {
|
|
@@ -76,52 +57,36 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
|
|
|
76
57
|
return (_jsx(Card, { className: "p-4 text-sm text-muted-foreground", children: "Voorraad kan pas worden ingesteld nadat het artikel voor het eerst is opgeslagen." }));
|
|
77
58
|
}
|
|
78
59
|
if (stockError) {
|
|
79
|
-
return (
|
|
60
|
+
return (_jsx(Card, { className: "p-4 text-sm text-destructive", children: "Voorraad laden lukt niet. Controleer of je bent ingelogd en of dit artikel aan een voorraadverbinding is gekoppeld." }));
|
|
80
61
|
}
|
|
81
62
|
const available = Math.max(0, values.physical_stock - values.reserved_stock);
|
|
82
|
-
// Foutmeldingen uit de database zijn soms JSON of technisch; hier maken we
|
|
83
|
-
// er gewone taal van zodat de gebruiker weet wat er moet gebeuren.
|
|
84
|
-
const humanError = (e) => {
|
|
85
|
-
const raw = e?.message ?? String(e);
|
|
86
|
-
try {
|
|
87
|
-
const parsed = JSON.parse(raw);
|
|
88
|
-
const msg = parsed?.message ?? parsed?.error ?? raw;
|
|
89
|
-
if (typeof msg === "string" && msg.toLowerCase().includes("onvoldoende voorraad")) {
|
|
90
|
-
return "Onvoldoende voorraad van een onderdeel. Pas de voorraad van het onderdeel eerst aan.";
|
|
91
|
-
}
|
|
92
|
-
return typeof msg === "string" ? msg : raw;
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return raw;
|
|
96
|
-
}
|
|
97
|
-
};
|
|
98
63
|
// Opslaan via set_product_stock werkt ook zonder koppeling (lokale context, 1.0.107).
|
|
99
64
|
const canWrite = !!productId || !!connectionId;
|
|
100
65
|
const handleSave = async () => {
|
|
101
66
|
if (!canWrite)
|
|
102
67
|
return;
|
|
103
68
|
setSaving(true);
|
|
69
|
+
let result = null;
|
|
70
|
+
let fout = null;
|
|
104
71
|
try {
|
|
105
|
-
await save(values);
|
|
106
|
-
setValues(valuesAfterSave(true, values, row));
|
|
107
|
-
setTouched(false);
|
|
108
|
-
let poortOpen = null;
|
|
109
|
-
if (tradeItemId) {
|
|
110
|
-
const { data } = await supabase.rpc("voorraad_floriday_klaarzetten_toegestaan");
|
|
111
|
-
poortOpen = data === true;
|
|
112
|
-
}
|
|
113
|
-
const extra = floridayNaOpslaanTekst(!!tradeItemId, poortOpen);
|
|
114
|
-
toast.success(extra ? `Voorraad opgeslagen. ${extra}` : "Voorraad opgeslagen");
|
|
72
|
+
result = await save(values, { wijzigingNotitie, artikelNotitieWijzigen: artikelNotitieGewijzigd });
|
|
115
73
|
}
|
|
116
74
|
catch (e) {
|
|
117
|
-
|
|
118
|
-
setValues(valuesAfterSave(false, values, row));
|
|
119
|
-
setTouched(false);
|
|
120
|
-
toast.error(`Opslaan mislukt, niets gewijzigd: ${humanError(e)}`);
|
|
75
|
+
fout = dbFoutTekst(e, "Opslaan");
|
|
121
76
|
}
|
|
122
|
-
|
|
123
|
-
|
|
77
|
+
const melding = opslaanMelding(result, !!tradeItemId);
|
|
78
|
+
setValues(valuesAfterSave(melding.ok, values, row));
|
|
79
|
+
setTouched(false);
|
|
80
|
+
if (melding.ok) {
|
|
81
|
+
setWijzigingNotitie("");
|
|
82
|
+
setArtikelNotitieGewijzigd(false);
|
|
83
|
+
toast.success(melding.tekst);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Niets opgeslagen (ook bij teruggedraaide opslag): oude waarden terug.
|
|
87
|
+
toast.error(fout ? `${OPSLAAN_MISLUKT_TEKST}. ${fout}` : OPSLAAN_MISLUKT_TEKST);
|
|
124
88
|
}
|
|
89
|
+
setSaving(false);
|
|
125
90
|
};
|
|
126
91
|
const handleAfboeken = async () => {
|
|
127
92
|
const aantal = Number(afboekAantal);
|
|
@@ -145,7 +110,7 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
|
|
|
145
110
|
p_notitie: afboekNotitie.trim() || null,
|
|
146
111
|
});
|
|
147
112
|
if (error)
|
|
148
|
-
throw
|
|
113
|
+
throw error;
|
|
149
114
|
toast.success(`${aantal} afgeboekt`);
|
|
150
115
|
setAfboekOpen(false);
|
|
151
116
|
setAfboekAantal("");
|
|
@@ -154,7 +119,7 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
|
|
|
154
119
|
await refetch();
|
|
155
120
|
}
|
|
156
121
|
catch (e) {
|
|
157
|
-
toast.error(`Afboeken mislukt, niets gewijzigd
|
|
122
|
+
toast.error(`Afboeken mislukt, niets gewijzigd. ${dbFoutTekst(e, "Afboeken")}`);
|
|
158
123
|
}
|
|
159
124
|
finally {
|
|
160
125
|
setAfboekBezig(false);
|
|
@@ -164,5 +129,8 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
|
|
|
164
129
|
setTouched(true);
|
|
165
130
|
setValues((v) => ({ ...v, [id]: Number(e.target.value) || 0 }));
|
|
166
131
|
} })] }));
|
|
167
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(Package, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Voorraad" }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Beschikbaar: ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: available })] })] }), _jsx(CardContent, { className: "pt-0 space-y-4", children: loading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Voorraad laden\u2026"] })) : (_jsxs(_Fragment, { children: [Number(values.reserved_stock) > Number(values.physical_stock) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ["Let op: er is meer gereserveerd (", Number(values.reserved_stock), ") dan er ligt (", Number(values.physical_stock), "). Controleer de open orders van dit artikel."] })), _jsxs("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-4", children: [numField("physical_stock", "Liggend"), numField("minimum_stock", "Minimum"), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "reserved_stock", children: "Gereserveerd" }), _jsx(Input, { id: "reserved_stock", type: "number", readOnly: true, disabled: true, value: String(values.reserved_stock ?? 0) }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: "Berekend uit open orders" })] }), numField("incoming_stock", "Inkomend")] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "stock-
|
|
132
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(Package, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Voorraad" }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Beschikbaar: ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: available })] })] }), _jsx(CardContent, { className: "pt-0 space-y-4", children: loading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Voorraad laden\u2026"] })) : (_jsxs(_Fragment, { children: [Number(values.reserved_stock) > Number(values.physical_stock) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ["Let op: er is meer gereserveerd (", Number(values.reserved_stock), ") dan er ligt (", Number(values.physical_stock), "). Controleer de open orders van dit artikel."] })), _jsxs("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-4", children: [numField("physical_stock", "Liggend"), numField("minimum_stock", "Minimum"), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "reserved_stock", children: "Gereserveerd" }), _jsx(Input, { id: "reserved_stock", type: "number", readOnly: true, disabled: true, value: String(values.reserved_stock ?? 0) }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: "Berekend uit open orders" })] }), numField("incoming_stock", "Inkomend")] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "stock-change-note", children: "Notitie bij deze wijziging (optioneel)" }), _jsx(Textarea, { id: "stock-change-note", rows: 2, value: wijzigingNotitie, disabled: !canWrite, onChange: (e) => setWijzigingNotitie(e.target.value), placeholder: "Komt alleen in de geschiedenis bij deze wijziging" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("button", { type: "button", className: "text-xs text-muted-foreground underline-offset-2 hover:underline", onClick: () => setArtikelNotitieOpen((o) => !o), children: artikelNotitieOpen ? "Vaste artikelnotitie verbergen" : `Vaste artikelnotitie${values.notes ? " (ingevuld)" : ""}` }), artikelNotitieOpen && (_jsx(Textarea, { id: "stock-notes", rows: 2, value: values.notes ?? "", disabled: !canWrite, onChange: (e) => {
|
|
133
|
+
setArtikelNotitieGewijzigd(true);
|
|
134
|
+
setValues((v) => ({ ...v, notes: e.target.value }));
|
|
135
|
+
}, placeholder: "Bijv. vaste locatie; verandert alleen als je hem hier aanpast" }))] }), _jsxs("div", { className: "flex justify-end", children: [!connectionId && (_jsx("span", { className: "mr-auto self-center text-xs text-muted-foreground", children: LOCAL_STOCK_TEXT })), _jsx(Button, { variant: "outline", className: "mr-2", onClick: () => setAfboekOpen(true), disabled: saving || !canWrite || Number(row.physical_stock) <= 0, children: "Afboeken" }), _jsxs(Button, { onClick: handleSave, disabled: saving || !canWrite, children: [saving ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Voorraad opslaan"] })] })] })) })] }), _jsx(Dialog, { open: afboekOpen, onOpenChange: setAfboekOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Afboeken" }), _jsxs(DialogDescription, { children: ["Haal planten van de liggende voorraad (", Number(row.physical_stock), "). Kies altijd een reden."] })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "afboek-aantal", children: "Aantal" }), _jsx(Input, { id: "afboek-aantal", type: "number", min: 1, value: afboekAantal, onChange: (e) => setAfboekAantal(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Reden" }), _jsxs(Select, { value: afboekReden ?? undefined, onValueChange: setAfboekReden, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een reden" }) }), _jsx(SelectContent, { children: AFBOEK_REDENEN.map((r) => (_jsx(SelectItem, { value: r.value, children: r.label }, r.value))) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "afboek-notitie", children: "Notitie (optioneel)" }), _jsx(Textarea, { id: "afboek-notitie", rows: 2, value: afboekNotitie, onChange: (e) => setAfboekNotitie(e.target.value) })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => setAfboekOpen(false), children: "Annuleren" }), _jsxs(Button, { onClick: handleAfboeken, disabled: afboekBezig || !afboekReden || !afboekAantal, children: [afboekBezig && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Afboeken"] })] })] }) })] }));
|
|
168
136
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zelfherstel bij het openen van Voorraad: vult ontbrekende standaardgegevens
|
|
3
|
+
* aan via voorraad_ensure_defaults(). Eén keer per sessie, op de achtergrond;
|
|
4
|
+
* een fout blokkeert niets en geeft alleen een melding in gewone taal.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useEnsureVoorraadDefaults(): void;
|
|
7
|
+
//# sourceMappingURL=useEnsureVoorraadDefaults.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEnsureVoorraadDefaults.d.ts","sourceRoot":"","sources":["../../src/hooks/useEnsureVoorraadDefaults.ts"],"names":[],"mappings":"AAMA;;;;GAIG;AACH,wBAAgB,yBAAyB,SAaxC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { toast } from "@flowselections/core";
|
|
3
|
+
import { supabase } from "../integrations/supabase/client.js";
|
|
4
|
+
let gestart = false;
|
|
5
|
+
/**
|
|
6
|
+
* Zelfherstel bij het openen van Voorraad: vult ontbrekende standaardgegevens
|
|
7
|
+
* aan via voorraad_ensure_defaults(). Eén keer per sessie, op de achtergrond;
|
|
8
|
+
* een fout blokkeert niets en geeft alleen een melding in gewone taal.
|
|
9
|
+
*/
|
|
10
|
+
export function useEnsureVoorraadDefaults() {
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (gestart)
|
|
13
|
+
return;
|
|
14
|
+
gestart = true;
|
|
15
|
+
void (async () => {
|
|
16
|
+
const { data, error } = await supabase.rpc("voorraad_ensure_defaults");
|
|
17
|
+
if (error || (data && data.ok === false)) {
|
|
18
|
+
// Functie nog niet geïnstalleerd of mislukt: niets blokkeren.
|
|
19
|
+
if (error?.code === "PGRST202")
|
|
20
|
+
return;
|
|
21
|
+
toast.error("Standaardgegevens van Voorraad konden niet worden aangevuld. Neem contact op met de beheerder als dit blijft gebeuren.");
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
24
|
+
}, []);
|
|
25
|
+
}
|
|
@@ -40,7 +40,10 @@ export declare function lookupStock(map: Map<string, TradeItemStockRow>, params:
|
|
|
40
40
|
export declare function useTradeItemStock(connectionId: string | null, keyOrKeys: string | string[] | null, productId?: string | null): {
|
|
41
41
|
row: TradeItemStockRow;
|
|
42
42
|
loading: boolean;
|
|
43
|
-
save: (values: TradeItemStockRow
|
|
43
|
+
save: (values: TradeItemStockRow, opts?: {
|
|
44
|
+
wijzigingNotitie?: string | null;
|
|
45
|
+
artikelNotitieWijzigen?: boolean;
|
|
46
|
+
}) => Promise<Record<string, unknown>>;
|
|
44
47
|
error: string | null;
|
|
45
48
|
resolvedKey: string | null;
|
|
46
49
|
resolvedConnectionId: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAEhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAUX;AAGD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnC,MAAM,EAAE;IACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,iBAAiB,GAAG,SAAS,CAa/B;AAGD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EACnC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;;;
|
|
1
|
+
{"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAEhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAUX;AAGD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnC,MAAM,EAAE;IACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,iBAAiB,GAAG,SAAS,CAa/B;AAGD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EACnC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;;;mBA2Gb,iBAAiB,SACnB;QAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,sBAAsB,CAAC,EAAE,OAAO,CAAA;KAAE,KAC3E,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;;;;;EA+EtC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAuBzC"}
|
|
@@ -147,57 +147,43 @@ export function useTradeItemStock(connectionId, keyOrKeys, productId) {
|
|
|
147
147
|
supabase.removeChannel(channel);
|
|
148
148
|
};
|
|
149
149
|
}, [connectionId, keys, primaryKey, refetch, resolvedConnectionId, resolvedKey]);
|
|
150
|
-
const save = useCallback(async (values) => {
|
|
150
|
+
const save = useCallback(async (values, opts = {}) => {
|
|
151
151
|
let savedKey = resolvedKey ?? primaryKey;
|
|
152
152
|
let savedConnectionId = resolvedConnectionId ?? connectionId;
|
|
153
|
-
if (productId) {
|
|
154
|
-
|
|
155
|
-
p_product_id: productId,
|
|
156
|
-
p_physical_stock: values.physical_stock,
|
|
157
|
-
p_minimum_stock: values.minimum_stock,
|
|
158
|
-
p_incoming_stock: values.incoming_stock,
|
|
159
|
-
// Notitie altijd expliciet meegeven (huidige of nieuwe waarde); minimum
|
|
160
|
-
// en inkomend komen uit de geladen rij, dus nooit stil 0.
|
|
161
|
-
p_notes: values.notes ?? null,
|
|
162
|
-
p_change_source: "MANUAL_STOCK",
|
|
163
|
-
});
|
|
164
|
-
if (rpcError)
|
|
165
|
-
throw new Error(rpcError.message);
|
|
166
|
-
const saved = Array.isArray(data) ? data[0] : null;
|
|
167
|
-
savedKey = saved?.trade_item_key ?? savedKey;
|
|
168
|
-
savedConnectionId = saved?.connection_id ?? savedConnectionId;
|
|
169
|
-
setResolvedKey(savedKey);
|
|
170
|
-
setResolvedConnectionId(savedConnectionId);
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
if (!connectionId || !primaryKey) {
|
|
174
|
-
throw new Error("Ontbrekende connectie of artikel-key");
|
|
175
|
-
}
|
|
176
|
-
const userRes = await supabase.auth.getUser();
|
|
177
|
-
const userId = userRes.data.user?.id;
|
|
178
|
-
if (!userId)
|
|
179
|
-
throw new Error("Niet ingelogd");
|
|
180
|
-
const { error: upsertError } = await supabase
|
|
181
|
-
.from("trade_item_stock")
|
|
182
|
-
.upsert({
|
|
183
|
-
user_id: userId,
|
|
184
|
-
connection_id: connectionId,
|
|
185
|
-
trade_item_key: primaryKey,
|
|
186
|
-
physical_stock: values.physical_stock,
|
|
187
|
-
minimum_stock: values.minimum_stock,
|
|
188
|
-
// Gereserveerd wordt berekend uit de orders; nooit vanuit dit scherm schrijven.
|
|
189
|
-
incoming_stock: values.incoming_stock,
|
|
190
|
-
notes: values.notes,
|
|
191
|
-
change_source: "MANUAL_STOCK",
|
|
192
|
-
}, { onConflict: "connection_id,trade_item_key" });
|
|
193
|
-
if (upsertError)
|
|
194
|
-
throw new Error(upsertError.message);
|
|
153
|
+
if (!productId && (!connectionId || !primaryKey)) {
|
|
154
|
+
throw new Error("Ontbrekende connectie of artikel-key");
|
|
195
155
|
}
|
|
156
|
+
// 1.0.140: één opslagfunctie; het antwoord bevestigt dat de database
|
|
157
|
+
// heeft opgeslagen. Notitie per wijziging gaat alleen naar de geschiedenis.
|
|
158
|
+
const { data, error: rpcError } = await supabase.rpc("voorraad_voorraad_opslaan", {
|
|
159
|
+
p_product_id: productId ?? null,
|
|
160
|
+
p_connection_id: productId ? null : connectionId,
|
|
161
|
+
p_trade_item_key: productId ? null : primaryKey,
|
|
162
|
+
p_physical_stock: values.physical_stock,
|
|
163
|
+
p_minimum_stock: values.minimum_stock,
|
|
164
|
+
p_incoming_stock: values.incoming_stock,
|
|
165
|
+
p_wijziging_notitie: opts.wijzigingNotitie?.trim() || null,
|
|
166
|
+
p_artikel_notitie: values.notes ?? null,
|
|
167
|
+
p_artikel_notitie_wijzigen: !!opts.artikelNotitieWijzigen,
|
|
168
|
+
});
|
|
169
|
+
if (rpcError)
|
|
170
|
+
throw rpcError;
|
|
171
|
+
const result = (data ?? null);
|
|
172
|
+
if (!result || result.opgeslagen !== true)
|
|
173
|
+
throw new Error("Opslaan niet bevestigd");
|
|
174
|
+
savedKey = result.trade_item_key ?? savedKey;
|
|
175
|
+
savedConnectionId = result.connection_id ?? savedConnectionId;
|
|
176
|
+
setResolvedKey(savedKey);
|
|
177
|
+
setResolvedConnectionId(savedConnectionId);
|
|
196
178
|
if (!savedConnectionId || !savedKey) {
|
|
197
179
|
throw new Error("Ontbrekende connectie of artikel-key");
|
|
198
180
|
}
|
|
199
181
|
const previousPhysical = row?.physical_stock ?? 0;
|
|
200
|
-
setRow({
|
|
182
|
+
setRow({
|
|
183
|
+
...values,
|
|
184
|
+
notes: result.notes ?? null,
|
|
185
|
+
reserved_stock: Number(result.reserved_stock ?? row?.reserved_stock ?? values.reserved_stock),
|
|
186
|
+
});
|
|
201
187
|
// Verkoop = daling van de fysieke voorraad. Het bijbehorende aantal
|
|
202
188
|
// fusten wordt automatisch van de fustenvoorraad afgeschreven op basis
|
|
203
189
|
// van de primaire belading van dit artikel.
|
|
@@ -234,6 +220,7 @@ export function useTradeItemStock(connectionId, keyOrKeys, productId) {
|
|
|
234
220
|
// Engine-tabel niet bereikbaar; voorraad is lokaal wel opgeslagen.
|
|
235
221
|
}
|
|
236
222
|
})();
|
|
223
|
+
return result;
|
|
237
224
|
}, [connectionId, primaryKey, productId, resolvedConnectionId, resolvedKey, row]);
|
|
238
225
|
return { row: row ?? EMPTY, loading, save, error, resolvedKey, resolvedConnectionId, refetch };
|
|
239
226
|
}
|