@flowselections/floriday-voorraad 1.0.48 → 1.0.49
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/ArtikelCompositionCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelCompositionCard.js +79 -6
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelWizard.js +14 -2
- package/dist-lib/components/voorraad/CollapsibleText.d.ts +9 -0
- package/dist-lib/components/voorraad/CollapsibleText.d.ts.map +1 -0
- package/dist-lib/components/voorraad/CollapsibleText.js +32 -0
- package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockMutationHistoryCard.js +2 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts +1 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.js +29 -42
- package/dist-lib/hooks/useTradeItemStock.d.ts +3 -1
- package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemStock.js +85 -32
- package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
- package/dist-lib/hooks/useVoorraadData.js +3 -2
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +340 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +340 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +340 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +348 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/floricode-required-features.functions.d.ts +340 -0
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts +340 -0
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-env.functions.d.ts +680 -0
- package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.d.ts +2040 -0
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts +680 -0
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +1020 -0
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.d.ts +680 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/inventory-settings.functions.d.ts +680 -0
- package/dist-lib/lib/inventory-settings.functions.d.ts.map +1 -1
- package/dist-lib/lib/product-packing.d.ts.map +1 -1
- package/dist-lib/lib/product-packing.js +10 -4
- package/dist-lib/lib/stock-sync.functions.d.ts +680 -0
- package/dist-lib/lib/stock-sync.functions.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArtikelCompositionCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelCompositionCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ArtikelCompositionCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelCompositionCard.tsx"],"names":[],"mappings":"AAMA,UAAU,KAAK;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAkHD,wBAAgB,sBAAsB,CAAC,EAAE,SAAS,EAAE,UAAU,EAAE,EAAE,KAAK,sCA6RtE"}
|
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { toast } from "sonner";
|
|
3
4
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Badge } from "@flowselections/core";
|
|
4
|
-
import { Loader2, Package } from "lucide-react";
|
|
5
|
+
import { ArrowDown, ArrowUp, Loader2, Package } from "lucide-react";
|
|
5
6
|
import { supabase } from "../../integrations/supabase/client";
|
|
7
|
+
async function loadStock(productIds, floridayIds) {
|
|
8
|
+
const out = new Map();
|
|
9
|
+
const idToProduct = new Map();
|
|
10
|
+
productIds.forEach((id) => idToProduct.set(id, id));
|
|
11
|
+
if (floridayIds.length) {
|
|
12
|
+
const { data } = await supabase
|
|
13
|
+
.from("products")
|
|
14
|
+
.select("id,floriday_trade_item_id")
|
|
15
|
+
.in("floriday_trade_item_id", floridayIds);
|
|
16
|
+
(Array.isArray(data) ? data : []).forEach((p) => {
|
|
17
|
+
if (p.floriday_trade_item_id)
|
|
18
|
+
idToProduct.set(String(p.floriday_trade_item_id), String(p.id));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
await Promise.all([...idToProduct.entries()].map(async ([ref, pid]) => {
|
|
22
|
+
const { data, error } = await supabase.rpc("get_product_stock", { p_product_id: pid });
|
|
23
|
+
if (error)
|
|
24
|
+
return;
|
|
25
|
+
const st = Array.isArray(data) ? data[0] : null;
|
|
26
|
+
if (!st) {
|
|
27
|
+
if (!out.has(ref))
|
|
28
|
+
out.set(ref, null);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const physical = Number(st.physical_stock ?? 0);
|
|
32
|
+
const reserved = Number(st.reserved_stock ?? 0);
|
|
33
|
+
out.set(ref, { physical, reserved, available: physical - reserved });
|
|
34
|
+
}));
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
6
37
|
async function resolveNames(productIds, floridayIds) {
|
|
7
38
|
const resolved = new Map();
|
|
8
39
|
const uuidLike = [...productIds, ...floridayIds].filter((v) => /^[0-9a-f-]{36}$/i.test(v));
|
|
@@ -57,15 +88,20 @@ async function resolveNames(productIds, floridayIds) {
|
|
|
57
88
|
export function ArtikelCompositionCard({ productId, floridayId }) {
|
|
58
89
|
const [loading, setLoading] = useState(true);
|
|
59
90
|
const [sections, setSections] = useState([]);
|
|
91
|
+
const [reloadTick, setReloadTick] = useState(0);
|
|
92
|
+
const [savingOrder, setSavingOrder] = useState(false);
|
|
60
93
|
useEffect(() => {
|
|
61
94
|
let cancelled = false;
|
|
62
95
|
(async () => {
|
|
63
|
-
|
|
96
|
+
if (reloadTick === 0)
|
|
97
|
+
setLoading(true);
|
|
64
98
|
const out = [];
|
|
65
99
|
// ---- Productgroep ----
|
|
66
100
|
let groupQ = supabase
|
|
67
101
|
.from("product_groups")
|
|
68
102
|
.select("id,name,description")
|
|
103
|
+
.eq("is_active", true)
|
|
104
|
+
.order("updated_at", { ascending: false })
|
|
69
105
|
.limit(1);
|
|
70
106
|
if (productId && floridayId) {
|
|
71
107
|
groupQ = groupQ.or(`head_product_id.eq.${productId},head_trade_item_floriday_id.eq.${floridayId}`);
|
|
@@ -92,7 +128,10 @@ export function ArtikelCompositionCard({ productId, floridayId }) {
|
|
|
92
128
|
const uuidIds = tradeIds.filter((v) => /^[0-9a-f-]{36}$/i.test(v));
|
|
93
129
|
const nonUuidIds = tradeIds.filter((v) => !/^[0-9a-f-]{36}$/i.test(v));
|
|
94
130
|
// uuid → could be either product uuid or a floriday uuid. Look both up.
|
|
95
|
-
const { resolved } = await
|
|
131
|
+
const [{ resolved }, stockMap] = await Promise.all([
|
|
132
|
+
resolveNames(uuidIds, [...uuidIds, ...nonUuidIds]),
|
|
133
|
+
loadStock(uuidIds, [...uuidIds, ...nonUuidIds]),
|
|
134
|
+
]);
|
|
96
135
|
const rows = items.map((i) => {
|
|
97
136
|
const id = String(i.trade_item_id);
|
|
98
137
|
const r = resolved.get(`floriday:${id}`) ??
|
|
@@ -105,9 +144,11 @@ export function ArtikelCompositionCard({ productId, floridayId }) {
|
|
|
105
144
|
code: r?.code,
|
|
106
145
|
quantity: i.quantity ?? undefined,
|
|
107
146
|
kind: "artikel",
|
|
147
|
+
stock: stockMap.has(id) ? stockMap.get(id) : null,
|
|
108
148
|
};
|
|
109
149
|
});
|
|
110
150
|
out.push({
|
|
151
|
+
groupId: String(group.id),
|
|
111
152
|
kind: "productgroep",
|
|
112
153
|
title: `Productgroep: ${group.name}`,
|
|
113
154
|
description: group.description || `${rows.length} artikelen in deze productgroep`,
|
|
@@ -184,7 +225,39 @@ export function ArtikelCompositionCard({ productId, floridayId }) {
|
|
|
184
225
|
return () => {
|
|
185
226
|
cancelled = true;
|
|
186
227
|
};
|
|
187
|
-
}, [productId, floridayId]);
|
|
228
|
+
}, [productId, floridayId, reloadTick]);
|
|
229
|
+
const groupId = sections.find((x) => x.kind === "productgroep")?.groupId;
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
if (!groupId)
|
|
232
|
+
return;
|
|
233
|
+
const channel = supabase
|
|
234
|
+
.channel(`pgi-${groupId}`)
|
|
235
|
+
.on("postgres_changes", { event: "*", schema: "public", table: "product_group_items", filter: `product_group_id=eq.${groupId}` }, () => setReloadTick((t) => t + 1))
|
|
236
|
+
.subscribe();
|
|
237
|
+
return () => {
|
|
238
|
+
supabase.removeChannel(channel);
|
|
239
|
+
};
|
|
240
|
+
}, [groupId]);
|
|
241
|
+
const move = useCallback(async (index, dir) => {
|
|
242
|
+
const sec = sections.find((x) => x.kind === "productgroep");
|
|
243
|
+
if (!sec)
|
|
244
|
+
return;
|
|
245
|
+
const target = index + dir;
|
|
246
|
+
if (target < 0 || target >= sec.rows.length)
|
|
247
|
+
return;
|
|
248
|
+
const prev = sections;
|
|
249
|
+
const rows = [...sec.rows];
|
|
250
|
+
[rows[index], rows[target]] = [rows[target], rows[index]];
|
|
251
|
+
setSections(sections.map((x) => (x === sec ? { ...x, rows } : x)));
|
|
252
|
+
setSavingOrder(true);
|
|
253
|
+
const results = await Promise.all(rows.map((r, i) => supabase.from("product_group_items").update({ sort_order: i }).eq("id", r.key)));
|
|
254
|
+
setSavingOrder(false);
|
|
255
|
+
const failed = results.find((r) => r.error);
|
|
256
|
+
if (failed?.error) {
|
|
257
|
+
setSections(prev);
|
|
258
|
+
toast.error(`Volgorde opslaan mislukt: ${failed.error.message}`);
|
|
259
|
+
}
|
|
260
|
+
}, [sections]);
|
|
188
261
|
if (loading) {
|
|
189
262
|
return (_jsx(Card, { children: _jsxs(CardContent, { className: "flex items-center gap-2 py-4 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Samenstelling laden\u2026"] }) }));
|
|
190
263
|
}
|
|
@@ -192,5 +265,5 @@ export function ArtikelCompositionCard({ productId, floridayId }) {
|
|
|
192
265
|
return null;
|
|
193
266
|
return (_jsx(_Fragment, { children: sections.map((s) => (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx(CardTitle, { children: s.title }), _jsx(Badge, { className: s.kind === "productgroep"
|
|
194
267
|
? "bg-indigo-500/15 text-indigo-700 border-indigo-500/30"
|
|
195
|
-
: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: s.kind === "productgroep" ? "Productgroep" : "Samenstelling" })] }), _jsx(CardDescription, { children: s.description })] }), _jsx(CardContent, { children: s.rows.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children: "Nog geen onderdelen toegevoegd." })) : (_jsx("ul", { className: "divide-y", children: s.rows.map((r) => (_jsxs("li", { className: "flex items-center gap-3 py-2", children: [_jsx("div", { className: "h-8 w-8 rounded bg-muted flex items-center justify-center shrink-0", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium text-sm truncate", children: r.name }), r.code && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: r.code })), r.detail && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: r.detail }))] }), r.quantity !== undefined && r.quantity !== null && (_jsxs("div", { className: "text-sm tabular-nums text-muted-foreground shrink-0", children: [r.quantity, r.unit ? ` ${r.unit}` : ""] }))] }, r.key))) })) })] }, s.kind))) }));
|
|
268
|
+
: "bg-emerald-500/15 text-emerald-700 border-emerald-500/30", children: s.kind === "productgroep" ? "Productgroep" : "Samenstelling" })] }), _jsx(CardDescription, { children: s.description })] }), _jsx(CardContent, { children: s.rows.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground py-4 text-center", children: "Nog geen onderdelen toegevoegd." })) : (_jsx("ul", { className: "divide-y", children: s.rows.map((r, idx) => (_jsxs("li", { className: "flex items-center gap-3 py-2", children: [s.kind === "productgroep" && (_jsxs("div", { className: "flex flex-col shrink-0", children: [_jsx("button", { type: "button", "aria-label": "Omhoog", disabled: idx === 0 || savingOrder, onClick: () => move(idx, -1), className: "p-0.5 text-muted-foreground hover:text-foreground disabled:opacity-30", children: _jsx(ArrowUp, { className: "h-3.5 w-3.5" }) }), _jsx("button", { type: "button", "aria-label": "Omlaag", disabled: idx === s.rows.length - 1 || savingOrder, onClick: () => move(idx, 1), className: "p-0.5 text-muted-foreground hover:text-foreground disabled:opacity-30", children: _jsx(ArrowDown, { className: "h-3.5 w-3.5" }) })] })), _jsx("div", { className: "h-8 w-8 rounded bg-muted flex items-center justify-center shrink-0", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "font-medium text-sm truncate", children: r.name }), r.code && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: r.code })), r.detail && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: r.detail }))] }), s.kind === "productgroep" && (_jsx("div", { className: "text-right shrink-0 w-40", children: r.stock ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "text-sm font-medium tabular-nums", children: ["Beschikbaar: ", r.stock.available] }), _jsxs("div", { className: "text-xs text-muted-foreground tabular-nums", children: ["fysiek ", r.stock.physical, " \u00B7 gereserveerd ", r.stock.reserved] })] })) : (_jsx("div", { className: "text-xs text-muted-foreground", children: "Nog geen voorraad" })) })), r.quantity !== undefined && r.quantity !== null && (_jsxs("div", { className: "text-sm tabular-nums text-muted-foreground shrink-0", title: s.kind === "productgroep" ? "Aantal in groep" : undefined, children: [r.quantity, r.unit ? ` ${r.unit}` : ""] }))] }, r.key))) })) })] }, s.kind))) }));
|
|
196
269
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA4MA,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,+
|
|
1
|
+
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA4MA,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,+BAkrEL"}
|
|
@@ -290,11 +290,23 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
290
290
|
setProductLoading(true);
|
|
291
291
|
(async () => {
|
|
292
292
|
try {
|
|
293
|
-
const
|
|
293
|
+
const baseCols = "id,product,barcode,article_number,quantity,image_url,plants_per_crate,crates_per_layer,layers_per_cart,trade_item_key,floriday_trade_item_id,custom_fields";
|
|
294
|
+
let { data: product, error } = await supabase
|
|
294
295
|
.from("products")
|
|
295
|
-
.select(
|
|
296
|
+
.select(`${baseCols},floriday_packing_confirmed_at,floriday_packing_source`)
|
|
296
297
|
.eq("id", productId)
|
|
297
298
|
.maybeSingle();
|
|
299
|
+
// Andere omgevingen hebben de beladingskolommen mogelijk nog niet:
|
|
300
|
+
// dan zonder die kolommen laden (belading wordt dan "onbekend").
|
|
301
|
+
if (error && /floriday_packing_/.test(error.message ?? "")) {
|
|
302
|
+
const retry = await supabase
|
|
303
|
+
.from("products")
|
|
304
|
+
.select(baseCols)
|
|
305
|
+
.eq("id", productId)
|
|
306
|
+
.maybeSingle();
|
|
307
|
+
product = retry.data;
|
|
308
|
+
error = retry.error;
|
|
309
|
+
}
|
|
298
310
|
if (cancelled)
|
|
299
311
|
return;
|
|
300
312
|
if (error)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
text: string | null | undefined;
|
|
3
|
+
/** Maximum zichtbare regels wanneer ingeklapt (standaard 5). */
|
|
4
|
+
lines?: number;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function CollapsibleText({ text, lines, className }: Props): import("react").JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=CollapsibleText.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CollapsibleText.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/CollapsibleText.tsx"],"names":[],"mappings":"AAOA,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAChC,gEAAgE;IAChE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,KAAS,EAAE,SAAS,EAAE,EAAE,KAAK,+BAmDpE"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Lange tekstvelden die meer dan ~5 regels beslaan kunnen worden ingeklapt.
|
|
4
|
+
* Kortere tekst wordt gewoon volledig getoond, zonder knop.
|
|
5
|
+
*/
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
|
+
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
8
|
+
export function CollapsibleText({ text, lines = 5, className }) {
|
|
9
|
+
const value = (text ?? "").trim();
|
|
10
|
+
const ref = useRef(null);
|
|
11
|
+
const [expanded, setExpanded] = useState(false);
|
|
12
|
+
const [overflowing, setOverflowing] = useState(false);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const el = ref.current;
|
|
15
|
+
if (!el)
|
|
16
|
+
return;
|
|
17
|
+
const check = () => {
|
|
18
|
+
// Alleen meten zolang de tekst ingeklapt is; uitgeklapt past hij immers.
|
|
19
|
+
if (!expanded)
|
|
20
|
+
setOverflowing(el.scrollHeight > el.clientHeight + 1);
|
|
21
|
+
};
|
|
22
|
+
check();
|
|
23
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
24
|
+
const ro = new ResizeObserver(check);
|
|
25
|
+
ro.observe(el);
|
|
26
|
+
return () => ro.disconnect();
|
|
27
|
+
}
|
|
28
|
+
}, [value, expanded]);
|
|
29
|
+
if (!value)
|
|
30
|
+
return _jsx("span", { className: "text-muted-foreground", children: "\u2014" });
|
|
31
|
+
return (_jsxs("div", { className: className, children: [_jsx("p", { ref: ref, className: "break-words", style: expanded ? undefined : { WebkitLineClamp: lines, display: "-webkit-box", WebkitBoxOrient: "vertical", overflow: "hidden" }, children: value }), (overflowing || expanded) && (_jsx("button", { type: "button", onClick: () => setExpanded((v) => !v), className: "mt-1 inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline", children: expanded ? (_jsxs(_Fragment, { children: [_jsx(ChevronUp, { className: "h-3.5 w-3.5" }), " Inklappen"] })) : (_jsxs(_Fragment, { children: [_jsx(ChevronDown, { className: "h-3.5 w-3.5" }), " Meer tonen"] })) }))] }));
|
|
32
|
+
}
|
|
@@ -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":"AAWA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AA8CD,wBAAgB,wBAAwB,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,KAAK,+BAmM1E"}
|
|
@@ -8,6 +8,7 @@ import { useEffect, useState } from "react";
|
|
|
8
8
|
import { Card, CardContent, Badge } from "@flowselections/core";
|
|
9
9
|
import { Loader2, History } from "lucide-react";
|
|
10
10
|
import { supabase } from "../../integrations/supabase/client";
|
|
11
|
+
import { CollapsibleText } from "./CollapsibleText";
|
|
11
12
|
const TYPE_LABEL = {
|
|
12
13
|
consumption: { label: "Afboeking (verkoop)", tone: "bg-rose-100 text-rose-800 border-rose-200" },
|
|
13
14
|
consumption_blocked: { label: "Afboeking geblokkeerd", tone: "bg-amber-100 text-amber-800 border-amber-200" },
|
|
@@ -129,6 +130,6 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
|
129
130
|
const resChanged = r.reserved_before !== null &&
|
|
130
131
|
r.reserved_after !== null &&
|
|
131
132
|
r.reserved_before !== r.reserved_after;
|
|
132
|
-
return (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "whitespace-nowrap px-3 py-2", children: formatMoment(r.created_at) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: "outline", className: `border ${meta.tone}`, children: meta.label }) }), _jsxs("td", { className: `px-3 py-2 text-right tabular-nums font-medium ${r.qty_delta < 0 ? "text-rose-700" : r.qty_delta > 0 ? "text-emerald-700" : ""}`, children: [r.qty_delta > 0 ? "+" : "", r.qty_delta] }), _jsxs("td", { className: "whitespace-nowrap px-3 py-2 tabular-nums text-muted-foreground", children: [physChanged && (_jsxs("div", { children: ["Liggend ", r.physical_before, " \u2192 ", r.physical_after] })), resChanged && (_jsxs("div", { children: ["Gereserveerd ", r.reserved_before, " \u2192 ", r.reserved_after] })), !physChanged && !resChanged && _jsx("span", { children: "\u2014" })] }), _jsx("td", { className: "whitespace-nowrap px-3 py-2", children: r.order_id ? (_jsx("a", { href: `/floriday-verkoop/verkooporders/${r.order_id}`, className: "text-primary hover:underline", children: r.order_number ?? "Order" })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) }), _jsx("td", { className: "px-3 py-2 text-muted-foreground", children: r.reason
|
|
133
|
+
return (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "whitespace-nowrap px-3 py-2", children: formatMoment(r.created_at) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: "outline", className: `border ${meta.tone}`, children: meta.label }) }), _jsxs("td", { className: `px-3 py-2 text-right tabular-nums font-medium ${r.qty_delta < 0 ? "text-rose-700" : r.qty_delta > 0 ? "text-emerald-700" : ""}`, children: [r.qty_delta > 0 ? "+" : "", r.qty_delta] }), _jsxs("td", { className: "whitespace-nowrap px-3 py-2 tabular-nums text-muted-foreground", children: [physChanged && (_jsxs("div", { children: ["Liggend ", r.physical_before, " \u2192 ", r.physical_after] })), resChanged && (_jsxs("div", { children: ["Gereserveerd ", r.reserved_before, " \u2192 ", r.reserved_after] })), !physChanged && !resChanged && _jsx("span", { children: "\u2014" })] }), _jsx("td", { className: "whitespace-nowrap px-3 py-2", children: r.order_id ? (_jsx("a", { href: `/floriday-verkoop/verkooporders/${r.order_id}`, className: "text-primary hover:underline", children: r.order_number ?? "Order" })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) }), _jsx("td", { className: "px-3 py-2 text-muted-foreground", children: _jsx(CollapsibleText, { text: r.reason }) })] }, r.id));
|
|
133
134
|
}) })] }) }))] })] }));
|
|
134
135
|
}
|
|
@@ -4,6 +4,6 @@ interface Props {
|
|
|
4
4
|
draftId?: string | null;
|
|
5
5
|
productId?: string | null;
|
|
6
6
|
}
|
|
7
|
-
export declare function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }: Props): import("react").JSX.Element;
|
|
7
|
+
export declare function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }: Props): import("react").JSX.Element | null;
|
|
8
8
|
export {};
|
|
9
9
|
//# sourceMappingURL=VoorraadStockCard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"AAcA,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,sCAgPzF"}
|
|
@@ -3,44 +3,15 @@ import { useEffect, useState } from "react";
|
|
|
3
3
|
import { Button, Card, CardContent, Input, Label, Textarea, toast, } from "@flowselections/core";
|
|
4
4
|
import { Loader2, Package, Check, Factory } from "lucide-react";
|
|
5
5
|
import { useTradeItemStock, stockKeysFor } from "../../hooks/useTradeItemStock";
|
|
6
|
-
import { useFloridayContinuousStock } from "../../hooks/useFloridayContinuousStock";
|
|
7
6
|
import { supabase } from "../../integrations/supabase/client";
|
|
8
7
|
export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }) {
|
|
9
|
-
// De canonieke sleutel van een gekoppeld product staat in de producttabel
|
|
10
|
-
// (Floriday-UUID of `trade_item_key`). Zonder die sleutel zou de kaart op
|
|
11
|
-
// `product:<id>` schrijven terwijl het overzicht de kale sleutel leest.
|
|
12
|
-
const [productKeys, setProductKeys] = useState({ floridayId: null, tradeItemKey: null });
|
|
13
|
-
useEffect(() => {
|
|
14
|
-
if (!productId) {
|
|
15
|
-
setProductKeys({ floridayId: null, tradeItemKey: null });
|
|
16
|
-
return;
|
|
17
|
-
}
|
|
18
|
-
let cancelled = false;
|
|
19
|
-
(async () => {
|
|
20
|
-
const { data } = await supabase
|
|
21
|
-
.from("products")
|
|
22
|
-
.select("trade_item_key,floriday_trade_item_id")
|
|
23
|
-
.eq("id", productId)
|
|
24
|
-
.maybeSingle();
|
|
25
|
-
if (cancelled)
|
|
26
|
-
return;
|
|
27
|
-
setProductKeys({
|
|
28
|
-
floridayId: data?.floriday_trade_item_id ?? null,
|
|
29
|
-
tradeItemKey: data?.trade_item_key ?? null,
|
|
30
|
-
});
|
|
31
|
-
})();
|
|
32
|
-
return () => {
|
|
33
|
-
cancelled = true;
|
|
34
|
-
};
|
|
35
|
-
}, [productId]);
|
|
36
8
|
const keys = stockKeysFor({
|
|
37
|
-
tradeItemId
|
|
38
|
-
tradeItemKey: productKeys.tradeItemKey,
|
|
9
|
+
tradeItemId,
|
|
39
10
|
draftId,
|
|
40
11
|
productId,
|
|
41
12
|
});
|
|
42
13
|
const key = keys[0] ?? null;
|
|
43
|
-
const { row, loading, save } = useTradeItemStock(connectionId, keys);
|
|
14
|
+
const { row, loading, save, error: stockError } = useTradeItemStock(connectionId, keys, productId);
|
|
44
15
|
const [values, setValues] = useState(row);
|
|
45
16
|
const [saving, setSaving] = useState(false);
|
|
46
17
|
const [touched, setTouched] = useState(false);
|
|
@@ -68,23 +39,39 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
|
|
|
68
39
|
cancelled = true;
|
|
69
40
|
};
|
|
70
41
|
}, [productId, tradeItemId]);
|
|
71
|
-
|
|
42
|
+
// Is dit artikel de kop van een productgroep? Dan geen voorraad invoeren.
|
|
43
|
+
const [isGroupHead, setIsGroupHead] = useState(false);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
let cancelled = false;
|
|
46
|
+
(async () => {
|
|
47
|
+
if (!productId && !tradeItemId) {
|
|
48
|
+
setIsGroupHead(false);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const filters = [
|
|
52
|
+
productId ? `head_product_id.eq.${productId}` : null,
|
|
53
|
+
tradeItemId ? `head_trade_item_floriday_id.eq.${tradeItemId}` : null,
|
|
54
|
+
].filter(Boolean).join(",");
|
|
55
|
+
const { data } = await supabase.from("product_groups").select("id").or(filters).limit(1);
|
|
56
|
+
if (!cancelled)
|
|
57
|
+
setIsGroupHead(Array.isArray(data) && data.length > 0);
|
|
58
|
+
})();
|
|
59
|
+
return () => {
|
|
60
|
+
cancelled = true;
|
|
61
|
+
};
|
|
62
|
+
}, [productId, tradeItemId]);
|
|
72
63
|
useEffect(() => {
|
|
73
64
|
setValues(row);
|
|
74
65
|
setTouched(false);
|
|
75
66
|
}, [row.updated_at, row.physical_stock, row.minimum_stock, row.reserved_stock, row.incoming_stock, row.notes]);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (floridayPieces === undefined || touched)
|
|
80
|
-
return;
|
|
81
|
-
setValues((v) => v.physical_stock === floridayPieces
|
|
82
|
-
? v
|
|
83
|
-
: { ...v, physical_stock: floridayPieces });
|
|
84
|
-
}, [floridayPieces, touched]);
|
|
85
|
-
if (!key) {
|
|
67
|
+
if (isGroupHead)
|
|
68
|
+
return null;
|
|
69
|
+
if (!productId && !key) {
|
|
86
70
|
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." }));
|
|
87
71
|
}
|
|
72
|
+
if (stockError) {
|
|
73
|
+
return (_jsxs(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.", _jsxs("div", { className: "mt-1 text-xs text-muted-foreground", children: ["Details: ", String(stockError)] })] }));
|
|
74
|
+
}
|
|
88
75
|
const available = Math.max(0, values.physical_stock - values.reserved_stock);
|
|
89
76
|
// Foutmeldingen uit de database zijn soms JSON of technisch; hier maken we
|
|
90
77
|
// er gewone taal van zodat de gebruiker weet wat er moet gebeuren.
|
|
@@ -37,10 +37,12 @@ export declare function lookupStock(map: Map<string, TradeItemStockRow>, params:
|
|
|
37
37
|
productId?: string | null;
|
|
38
38
|
draftId?: string | null;
|
|
39
39
|
}): TradeItemStockRow | undefined;
|
|
40
|
-
export declare function useTradeItemStock(connectionId: string | null, keyOrKeys: string | string[] | null): {
|
|
40
|
+
export declare function useTradeItemStock(connectionId: string | null, keyOrKeys: string | string[] | null, productId?: string | null): {
|
|
41
41
|
row: TradeItemStockRow;
|
|
42
42
|
loading: boolean;
|
|
43
43
|
save: (values: TradeItemStockRow) => Promise<void>;
|
|
44
|
+
error: string | null;
|
|
45
|
+
resolvedKey: string | null;
|
|
44
46
|
};
|
|
45
47
|
export declare function fetchStockMap(connectionId: string): Promise<Map<string, TradeItemStockRow>>;
|
|
46
48
|
//# sourceMappingURL=useTradeItemStock.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAGA,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;;;
|
|
1
|
+
{"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAGA,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;;;EAyFnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAUzC"}
|
|
@@ -51,7 +51,7 @@ export function lookupStock(map, params) {
|
|
|
51
51
|
}
|
|
52
52
|
return undefined;
|
|
53
53
|
}
|
|
54
|
-
export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
54
|
+
export function useTradeItemStock(connectionId, keyOrKeys, productId) {
|
|
55
55
|
const keysKey = Array.isArray(keyOrKeys)
|
|
56
56
|
? keyOrKeys.filter(Boolean).join("|")
|
|
57
57
|
: keyOrKeys ?? "";
|
|
@@ -59,7 +59,33 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
59
59
|
const primaryKey = keys[0] ?? null;
|
|
60
60
|
const [row, setRow] = useState(null);
|
|
61
61
|
const [loading, setLoading] = useState(false);
|
|
62
|
+
const [resolvedConnectionId, setResolvedConnectionId] = useState(connectionId);
|
|
63
|
+
const [resolvedKey, setResolvedKey] = useState(primaryKey);
|
|
64
|
+
const [error, setError] = useState(null);
|
|
62
65
|
const refetch = useCallback(async () => {
|
|
66
|
+
setError(null);
|
|
67
|
+
if (productId) {
|
|
68
|
+
const { data, error: rpcError } = await supabase.rpc("get_product_stock", {
|
|
69
|
+
p_product_id: productId,
|
|
70
|
+
});
|
|
71
|
+
if (rpcError) {
|
|
72
|
+
setError(rpcError.message);
|
|
73
|
+
setRow({ ...EMPTY });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const stock = Array.isArray(data) ? data[0] : null;
|
|
77
|
+
setResolvedConnectionId(stock?.connection_id ?? null);
|
|
78
|
+
setResolvedKey(stock?.trade_item_key ?? `product:${productId}`);
|
|
79
|
+
setRow(stock ? {
|
|
80
|
+
physical_stock: Number(stock.physical_stock ?? 0),
|
|
81
|
+
minimum_stock: Number(stock.minimum_stock ?? 0),
|
|
82
|
+
reserved_stock: Number(stock.reserved_stock ?? 0),
|
|
83
|
+
incoming_stock: Number(stock.incoming_stock ?? 0),
|
|
84
|
+
notes: stock.notes ?? null,
|
|
85
|
+
updated_at: stock.updated_at ?? undefined,
|
|
86
|
+
} : { ...EMPTY });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
63
89
|
if (!connectionId || keys.length === 0)
|
|
64
90
|
return;
|
|
65
91
|
const { data } = await supabase
|
|
@@ -77,10 +103,12 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
77
103
|
break;
|
|
78
104
|
}
|
|
79
105
|
}
|
|
106
|
+
setResolvedConnectionId(connectionId);
|
|
107
|
+
setResolvedKey(primaryKey);
|
|
80
108
|
setRow(picked ?? { ...EMPTY });
|
|
81
|
-
}, [connectionId, keys]);
|
|
109
|
+
}, [connectionId, keys, primaryKey, productId]);
|
|
82
110
|
useEffect(() => {
|
|
83
|
-
if (!connectionId || keys.length === 0) {
|
|
111
|
+
if (!productId && (!connectionId || keys.length === 0)) {
|
|
84
112
|
setRow(null);
|
|
85
113
|
return;
|
|
86
114
|
}
|
|
@@ -94,15 +122,17 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
94
122
|
return () => {
|
|
95
123
|
cancelled = true;
|
|
96
124
|
};
|
|
97
|
-
}, [connectionId, keys, refetch]);
|
|
125
|
+
}, [connectionId, keys, productId, refetch]);
|
|
98
126
|
// Realtime: reflecteer elke externe mutatie (order-reservering,
|
|
99
127
|
// completion, bulk-import, wizard-save vanuit ander tabblad).
|
|
100
128
|
useEffect(() => {
|
|
101
|
-
|
|
129
|
+
const realtimeConnectionId = resolvedConnectionId ?? connectionId;
|
|
130
|
+
const realtimeKeys = resolvedKey ? [resolvedKey] : keys;
|
|
131
|
+
if (!realtimeConnectionId || realtimeKeys.length === 0)
|
|
102
132
|
return;
|
|
103
|
-
const filter = `trade_item_key=in.(${
|
|
133
|
+
const filter = `trade_item_key=in.(${realtimeKeys.map((k) => `"${k}"`).join(",")})`;
|
|
104
134
|
const channel = supabase
|
|
105
|
-
.channel(`stock-${
|
|
135
|
+
.channel(`stock-${realtimeConnectionId}-${resolvedKey ?? primaryKey}`)
|
|
106
136
|
.on("postgres_changes", {
|
|
107
137
|
event: "*",
|
|
108
138
|
schema: "public",
|
|
@@ -115,29 +145,52 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
115
145
|
return () => {
|
|
116
146
|
supabase.removeChannel(channel);
|
|
117
147
|
};
|
|
118
|
-
}, [connectionId, keys, primaryKey, refetch]);
|
|
148
|
+
}, [connectionId, keys, primaryKey, refetch, resolvedConnectionId, resolvedKey]);
|
|
119
149
|
const save = useCallback(async (values) => {
|
|
120
|
-
|
|
150
|
+
let savedKey = resolvedKey ?? primaryKey;
|
|
151
|
+
let savedConnectionId = resolvedConnectionId ?? connectionId;
|
|
152
|
+
if (productId) {
|
|
153
|
+
const { data, error: rpcError } = await supabase.rpc("set_product_stock", {
|
|
154
|
+
p_product_id: productId,
|
|
155
|
+
p_physical_stock: values.physical_stock,
|
|
156
|
+
p_minimum_stock: values.minimum_stock,
|
|
157
|
+
p_incoming_stock: values.incoming_stock,
|
|
158
|
+
p_notes: values.notes ?? undefined,
|
|
159
|
+
});
|
|
160
|
+
if (rpcError)
|
|
161
|
+
throw new Error(rpcError.message);
|
|
162
|
+
const saved = Array.isArray(data) ? data[0] : null;
|
|
163
|
+
savedKey = saved?.trade_item_key ?? savedKey;
|
|
164
|
+
savedConnectionId = saved?.connection_id ?? savedConnectionId;
|
|
165
|
+
setResolvedKey(savedKey);
|
|
166
|
+
setResolvedConnectionId(savedConnectionId);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
if (!connectionId || !primaryKey) {
|
|
170
|
+
throw new Error("Ontbrekende connectie of artikel-key");
|
|
171
|
+
}
|
|
172
|
+
const userRes = await supabase.auth.getUser();
|
|
173
|
+
const userId = userRes.data.user?.id;
|
|
174
|
+
if (!userId)
|
|
175
|
+
throw new Error("Niet ingelogd");
|
|
176
|
+
const { error: upsertError } = await supabase
|
|
177
|
+
.from("trade_item_stock")
|
|
178
|
+
.upsert({
|
|
179
|
+
user_id: userId,
|
|
180
|
+
connection_id: connectionId,
|
|
181
|
+
trade_item_key: primaryKey,
|
|
182
|
+
physical_stock: values.physical_stock,
|
|
183
|
+
minimum_stock: values.minimum_stock,
|
|
184
|
+
reserved_stock: values.reserved_stock,
|
|
185
|
+
incoming_stock: values.incoming_stock,
|
|
186
|
+
notes: values.notes,
|
|
187
|
+
}, { onConflict: "connection_id,trade_item_key" });
|
|
188
|
+
if (upsertError)
|
|
189
|
+
throw new Error(upsertError.message);
|
|
190
|
+
}
|
|
191
|
+
if (!savedConnectionId || !savedKey) {
|
|
121
192
|
throw new Error("Ontbrekende connectie of artikel-key");
|
|
122
193
|
}
|
|
123
|
-
const userRes = await supabase.auth.getUser();
|
|
124
|
-
const userId = userRes.data.user?.id;
|
|
125
|
-
if (!userId)
|
|
126
|
-
throw new Error("Niet ingelogd");
|
|
127
|
-
const { error } = await supabase
|
|
128
|
-
.from("trade_item_stock")
|
|
129
|
-
.upsert({
|
|
130
|
-
user_id: userId,
|
|
131
|
-
connection_id: connectionId,
|
|
132
|
-
trade_item_key: primaryKey,
|
|
133
|
-
physical_stock: values.physical_stock,
|
|
134
|
-
minimum_stock: values.minimum_stock,
|
|
135
|
-
reserved_stock: values.reserved_stock,
|
|
136
|
-
incoming_stock: values.incoming_stock,
|
|
137
|
-
notes: values.notes,
|
|
138
|
-
}, { onConflict: "connection_id,trade_item_key" });
|
|
139
|
-
if (error)
|
|
140
|
-
throw new Error(error.message);
|
|
141
194
|
const previousPhysical = row?.physical_stock ?? 0;
|
|
142
195
|
setRow(values);
|
|
143
196
|
// Verkoop = daling van de fysieke voorraad. Het bijbehorende aantal
|
|
@@ -149,8 +202,8 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
149
202
|
try {
|
|
150
203
|
const { registerSaleFustUsage } = await import("../lib/fusten");
|
|
151
204
|
await registerSaleFustUsage({
|
|
152
|
-
connectionId,
|
|
153
|
-
tradeItemKey:
|
|
205
|
+
connectionId: savedConnectionId,
|
|
206
|
+
tradeItemKey: savedKey,
|
|
154
207
|
piecesSold: sold,
|
|
155
208
|
reason: "manual_sale",
|
|
156
209
|
});
|
|
@@ -167,7 +220,7 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
167
220
|
try {
|
|
168
221
|
const { emitStockChangedEvent } = await import("../lib/logistics-events");
|
|
169
222
|
await emitStockChangedEvent({
|
|
170
|
-
tradeItemKey:
|
|
223
|
+
tradeItemKey: savedKey,
|
|
171
224
|
physicalStock: values.physical_stock,
|
|
172
225
|
reservedStock: values.reserved_stock,
|
|
173
226
|
});
|
|
@@ -176,8 +229,8 @@ export function useTradeItemStock(connectionId, keyOrKeys) {
|
|
|
176
229
|
// Engine-tabel niet bereikbaar; voorraad is lokaal wel opgeslagen.
|
|
177
230
|
}
|
|
178
231
|
})();
|
|
179
|
-
}, [connectionId, primaryKey, row]);
|
|
180
|
-
return { row: row ?? EMPTY, loading, save };
|
|
232
|
+
}, [connectionId, primaryKey, productId, resolvedConnectionId, resolvedKey, row]);
|
|
233
|
+
return { row: row ?? EMPTY, loading, save, error, resolvedKey };
|
|
181
234
|
}
|
|
182
235
|
export async function fetchStockMap(connectionId) {
|
|
183
236
|
const { data } = await supabase
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useVoorraadData.d.ts","sourceRoot":"","sources":["../../src/hooks/useVoorraadData.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAsB5E;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;;;;;
|
|
1
|
+
{"version":3,"file":"useVoorraadData.d.ts","sourceRoot":"","sources":["../../src/hooks/useVoorraadData.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAsB5E;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;;;;;EAuV1D"}
|
|
@@ -63,8 +63,9 @@ export function useVoorraadData(connectionId) {
|
|
|
63
63
|
fetchStockMap(connectionId),
|
|
64
64
|
supabase
|
|
65
65
|
.from("product_groups")
|
|
66
|
-
.select("id,head_product_id,head_trade_item_floriday_id")
|
|
67
|
-
.eq("is_active", true)
|
|
66
|
+
.select("id,head_product_id,head_trade_item_floriday_id,updated_at")
|
|
67
|
+
.eq("is_active", true)
|
|
68
|
+
.order("updated_at", { ascending: true }),
|
|
68
69
|
supabase
|
|
69
70
|
.from("product_bom_items")
|
|
70
71
|
.select("parent_product_id,parent_trade_item_floriday_id"),
|