@flowselections/floriday-voorraad 1.0.105 → 1.0.110
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/ArtikelWizard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelWizard.js +3 -2
- package/dist-lib/components/voorraad/FloridayLoadCarriers.d.ts +9 -0
- package/dist-lib/components/voorraad/FloridayLoadCarriers.d.ts.map +1 -0
- package/dist-lib/components/voorraad/FloridayLoadCarriers.js +60 -0
- package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/FustenPage.js +14 -6
- package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/NieuwArtikelPage.js +7 -6
- package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockBulkButtons.js +3 -6
- package/dist-lib/components/voorraad/StockSyncStatus.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockSyncStatus.js +10 -2
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.js +6 -4
- package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadTable.js +6 -1
- package/dist-lib/hooks/useFloridayWriteMode.d.ts +7 -0
- package/dist-lib/hooks/useFloridayWriteMode.d.ts.map +1 -0
- package/dist-lib/hooks/useFloridayWriteMode.js +21 -0
- package/dist-lib/hooks/useStockSyncHealth.d.ts +9 -0
- package/dist-lib/hooks/useStockSyncHealth.d.ts.map +1 -1
- package/dist-lib/hooks/useStockSyncHealth.js +23 -0
- package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemStock.js +19 -9
- package/dist-lib/integrations/supabase/types.d.ts +1669 -122
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/__tests__/floriday-write-mode.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/floriday-write-mode.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/floriday-write-mode.test.js +40 -0
- package/dist-lib/lib/floriday-write-mode.d.ts +21 -0
- package/dist-lib/lib/floriday-write-mode.d.ts.map +1 -0
- package/dist-lib/lib/floriday-write-mode.js +40 -0
- package/dist-lib/lib/fusten.d.ts +20 -0
- package/dist-lib/lib/fusten.d.ts.map +1 -1
- package/dist-lib/lib/fusten.js +48 -41
- package/dist-lib/lib/stock-scope.d.ts +3 -1
- package/dist-lib/lib/stock-scope.d.ts.map +1 -1
- package/dist-lib/lib/stock-scope.js +3 -1
- package/package.json +1 -1
- package/sql/AGENTS.md +2 -8
- package/sql/floriday-voorraad-schema.sql +346 -68
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA+MA,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EACX,SAAS,EACT,WAAW,GACZ,GAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACjB,+BA0tEL"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { saveOwnPackings, packingRowsFromWizard } from "../../lib/product-packings.js";
|
|
3
|
+
import { FloridayLoadCarriers } from "./FloridayLoadCarriers.js";
|
|
3
4
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
5
|
import { Link, useNavigate } from "@tanstack/react-router";
|
|
5
6
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, Button, Card, CardContent, Checkbox, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Popover, PopoverContent, PopoverTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, toast, } from "@flowselections/core";
|
|
@@ -2526,7 +2527,7 @@ function StepWarehouse({ data, update, activeId }) {
|
|
|
2526
2527
|
* 3. Reguliere of klantspecifieke belading (multi-select organisaties)
|
|
2527
2528
|
* 4. Optionele artikelfoto in dit fust (upload via Floriday /media)
|
|
2528
2529
|
*/
|
|
2529
|
-
function StepBeladingen({ data, update, packagingTypes, loadCarriers, floridayCustomers, activeId, packingStatus, }) {
|
|
2530
|
+
function StepBeladingen({ data, update, packagingTypes, loadCarriers, floridayCustomers, activeId, packingStatus, parentFloridayId, }) {
|
|
2530
2531
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
2531
2532
|
const [editingId, setEditingId] = useState(null);
|
|
2532
2533
|
const openNew = () => {
|
|
@@ -2576,7 +2577,7 @@ function StepBeladingen({ data, update, packagingTypes, loadCarriers, floridayCu
|
|
|
2576
2577
|
return (_jsxs("tr", { className: "hover:bg-muted/30", children: [_jsx("td", { className: "px-3 py-2", children: _jsx("input", { type: "radio", name: "primary-packing", checked: Boolean(p.isPrimary), onChange: () => setPrimary(p.id), "aria-label": "Als primaire belading instellen" }) }), _jsxs("td", { className: "px-3 py-2", children: [_jsx("div", { className: "font-medium", children: pkgLabel
|
|
2577
2578
|
? `${codeOf(pkgLabel)}: ${labelOf(pkgLabel)}`
|
|
2578
2579
|
: p.packagingType || "—" }), p.source === "SUPPLIER" && (_jsx("div", { className: "text-xs text-muted-foreground", children: p.packagingSupplierName ?? p.packagingSupplierId }))] }), _jsx("td", { className: "px-3 py-2", children: LOAD_CARRIER_LABELS[p.loadCarrier] ?? p.loadCarrier ?? "—" }), _jsxs("td", { className: "px-3 py-2 tabular-nums", children: [p.layersPerLoadCarrier, "\u00D7", p.packagesPerLayer, "\u00D7", p.unitsPerPackage] }), _jsx("td", { className: "px-3 py-2", children: _jsxs("div", { className: "flex flex-wrap items-center gap-1", children: [p.isCustomerSpecific ? (_jsxs("span", { className: "rounded bg-amber-100 px-2 py-0.5 text-xs text-amber-900", children: ["Klantspecifiek (", p.customerOrganizationIds.length, ")"] })) : (_jsx("span", { className: "text-xs text-muted-foreground", children: "Regulier" })), p.isHiddenForDirectSales && (_jsx("span", { className: "rounded bg-slate-200 px-2 py-0.5 text-xs text-slate-700", children: "Verborgen" }))] }) }), _jsx("td", { className: "px-3 py-2 text-right", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => openEdit(p.id), children: "Bewerk" }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => remove(p.id), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }) })] }, p.id));
|
|
2579
|
-
}) })] }) })), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: openNew, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Belading toevoegen"] }), _jsx(PackingConfigDialog, { open: dialogOpen, onOpenChange: (o) => {
|
|
2580
|
+
}) })] }) })), _jsx(FloridayLoadCarriers, { connectionId: activeId, floridayId: parentFloridayId }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: openNew, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), " Belading toevoegen"] }), _jsx(PackingConfigDialog, { open: dialogOpen, onOpenChange: (o) => {
|
|
2580
2581
|
setDialogOpen(o);
|
|
2581
2582
|
if (!o)
|
|
2582
2583
|
setEditingId(null);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function loadCarriersFromCache(data: unknown): Array<{
|
|
2
|
+
loadCarrier: string | null;
|
|
3
|
+
primary: boolean;
|
|
4
|
+
}>;
|
|
5
|
+
export declare function FloridayLoadCarriers({ connectionId, floridayId, }: {
|
|
6
|
+
connectionId: string | null;
|
|
7
|
+
floridayId: string | null;
|
|
8
|
+
}): import("react").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=FloridayLoadCarriers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FloridayLoadCarriers.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayLoadCarriers.tsx"],"names":[],"mappings":"AASA,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC;IAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC,CAO5G;AAED,wBAAgB,oBAAoB,CAAC,EACnC,YAAY,EACZ,UAAU,GACX,EAAE;IACD,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,+BAsDA"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Ladingdrager per verpakking zoals Floriday die kent:
|
|
4
|
+
* floriday_trade_items_cache.data.packingConfigurations[].loadCarrier,
|
|
5
|
+
* gezocht op connection_id + floriday_id (nooit op naam). Alleen lezen.
|
|
6
|
+
*/
|
|
7
|
+
import { useEffect, useState } from "react";
|
|
8
|
+
import { supabase } from "../../integrations/supabase/client.js";
|
|
9
|
+
import { LOAD_CARRIER_LABELS } from "../../lib/floriday-info.js";
|
|
10
|
+
export function loadCarriersFromCache(data) {
|
|
11
|
+
const list = data?.packingConfigurations;
|
|
12
|
+
if (!Array.isArray(list))
|
|
13
|
+
return [];
|
|
14
|
+
return list.map((p) => ({
|
|
15
|
+
loadCarrier: typeof p?.loadCarrier === "string" && p.loadCarrier.trim() ? p.loadCarrier : null,
|
|
16
|
+
primary: p?.primary === true || p?.isPrimary === true,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
export function FloridayLoadCarriers({ connectionId, floridayId, }) {
|
|
20
|
+
const [state, setState] = useState({ kind: "loading" });
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (!connectionId || !floridayId)
|
|
23
|
+
return;
|
|
24
|
+
let cancelled = false;
|
|
25
|
+
setState({ kind: "loading" });
|
|
26
|
+
void (async () => {
|
|
27
|
+
const { data, error } = await supabase
|
|
28
|
+
.from("floriday_trade_items_cache")
|
|
29
|
+
.select("data")
|
|
30
|
+
.eq("connection_id", connectionId)
|
|
31
|
+
.eq("floriday_id", floridayId)
|
|
32
|
+
.maybeSingle();
|
|
33
|
+
if (cancelled)
|
|
34
|
+
return;
|
|
35
|
+
if (error)
|
|
36
|
+
setState({ kind: "error", message: error.message });
|
|
37
|
+
else if (!data)
|
|
38
|
+
setState({ kind: "missing" });
|
|
39
|
+
else
|
|
40
|
+
setState({ kind: "ok", rows: loadCarriersFromCache(data.data) });
|
|
41
|
+
})();
|
|
42
|
+
return () => {
|
|
43
|
+
cancelled = true;
|
|
44
|
+
};
|
|
45
|
+
}, [connectionId, floridayId]);
|
|
46
|
+
let body;
|
|
47
|
+
if (!connectionId || !floridayId)
|
|
48
|
+
body = "Niet gekoppeld aan Floriday";
|
|
49
|
+
else if (state.kind === "loading")
|
|
50
|
+
body = "Laden…";
|
|
51
|
+
else if (state.kind === "error")
|
|
52
|
+
body = `Niet leesbaar: ${state.message}`;
|
|
53
|
+
else if (state.kind === "missing")
|
|
54
|
+
body = "Artikel niet gevonden in de Floriday-gegevens";
|
|
55
|
+
else if (state.rows.length === 0)
|
|
56
|
+
body = "Floriday heeft geen belading voor dit artikel";
|
|
57
|
+
else
|
|
58
|
+
body = (_jsx("ul", { className: "list-disc pl-5", children: state.rows.map((r, i) => (_jsxs("li", { children: ["Belading ", i + 1, r.primary ? " (primair)" : "", ":", " ", r.loadCarrier ? (LOAD_CARRIER_LABELS[r.loadCarrier] ?? r.loadCarrier) : "—"] }, i))) }));
|
|
59
|
+
return (_jsxs("div", { className: "rounded-md border bg-muted/30 p-3 text-sm", children: [_jsx("div", { className: "mb-1 font-medium", children: "Ladingdrager volgens Floriday" }), _jsx("div", { className: "text-muted-foreground", children: body })] }));
|
|
60
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"FustenPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FustenPage.tsx"],"names":[],"mappings":"AA4KA,wBAAgB,UAAU,gCA6PzB"}
|
|
@@ -5,29 +5,30 @@ import { Button, Card, Input, Badge, toast } from "@flowselections/core";
|
|
|
5
5
|
import { supabase } from "../../integrations/supabase/client.js";
|
|
6
6
|
import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
|
|
7
7
|
import { useFusten } from "../../hooks/useFusten.js";
|
|
8
|
-
import { fetchFustMutations, formatFustDimensions, } from "../../lib/fusten.js";
|
|
8
|
+
import { fetchFloricodeFustMap, fetchFustMutations, formatFustDimensions, resolveFustDimensions, } from "../../lib/fusten.js";
|
|
9
9
|
/** Afmetingen tonen en handmatig aanvullen wanneer de catalogus ze niet kent. */
|
|
10
|
-
function DimensionCell({ fust, onSave, }) {
|
|
10
|
+
function DimensionCell({ fust, floricode, onSave, }) {
|
|
11
11
|
const [editing, setEditing] = useState(false);
|
|
12
12
|
const [draft, setDraft] = useState({
|
|
13
13
|
length_mm: fust.length_mm?.toString() ?? "",
|
|
14
14
|
width_mm: fust.width_mm?.toString() ?? "",
|
|
15
15
|
height_mm: fust.height_mm?.toString() ?? "",
|
|
16
16
|
});
|
|
17
|
-
const
|
|
17
|
+
const resolved = resolveFustDimensions(fust, floricode);
|
|
18
|
+
const label = formatFustDimensions(resolved);
|
|
18
19
|
const toNum = (v) => {
|
|
19
20
|
const n = Math.trunc(Number(v));
|
|
20
21
|
return Number.isFinite(n) && n > 0 ? n : null;
|
|
21
22
|
};
|
|
22
23
|
if (!editing) {
|
|
23
|
-
return (_jsxs("div", { children: [_jsx("div", { className: label ? "tabular-nums" : "text-muted-foreground", children: label ?? "Onbekend" }), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => {
|
|
24
|
+
return (_jsxs("div", { children: [_jsx("div", { className: label ? "tabular-nums" : "text-muted-foreground", children: label ?? "Onbekend" }), resolved.source !== "onbekend" && (_jsx("div", { className: "text-xs text-muted-foreground", children: resolved.source === "floricode" ? "Floricode" : "handmatig" })), _jsx("button", { type: "button", className: "text-xs text-primary hover:underline", onClick: () => {
|
|
24
25
|
setDraft({
|
|
25
26
|
length_mm: fust.length_mm?.toString() ?? "",
|
|
26
27
|
width_mm: fust.width_mm?.toString() ?? "",
|
|
27
28
|
height_mm: fust.height_mm?.toString() ?? "",
|
|
28
29
|
});
|
|
29
30
|
setEditing(true);
|
|
30
|
-
}, children:
|
|
31
|
+
}, children: resolved.source === "handmatig" ? "Wijzigen" : resolved.source === "floricode" ? "Handmatig aanvullen" : "Afmeting toevoegen" })] }));
|
|
31
32
|
}
|
|
32
33
|
return (_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-1", children: [["length_mm", "width_mm", "height_mm"].map((k, i) => (_jsxs("div", { className: "flex items-center gap-1", children: [i > 0 && _jsx("span", { className: "text-muted-foreground", children: "\u00D7" }), _jsx(Input, { type: "number", className: "h-8 w-20 text-right", placeholder: ["L", "B", "H"][i], value: draft[k], onChange: (e) => setDraft((d) => ({ ...d, [k]: e.target.value })) })] }, k))), _jsx("span", { className: "text-xs text-muted-foreground", children: "mm" })] }), _jsxs("div", { className: "flex gap-1", children: [_jsx(Button, { size: "sm", className: "h-7", onClick: async () => {
|
|
33
34
|
await onSave({
|
|
@@ -66,6 +67,13 @@ export function FustenPage() {
|
|
|
66
67
|
const { activeId } = useFloridayConnection();
|
|
67
68
|
const { items, loading, syncing, error, sync, adjust, patch } = useFusten(activeId);
|
|
68
69
|
const [search, setSearch] = useState("");
|
|
70
|
+
const [floricode, setFloricode] = useState(null);
|
|
71
|
+
const [floricodeError, setFloricodeError] = useState(null);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
fetchFloricodeFustMap()
|
|
74
|
+
.then((m) => { setFloricode(m); setFloricodeError(null); })
|
|
75
|
+
.catch((e) => setFloricodeError(e.message));
|
|
76
|
+
}, []);
|
|
69
77
|
const [busyId, setBusyId] = useState(null);
|
|
70
78
|
const [amounts, setAmounts] = useState({});
|
|
71
79
|
const [negative, setNegative] = useState([]);
|
|
@@ -107,7 +115,7 @@ export function FustenPage() {
|
|
|
107
115
|
void loadNegative();
|
|
108
116
|
}
|
|
109
117
|
};
|
|
110
|
-
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight truncate", children: "Fusten" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Alle fusttypen worden automatisch afgeleid uit de beladingen van je producten." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void sync(), disabled: syncing, children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${syncing ? "animate-spin" : ""}` }), "Synchroniseren"] })] }), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Fusten laden mislukt: ", error] })), negative.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive space-y-1", children: [_jsxs("p", { className: "font-medium", children: [negative.length, " fusttype", negative.length === 1 ? " heeft" : "n hebben", " een tekort."] }), _jsx("p", { className: "text-foreground/80", children: "Er is meer uitgeleverd dan ingeboekt. Controleer de ontvangsten en retouren, of boek de werkelijke telling bij." }), _jsx("ul", { className: "text-xs text-foreground/80", children: negative.map((n) => (_jsxs("li", { children: [n.name.trim(), ": tekort van ", n.shortage.toLocaleString("nl-NL"), " fusten", n.duplicate_names > 0 ? " (dit fusttype bestaat meerdere keren)" : ""] }, n.fust_type_id))) })] })), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Fusttypen" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.types })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Totale fustvoorraad" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.stock })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Onder minimum" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.belowMin })] })] }), _jsxs("div", { className: "relative max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Zoek fust\u2026", className: "pl-9" })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("p", { className: "p-6 text-sm text-muted-foreground", children: "Fusten laden\u2026" })) : filtered.length === 0 ? (_jsxs("div", { className: "p-6 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Boxes, { className: "h-4 w-4" }), "Nog geen fusttypen gevonden. Voeg een belading toe aan een product."] })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left", children: _jsxs("tr", { children: [_jsx("th", { className: "p-3", children: "Fust" }), _jsx("th", { className: "p-3", children: "Bron" }), _jsx("th", { className: "p-3", children: "Afmeting (L \u00D7 B \u00D7 H)" }), _jsx("th", { className: "p-3 text-right", children: "Gebruikt door" }), _jsx("th", { className: "p-3 text-right", children: "Voorraad" }), _jsx("th", { className: "p-3 text-right", children: "Minimum" }), _jsx("th", { className: "p-3 text-right", children: "Aanpassen" })] }) }), _jsx("tbody", { children: filtered.map((f) => (_jsxs("tr", { className: "border-t border-border align-top", children: [_jsxs("td", { className: "p-3", children: [_jsx("div", { className: "font-medium", children: f.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: f.vbn_code ? `VBN ${f.vbn_code}` : f.fust_key }), !f.in_use && (_jsx(Badge, { variant: "outline", className: "mt-1", children: "Niet meer in gebruik" })), _jsx("div", { className: "mt-1", children: _jsx(MutationHistory, { fust: f }) })] }), _jsx("td", { className: "p-3", children: f.source === "SUPPLIER" ? "Leverancier" : "VBN" }), _jsx("td", { className: "p-3 text-sm", children: _jsx(DimensionCell, { fust: f, onSave: (v) => patch(f.id, v) }) }), _jsx("td", { className: "p-3 text-right tabular-nums", children: f.used_by_count }), _jsx("td", { className: "p-3 text-right", children: f.stock < 0 ? (_jsxs("span", { className: "tabular-nums font-semibold text-rose-600", title: "Er is meer uitgeleverd dan ingeboekt. Boek ontvangen of geretourneerde fusten bij.", children: ["Tekort van ", Math.abs(f.stock).toLocaleString("nl-NL"), " fusten"] })) : (_jsx("span", { className: `tabular-nums font-semibold ${f.minimum_stock > 0 && f.stock < f.minimum_stock
|
|
118
|
+
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "grid grid-cols-[minmax(0,1fr)_auto] items-start gap-4", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight truncate", children: "Fusten" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Alle fusttypen worden automatisch afgeleid uit de beladingen van je producten." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void sync(), disabled: syncing, children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${syncing ? "animate-spin" : ""}` }), "Synchroniseren"] })] }), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Fusten laden mislukt: ", error] })), floricodeError && (_jsxs("div", { className: "rounded-md border border-amber-500/40 bg-amber-500/10 p-3 text-sm", children: ["Floricode-fustcodes niet leesbaar: ", floricodeError, ". Alleen handmatig ingevoerde maten worden getoond."] })), negative.length > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive space-y-1", children: [_jsxs("p", { className: "font-medium", children: [negative.length, " fusttype", negative.length === 1 ? " heeft" : "n hebben", " een tekort."] }), _jsx("p", { className: "text-foreground/80", children: "Er is meer uitgeleverd dan ingeboekt. Controleer de ontvangsten en retouren, of boek de werkelijke telling bij." }), _jsx("ul", { className: "text-xs text-foreground/80", children: negative.map((n) => (_jsxs("li", { children: [n.name.trim(), ": tekort van ", n.shortage.toLocaleString("nl-NL"), " fusten", n.duplicate_names > 0 ? " (dit fusttype bestaat meerdere keren)" : ""] }, n.fust_type_id))) })] })), _jsxs("div", { className: "grid gap-4 sm:grid-cols-3", children: [_jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Fusttypen" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.types })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Totale fustvoorraad" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.stock })] }), _jsxs(Card, { className: "p-4", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Onder minimum" }), _jsx("p", { className: "mt-2 text-3xl font-bold tabular-nums", children: totals.belowMin })] })] }), _jsxs("div", { className: "relative max-w-sm", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { value: search, onChange: (e) => setSearch(e.target.value), placeholder: "Zoek fust\u2026", className: "pl-9" })] }), _jsx(Card, { className: "overflow-hidden", children: loading ? (_jsx("p", { className: "p-6 text-sm text-muted-foreground", children: "Fusten laden\u2026" })) : filtered.length === 0 ? (_jsxs("div", { className: "p-6 text-sm text-muted-foreground flex items-center gap-2", children: [_jsx(Boxes, { className: "h-4 w-4" }), "Nog geen fusttypen gevonden. Voeg een belading toe aan een product."] })) : (_jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-left", children: _jsxs("tr", { children: [_jsx("th", { className: "p-3", children: "Fust" }), _jsx("th", { className: "p-3", children: "Bron" }), _jsx("th", { className: "p-3", children: "Afmeting (L \u00D7 B \u00D7 H)" }), _jsx("th", { className: "p-3 text-right", children: "Gebruikt door" }), _jsx("th", { className: "p-3 text-right", children: "Voorraad" }), _jsx("th", { className: "p-3 text-right", children: "Minimum" }), _jsx("th", { className: "p-3 text-right", children: "Aanpassen" })] }) }), _jsx("tbody", { children: filtered.map((f) => (_jsxs("tr", { className: "border-t border-border align-top", children: [_jsxs("td", { className: "p-3", children: [_jsx("div", { className: "font-medium", children: f.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: f.vbn_code ? `VBN ${f.vbn_code}` : f.fust_key }), !f.in_use && (_jsx(Badge, { variant: "outline", className: "mt-1", children: "Niet meer in gebruik" })), _jsx("div", { className: "mt-1", children: _jsx(MutationHistory, { fust: f }) })] }), _jsx("td", { className: "p-3", children: f.source === "SUPPLIER" ? "Leverancier" : "VBN" }), _jsx("td", { className: "p-3 text-sm", children: _jsx(DimensionCell, { fust: f, floricode: floricode, onSave: (v) => patch(f.id, v) }) }), _jsx("td", { className: "p-3 text-right tabular-nums", children: f.used_by_count }), _jsx("td", { className: "p-3 text-right", children: f.stock < 0 ? (_jsxs("span", { className: "tabular-nums font-semibold text-rose-600", title: "Er is meer uitgeleverd dan ingeboekt. Boek ontvangen of geretourneerde fusten bij.", children: ["Tekort van ", Math.abs(f.stock).toLocaleString("nl-NL"), " fusten"] })) : (_jsx("span", { className: `tabular-nums font-semibold ${f.minimum_stock > 0 && f.stock < f.minimum_stock
|
|
111
119
|
? "text-rose-600"
|
|
112
120
|
: ""}`, children: f.stock })) }), _jsx("td", { className: "p-3 text-right", children: _jsx(Input, { type: "number", className: "h-8 w-28 text-right", defaultValue: f.minimum_stock, onBlur: (e) => {
|
|
113
121
|
const v = Math.max(0, Math.trunc(Number(e.target.value) || 0));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AAmBA,wBAAgB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AAmBA,wBAAgB,gBAAgB,gCAkO/B"}
|
|
@@ -13,15 +13,16 @@ import { supabase } from "../../integrations/supabase/client.js";
|
|
|
13
13
|
import { validateDraftForPublish } from "../../lib/floriday-import-validator.js";
|
|
14
14
|
import { ArtikelWizard } from "./ArtikelWizard.js";
|
|
15
15
|
import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
|
|
16
|
-
import {
|
|
16
|
+
import { LOCAL_STOCK_TEXT } from "../../lib/stock-scope.js";
|
|
17
17
|
import { FloridayConnectionNotice } from "./FloridayConnectionNotice.js";
|
|
18
18
|
const STEPS = ["Naam en code", "Voorraad", "Prijs"];
|
|
19
19
|
export function NieuwArtikelPage() {
|
|
20
20
|
const navigate = useNavigate();
|
|
21
21
|
const conn = useFloridayConnection();
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
const canSaveStock = conn.status
|
|
22
|
+
// Sinds 1.0.107 slaat set_product_stock zonder koppeling op in de lokale
|
|
23
|
+
// voorraadcontext; alleen tijdens het controleren van de koppeling staat het veld uit.
|
|
24
|
+
const canSaveStock = conn.status !== "laden";
|
|
25
|
+
const hasFloriday = conn.status === "actief";
|
|
25
26
|
const [full, setFull] = useState(false);
|
|
26
27
|
const [step, setStep] = useState(0);
|
|
27
28
|
const [name, setName] = useState("");
|
|
@@ -96,7 +97,7 @@ export function NieuwArtikelPage() {
|
|
|
96
97
|
if (createdId) {
|
|
97
98
|
const missing = validateDraftForPublish({}).missing;
|
|
98
99
|
const hasPrice = Number(price.replace(",", ".")) > 0;
|
|
99
|
-
return (_jsx("main", { className: "p-6 space-y-4 max-w-2xl", children: _jsxs(Card, { className: "p-5 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2 text-emerald-700", children: [_jsx(Check, { className: "h-5 w-5" }), _jsxs("p", { className: "font-medium", children: [name, " is aangemaakt."] })] }),
|
|
100
|
+
return (_jsx("main", { className: "p-6 space-y-4 max-w-2xl", children: _jsxs(Card, { className: "p-5 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2 text-emerald-700", children: [_jsx(Check, { className: "h-5 w-5" }), _jsxs("p", { className: "font-medium", children: [name, " is aangemaakt."] })] }), 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" }) })] })] }) }));
|
|
100
101
|
}
|
|
101
|
-
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:
|
|
102
|
+
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\")." })] })), 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(), 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" }))] }));
|
|
102
103
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StockBulkButtons.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockBulkButtons.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAgH3D,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,kBAAkB,EAClB,UAAU,GACX,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,+
|
|
1
|
+
{"version":3,"file":"StockBulkButtons.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockBulkButtons.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAgH3D,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,kBAAkB,EAClB,UAAU,GACX,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,+BAgrBA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { LOCAL_STOCK_TEXT } from "../../lib/stock-scope.js";
|
|
3
3
|
import { positiveIntOrNull, saveOwnPackings } from "../../lib/product-packings.js";
|
|
4
4
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
5
|
import * as XLSX from "xlsx";
|
|
@@ -202,10 +202,7 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
202
202
|
};
|
|
203
203
|
/** Stap 1: bestand lezen en per rij controleren (nog niets opgeslagen). */
|
|
204
204
|
const handleFile = async (file) => {
|
|
205
|
-
|
|
206
|
-
toast.error("Geen actieve Floriday-koppeling. Kijk bij Instellingen › Floriday.");
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
205
|
+
// Zonder koppeling importeert de database in de lokale voorraadcontext (1.0.107).
|
|
209
206
|
setBusy("lezen");
|
|
210
207
|
setBanner(null);
|
|
211
208
|
setPrepared(null);
|
|
@@ -578,7 +575,7 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
578
575
|
: banner?.kind === "success"
|
|
579
576
|
? "border-primary/40 bg-primary/5"
|
|
580
577
|
: "bg-muted/30";
|
|
581
|
-
return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", onClick: downloadTemplate, disabled: items.length === 0, children: [_jsx(FileDown, { className: "mr-2 h-4 w-4" }), "Template downloaden"] }), _jsxs(Button, { variant: "outline", onClick: () => setOpen(true),
|
|
578
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", onClick: downloadTemplate, disabled: items.length === 0, children: [_jsx(FileDown, { className: "mr-2 h-4 w-4" }), "Template downloaden"] }), _jsxs(Button, { variant: "outline", onClick: () => setOpen(true), title: activeConnectionId ? undefined : LOCAL_STOCK_TEXT, children: [_jsx(Upload, { className: "mr-2 h-4 w-4" }), "Voorraad importeren"] }), !activeConnectionId && (_jsx("span", { className: "text-xs text-muted-foreground", children: LOCAL_STOCK_TEXT })), _jsx(Dialog, { open: open, onOpenChange: (o) => !busy && setOpen(o), children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Voorraad importeren" }), _jsxs(DialogDescription, { children: ["Upload een Excel-bestand (op basis van de template). Bestaande artikelen worden gematcht op", " ", _jsx("strong", { children: "Artikelcode" }), "; liggende, minimum- en inkomende voorraad worden bijgewerkt. Gereserveerde voorraad wordt nooit ge\u00EFmporteerd. Nieuwe artikelcodes worden als concept aangemaakt en niet naar Floriday gepubliceerd."] })] }), _jsxs("div", { className: "space-y-4", children: [_jsx("input", { ref: fileRef, type: "file", accept: ".xlsx,.xls", className: "block w-full text-sm file:mr-3 file:rounded-md file:border-0 file:bg-primary file:px-3 file:py-2 file:text-primary-foreground hover:file:bg-primary/90 disabled:opacity-50", disabled: !!busy, onChange: (e) => {
|
|
582
579
|
const f = e.target.files?.[0];
|
|
583
580
|
if (f)
|
|
584
581
|
void handleFile(f);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StockSyncStatus.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockSyncStatus.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"StockSyncStatus.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockSyncStatus.tsx"],"names":[],"mappings":"AAeA,wBAAgB,eAAe,uCAwH9B"}
|
|
@@ -10,6 +10,9 @@ import { useServerFn } from "@tanstack/react-start";
|
|
|
10
10
|
import { toast } from "sonner";
|
|
11
11
|
import { useStockSyncHealth } from "../../hooks/useStockSyncHealth.js";
|
|
12
12
|
import { retryFailedStockSync } from "../../lib/stock-sync.functions.js";
|
|
13
|
+
import { Link } from "@tanstack/react-router";
|
|
14
|
+
import { useFloridayWriteMode } from "../../hooks/useFloridayWriteMode.js";
|
|
15
|
+
import { gateBlockedText, waitCauseText } from "../../lib/floriday-write-mode.js";
|
|
13
16
|
export function StockSyncStatus() {
|
|
14
17
|
const { data } = useStockSyncHealth();
|
|
15
18
|
const queryClient = useQueryClient();
|
|
@@ -24,11 +27,16 @@ export function StockSyncStatus() {
|
|
|
24
27
|
},
|
|
25
28
|
onError: (e) => toast.error(e?.message ?? "Opnieuw proberen is niet gelukt."),
|
|
26
29
|
});
|
|
30
|
+
const showCause = Boolean(data && (data.stalePending > 0 || data.blocked.length > 0 || data.blockedWithoutProduct > 0));
|
|
31
|
+
const writeMode = useFloridayWriteMode(showCause);
|
|
27
32
|
if (!data)
|
|
28
33
|
return null;
|
|
29
34
|
const { failed, stalePending, oldestPendingAt, topReason, articleCount, noPriceCount } = data;
|
|
30
|
-
|
|
35
|
+
const blocked = data.blocked;
|
|
36
|
+
const blockedCount = blocked.length + data.blockedWithoutProduct;
|
|
37
|
+
if (failed === 0 && stalePending === 0 && blockedCount === 0)
|
|
31
38
|
return null;
|
|
39
|
+
const cause = showCause ? waitCauseText(writeMode) : null;
|
|
32
40
|
const parts = [];
|
|
33
41
|
if (failed > 0) {
|
|
34
42
|
parts.push(`${failed} voorraadwijziging${failed === 1 ? "" : "en"} mislukt`);
|
|
@@ -36,5 +44,5 @@ export function StockSyncStatus() {
|
|
|
36
44
|
if (stalePending > 0) {
|
|
37
45
|
parts.push(`${stalePending} wacht${stalePending === 1 ? "" : "en"} langer dan 10 minuten`);
|
|
38
46
|
}
|
|
39
|
-
return (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { className: "flex-1 space-y-0.5", children: [_jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Voorraadsync naar Floriday: " }), parts.join(" · "), " (laatste 24 uur), voor ", articleCount, " artikel", articleCount === 1 ? "" : "en", ".", oldestPendingAt && (_jsxs("span", { className: "opacity-80", children: [" ", "Oudste wachtende regel: ", new Date(oldestPendingAt).toLocaleString("nl-NL"), "."] }))] }), topReason && (_jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Meest voorkomende reden: " }), topReason, "."] })), noPriceCount > 0 && (_jsxs("div", { className: "opacity-90", children: [noPriceCount, " van deze artikelen ", noPriceCount === 1 ? "heeft" : "hebben", " nog geen prijs in het weekaanbod; het aantal wordt dan zonder prijs doorgezet."] }))] }), failed > 0 && (_jsxs("button", { type: "button", className: "inline-flex shrink-0 items-center rounded-md border border-amber-500/40 bg-amber-500/10 px-2 py-1 text-xs font-medium text-amber-900 hover:bg-amber-500/20 disabled:opacity-50", disabled: mutation.isPending, onClick: () => mutation.mutate(), children: [_jsx(RotateCw, { className: `mr-1 h-3.5 w-3.5 ${mutation.isPending ? "animate-spin" : ""}` }), "Opnieuw proberen"] }))] }));
|
|
47
|
+
return (_jsxs("div", { className: "flex items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { className: "flex-1 space-y-0.5", children: [parts.length > 0 && (_jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Voorraadsync naar Floriday: " }), parts.join(" · "), " (laatste 24 uur), voor ", articleCount, " artikel", articleCount === 1 ? "" : "en", ".", oldestPendingAt && (_jsxs("span", { className: "opacity-80", children: [" ", "Oudste wachtende regel: ", new Date(oldestPendingAt).toLocaleString("nl-NL"), "."] }))] })), cause && (_jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Oorzaak: " }), cause] })), blockedCount > 0 && (_jsxs("div", { className: "space-y-0.5", children: [_jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Wacht op Floriday \u2013 verpakkingsverschil: " }), blockedCount, " regel", blockedCount === 1 ? "" : "s", " tegengehouden."] }), _jsxs("ul", { className: "list-disc pl-5 text-xs", children: [blocked.map((b) => (_jsx("li", { children: _jsx(Link, { to: "/voorraad/$id", params: { id: b.productId }, className: "underline hover:no-underline", children: gateBlockedText({ name: b.name, code: b.code }) }) }, b.productId))), data.blockedWithoutProduct > 0 && (_jsxs("li", { children: [data.blockedWithoutProduct, " regel", data.blockedWithoutProduct === 1 ? "" : "s", " zonder bekend artikel"] }))] })] })), topReason && (_jsxs("div", { children: [_jsx("span", { className: "font-medium", children: "Meest voorkomende reden: " }), topReason, "."] })), noPriceCount > 0 && (_jsxs("div", { className: "opacity-90", children: [noPriceCount, " van deze artikelen ", noPriceCount === 1 ? "heeft" : "hebben", " nog geen prijs in het weekaanbod; het aantal wordt dan zonder prijs doorgezet."] }))] }), failed > 0 && (_jsxs("button", { type: "button", className: "inline-flex shrink-0 items-center rounded-md border border-amber-500/40 bg-amber-500/10 px-2 py-1 text-xs font-medium text-amber-900 hover:bg-amber-500/20 disabled:opacity-50", disabled: mutation.isPending, onClick: () => mutation.mutate(), children: [_jsx(RotateCw, { className: `mr-1 h-3.5 w-3.5 ${mutation.isPending ? "animate-spin" : ""}` }), "Opnieuw proberen"] }))] }));
|
|
40
48
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"AAgBA,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":"AAgBA,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,sCA4MzF"}
|
|
@@ -5,7 +5,7 @@ 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
7
|
import { FloridaySentCard } from "./FloridaySentCard.js";
|
|
8
|
-
import {
|
|
8
|
+
import { LOCAL_STOCK_TEXT } from "../../lib/stock-scope.js";
|
|
9
9
|
export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }) {
|
|
10
10
|
const keys = stockKeysFor({
|
|
11
11
|
tradeItemId,
|
|
@@ -89,8 +89,10 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
|
|
|
89
89
|
return raw;
|
|
90
90
|
}
|
|
91
91
|
};
|
|
92
|
+
// Opslaan via set_product_stock werkt ook zonder koppeling (lokale context, 1.0.107).
|
|
93
|
+
const canWrite = !!productId || !!connectionId;
|
|
92
94
|
const handleSave = async () => {
|
|
93
|
-
if (!
|
|
95
|
+
if (!canWrite)
|
|
94
96
|
return;
|
|
95
97
|
setSaving(true);
|
|
96
98
|
try {
|
|
@@ -104,9 +106,9 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
|
|
|
104
106
|
setSaving(false);
|
|
105
107
|
}
|
|
106
108
|
};
|
|
107
|
-
const numField = (id, label) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: id, children: label }), _jsx(Input, { id: id, type: "number", min: 0, disabled: !
|
|
109
|
+
const numField = (id, label) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: id, children: label }), _jsx(Input, { id: id, type: "number", min: 0, disabled: !canWrite, value: String(values[id] ?? 0), onChange: (e) => {
|
|
108
110
|
setTouched(true);
|
|
109
111
|
setValues((v) => ({ ...v, [id]: Number(e.target.value) || 0 }));
|
|
110
112
|
} })] }));
|
|
111
|
-
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"), numField("reserved_stock", "Gereserveerd"), numField("incoming_stock", "Inkomend")] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "stock-notes", children: "Notitie (optioneel)" }), _jsx(Textarea, { id: "stock-notes", rows: 2, value: values.notes ?? "", disabled: !
|
|
113
|
+
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"), numField("reserved_stock", "Gereserveerd"), numField("incoming_stock", "Inkomend")] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "stock-notes", children: "Notitie (optioneel)" }), _jsx(Textarea, { id: "stock-notes", rows: 2, value: values.notes ?? "", disabled: !canWrite, onChange: (e) => setValues((v) => ({ ...v, notes: e.target.value })), placeholder: "Bijv. locatie of opmerking" })] }), _jsxs("div", { className: "flex justify-end", children: [!connectionId && (_jsx("span", { className: "mr-auto self-center text-xs text-muted-foreground", children: LOCAL_STOCK_TEXT })), _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(FloridaySentCard, { connectionId: connectionId, productId: productId, tradeItemId: tradeItemId, productType: isGroupHead ? "GROUP" : hasBom ? "SET" : null })] }));
|
|
112
114
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAgFzE,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,EACP,aAAa,EACb,QAAQ,EACR,cAAc,EACd,WAAW,EACX,cAAc,GACf,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,+BAmUA"}
|
|
@@ -8,6 +8,8 @@ import { useVoorraadCustomFields } from "../../hooks/useVoorraadCustomFields.js"
|
|
|
8
8
|
import { formatCustomValue } from "../../lib/voorraad-custom-fields.js";
|
|
9
9
|
import { InventoryStatusBadge } from "./InventoryStatusBadge.js";
|
|
10
10
|
import { loadIncompletePackingIds, PACKING_INCOMPLETE_TEXT } from "../../lib/product-packings.js";
|
|
11
|
+
import { useFloridayWriteMode } from "../../hooks/useFloridayWriteMode.js";
|
|
12
|
+
import { waitCauseText } from "../../lib/floriday-write-mode.js";
|
|
11
13
|
import { PRODUCT_TYPE_CLASSES, PRODUCT_TYPE_HELP, PRODUCT_TYPE_LABELS, toStructureType } from "../../lib/product-types.js";
|
|
12
14
|
const columns = [
|
|
13
15
|
{ key: "quantity", label: "Liggend" },
|
|
@@ -90,6 +92,9 @@ export function settingKeyOf(item) {
|
|
|
90
92
|
}
|
|
91
93
|
export function VoorraadTable({ items, loading, settingsByKey, selected, onToggleSelect, onToggleAll, onOpenSettings, }) {
|
|
92
94
|
const navigate = useNavigate();
|
|
95
|
+
// Oorzaak naast "Wacht op Floriday" (alleen lezen, alleen als er zo'n melding is).
|
|
96
|
+
const anyWaiting = items.some((i) => Boolean(customValue(i, "floriday_sync")?.dirty));
|
|
97
|
+
const waitCause = waitCauseText(useFloridayWriteMode(anyWaiting));
|
|
93
98
|
// Artikelen zonder volledige primaire verpakking (karindeling kan ze niet plannen).
|
|
94
99
|
const [incompletePacking, setIncompletePacking] = useState(new Set());
|
|
95
100
|
const productIdsKey = items.map((i) => i.productId ?? "").join(",");
|
|
@@ -155,7 +160,7 @@ export function VoorraadTable({ items, loading, settingsByKey, selected, onToggl
|
|
|
155
160
|
: "Niet online in Floriday", title: floriday
|
|
156
161
|
? "Online in Floriday"
|
|
157
162
|
: "Niet online in Floriday", className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-background px-2 py-0.5 text-[11px] font-medium leading-none text-muted-foreground", children: [_jsx("span", { className: "h-1.5 w-1.5 rounded-full " +
|
|
158
|
-
(floriday ? "bg-emerald-500" : "bg-red-500"), "aria-hidden": true }), _jsx("span", { children: "Floriday" })] })] }), Boolean(customValue(item, "floriday_sync")?.dirty) && (
|
|
163
|
+
(floriday ? "bg-emerald-500" : "bg-red-500"), "aria-hidden": true }), _jsx("span", { children: "Floriday" })] })] }), Boolean(customValue(item, "floriday_sync")?.dirty) && (_jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-1.5", children: [_jsx("div", { className: "inline-flex items-center rounded-full bg-amber-100 px-2 py-0.5 text-[11px] font-medium leading-none text-amber-800", title: "Opgeslagen in dit systeem, wacht nog op doorgifte naar Floriday", children: "Wacht op Floriday" }), waitCause && (_jsx("span", { className: "text-[11px] text-muted-foreground", children: waitCause }))] })), low && (_jsx("div", { className: "text-xs text-destructive", children: "onder minimumvoorraad" })), overReserved && (_jsx("div", { className: "text-xs text-amber-700", title: "Er is meer gereserveerd voor orders dan er ligt. Controleer open orders.", children: "gereserveerd groter dan liggend" }))] }), _jsx("td", { className: "p-3", onClick: (e) => e.stopPropagation(), children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(InventoryStatusBadge, { setting: setting }), settingProductId && onOpenSettings && (_jsx(Button, { variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => onOpenSettings(settingProductId), "aria-label": `Voorraadsturing van ${item.name}`, children: _jsx(Settings2, { className: "h-4 w-4" }) }))] }) }), showType && _jsx("td", { className: "p-3", children: (() => {
|
|
159
164
|
const t = toStructureType(setting?.product_type, item.itemType);
|
|
160
165
|
return (_jsxs("div", { className: "flex flex-col items-start gap-1", children: [_jsx(Badge, { variant: t === "NORMAL" ? "secondary" : undefined, className: PRODUCT_TYPE_CLASSES[t], title: PRODUCT_TYPE_HELP[t], children: PRODUCT_TYPE_LABELS[t] }), setting?.action_reason === "GROUP_MEMBER_OFF" && (_jsx("span", { className: "text-[11px] text-muted-foreground", title: "Dit artikel is lid van een actieve productgroep; alleen de groep staat in Floriday.", children: "Lid van groep \u00B7 niet los in Floriday" })), setting?.action_reason === "SET_AWAITS_LOGISTICS" && (item.floridayId ? (_jsx("span", { className: "text-[11px] text-amber-700", title: "Set: dit artikel staat in Floriday. De voorraad (berekend uit de onderdelen) gaat pas naar Floriday na akkoord, als de Logistieke engine onderdelen reserveert.", children: "In Floriday \u00B7 voorraad van de samenstelling volgt" })) : (_jsx("span", { className: "text-[11px] text-amber-700", title: "Berekend uit de onderdelen; nog niet gekoppeld aan een Floriday-artikel.", children: "Berekend \u00B7 nog niet gekoppeld aan Floriday" })))] }));
|
|
161
166
|
})() }), showArticle && _jsx("td", { className: "p-3 font-mono text-xs", children: item.articleNumber || "—" }), shownColumns.map((c) => (_jsx("td", { className: "p-3 text-right tabular-nums text-muted-foreground", children: item.stockMissing && item.itemType !== "productgroep"
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type FloridayWriteMode } from "../lib/floriday-write-mode.js";
|
|
2
|
+
/**
|
|
3
|
+
* Schrijfstand van Floriday. Geeft undefined tijdens laden, null als lezen
|
|
4
|
+
* mislukt (dan toont het scherm "oorzaak onbekend"), anders de stand.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useFloridayWriteMode(enabled?: boolean): FloridayWriteMode | null | undefined;
|
|
7
|
+
//# sourceMappingURL=useFloridayWriteMode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useFloridayWriteMode.d.ts","sourceRoot":"","sources":["../../src/hooks/useFloridayWriteMode.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,KAAK,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAE5F;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,UAAO,GAAG,iBAAiB,GAAG,IAAI,GAAG,SAAS,CAYzF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import { fetchFloridayWriteMode } from "../lib/floriday-write-mode.js";
|
|
3
|
+
/**
|
|
4
|
+
* Schrijfstand van Floriday. Geeft undefined tijdens laden, null als lezen
|
|
5
|
+
* mislukt (dan toont het scherm "oorzaak onbekend"), anders de stand.
|
|
6
|
+
*/
|
|
7
|
+
export function useFloridayWriteMode(enabled = true) {
|
|
8
|
+
const q = useQuery({
|
|
9
|
+
queryKey: ["floriday-write-mode"],
|
|
10
|
+
queryFn: fetchFloridayWriteMode,
|
|
11
|
+
enabled,
|
|
12
|
+
staleTime: 60000,
|
|
13
|
+
refetchOnWindowFocus: true,
|
|
14
|
+
retry: 1,
|
|
15
|
+
});
|
|
16
|
+
if (!enabled)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (q.isError)
|
|
19
|
+
return null;
|
|
20
|
+
return q.data;
|
|
21
|
+
}
|
|
@@ -9,6 +9,15 @@ export interface StockSyncHealth {
|
|
|
9
9
|
articleCount: number;
|
|
10
10
|
/** Aantal van die artikelen zonder prijs in het weekaanbod. */
|
|
11
11
|
noPriceCount: number;
|
|
12
|
+
/** Artikelen met regels die door het verpakkingsverschil zijn tegengehouden. */
|
|
13
|
+
blocked: BlockedArticle[];
|
|
14
|
+
/** Tegengehouden regels zonder bekend artikel. */
|
|
15
|
+
blockedWithoutProduct: number;
|
|
16
|
+
}
|
|
17
|
+
export interface BlockedArticle {
|
|
18
|
+
productId: string;
|
|
19
|
+
name: string | null;
|
|
20
|
+
code: string | null;
|
|
12
21
|
}
|
|
13
22
|
export declare function reasonLabel(error: string | null | undefined): string | null;
|
|
14
23
|
export declare function useStockSyncHealth(): import("@tanstack/react-query").UseQueryResult<StockSyncHealth, Error>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useStockSyncHealth.d.ts","sourceRoot":"","sources":["../../src/hooks/useStockSyncHealth.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"useStockSyncHealth.d.ts","sourceRoot":"","sources":["../../src/hooks/useStockSyncHealth.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,4DAA4D;IAC5D,YAAY,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,kDAAkD;IAClD,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAS3E;AAED,wBAAgB,kBAAkB,2EAuGjC"}
|
|
@@ -88,6 +88,27 @@ export function useStockSyncHealth() {
|
|
|
88
88
|
const withPrice = new Set((priced ?? []).map((p) => p.trade_item_id));
|
|
89
89
|
noPriceCount = ids.filter((id) => !withPrice.has(id)).length;
|
|
90
90
|
}
|
|
91
|
+
// Regels tegengehouden door het verpakkingsverschil (alleen lezen).
|
|
92
|
+
const { data: gateRows } = await supabase
|
|
93
|
+
.from("floriday_continuous_stock_queue")
|
|
94
|
+
.select("product_id, trade_item_id")
|
|
95
|
+
.eq("status", "blocked_by_gate")
|
|
96
|
+
.limit(500);
|
|
97
|
+
const gateProductIds = Array.from(new Set((gateRows ?? []).map((r) => r.product_id).filter(Boolean)));
|
|
98
|
+
const gateNoProduct = (gateRows ?? []).filter((r) => !r.product_id).length;
|
|
99
|
+
let blocked = [];
|
|
100
|
+
if (gateProductIds.length > 0) {
|
|
101
|
+
const { data: prods } = await supabase
|
|
102
|
+
.from("products")
|
|
103
|
+
.select("id, product, article_number")
|
|
104
|
+
.in("id", gateProductIds.slice(0, 200));
|
|
105
|
+
const byId = new Map((prods ?? []).map((p) => [p.id, p]));
|
|
106
|
+
blocked = gateProductIds.slice(0, 200).map((id) => ({
|
|
107
|
+
productId: id,
|
|
108
|
+
name: byId.get(id)?.product ?? null,
|
|
109
|
+
code: byId.get(id)?.article_number ?? null,
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
91
112
|
return {
|
|
92
113
|
failed,
|
|
93
114
|
pending,
|
|
@@ -96,6 +117,8 @@ export function useStockSyncHealth() {
|
|
|
96
117
|
topReason,
|
|
97
118
|
articleCount: ids.length,
|
|
98
119
|
noPriceCount,
|
|
120
|
+
blocked,
|
|
121
|
+
blockedWithoutProduct: gateNoProduct,
|
|
99
122
|
};
|
|
100
123
|
},
|
|
101
124
|
});
|
|
@@ -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;;;mBA0GR,iBAAiB;;;EA4FnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,
|
|
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;;;mBA0GR,iBAAiB;;;EA4FnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAuBzC"}
|
|
@@ -237,18 +237,28 @@ export function useTradeItemStock(connectionId, keyOrKeys, productId) {
|
|
|
237
237
|
return { row: row ?? EMPTY, loading, save, error, resolvedKey };
|
|
238
238
|
}
|
|
239
239
|
export async function fetchStockMap(connectionId) {
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
|
|
240
|
+
// Met koppeling: alleen de rijen van die koppeling. Zonder koppeling: de
|
|
241
|
+
// lokale voorraadcontext (1.0.107, één vaste rij in voorraad_local_context).
|
|
242
|
+
// Nooit rijen van andere koppelingen/omgevingen.
|
|
243
|
+
let scopeId = connectionId;
|
|
244
|
+
if (!scopeId) {
|
|
245
|
+
const { data: lc, error: lcErr } = await supabase
|
|
246
|
+
.from("voorraad_local_context")
|
|
247
|
+
.select("id")
|
|
248
|
+
.maybeSingle();
|
|
249
|
+
if (lcErr)
|
|
250
|
+
throw new Error(lcErr.message);
|
|
251
|
+
scopeId = lc?.id ?? null;
|
|
252
|
+
if (!scopeId)
|
|
253
|
+
return new Map(); // nog nooit eigen voorraad opgeslagen
|
|
254
|
+
}
|
|
255
|
+
const { data, error } = await supabase
|
|
243
256
|
.from("trade_item_stock")
|
|
244
|
-
.select("connection_id,trade_item_key,physical_stock,minimum_stock,reserved_stock,incoming_stock,notes,updated_at")
|
|
245
|
-
|
|
246
|
-
? query.or(`connection_id.is.null,connection_id.eq.${connectionId}`)
|
|
247
|
-
: query.is("connection_id", null);
|
|
248
|
-
const { data, error } = await query;
|
|
257
|
+
.select("connection_id,trade_item_key,physical_stock,minimum_stock,reserved_stock,incoming_stock,notes,updated_at")
|
|
258
|
+
.eq("connection_id", scopeId);
|
|
249
259
|
if (error)
|
|
250
260
|
throw new Error(error.message);
|
|
251
|
-
const scoped = buildScopedStockMap((data ?? []),
|
|
261
|
+
const scoped = buildScopedStockMap((data ?? []), scopeId);
|
|
252
262
|
const map = new Map();
|
|
253
263
|
for (const [k, r] of scoped)
|
|
254
264
|
map.set(k, r);
|