@flowselections/floriday-voorraad 1.0.33 → 1.0.35

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.
Files changed (62) hide show
  1. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  2. package/dist-lib/components/voorraad/ArtikelWizard.js +11 -5
  3. package/dist-lib/components/voorraad/ModuleHealthCard.d.ts +2 -0
  4. package/dist-lib/components/voorraad/ModuleHealthCard.d.ts.map +1 -0
  5. package/dist-lib/components/voorraad/ModuleHealthCard.js +40 -0
  6. package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -1
  7. package/dist-lib/components/voorraad/StockBulkButtons.js +5 -2
  8. package/dist-lib/components/voorraad/StockPushMonitor.d.ts +4 -0
  9. package/dist-lib/components/voorraad/StockPushMonitor.d.ts.map +1 -0
  10. package/dist-lib/components/voorraad/StockPushMonitor.js +110 -0
  11. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  12. package/dist-lib/components/voorraad/VoorraadPage.js +2 -1
  13. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
  14. package/dist-lib/components/voorraad/VoorraadStockCard.js +114 -10
  15. package/dist-lib/hooks/useStockPushJobs.d.ts +23 -0
  16. package/dist-lib/hooks/useStockPushJobs.d.ts.map +1 -0
  17. package/dist-lib/hooks/useStockPushJobs.js +86 -0
  18. package/dist-lib/hooks/useTradeItemStock.d.ts +19 -5
  19. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  20. package/dist-lib/hooks/useTradeItemStock.js +36 -18
  21. package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
  22. package/dist-lib/hooks/useVoorraadData.js +11 -3
  23. package/dist-lib/index.d.ts +1 -0
  24. package/dist-lib/index.d.ts.map +1 -1
  25. package/dist-lib/index.js +6 -1
  26. package/dist-lib/integrations/supabase/auth-middleware.d.ts +410 -4
  27. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  28. package/dist-lib/integrations/supabase/client.d.ts +410 -4
  29. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  30. package/dist-lib/integrations/supabase/client.server.d.ts +410 -4
  31. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  32. package/dist-lib/integrations/supabase/types.d.ts +411 -5
  33. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  34. package/dist-lib/integrations/supabase/types.js +12 -1
  35. package/dist-lib/lib/centralSecrets.server.js +1 -1
  36. package/dist-lib/lib/floricode-required-features.functions.d.ts +410 -4
  37. package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
  38. package/dist-lib/lib/floriday-customer-offer.functions.d.ts +410 -4
  39. package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
  40. package/dist-lib/lib/floriday-env.functions.d.ts +820 -8
  41. package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -1
  42. package/dist-lib/lib/floriday-gateway.functions.d.ts +2460 -24
  43. package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
  44. package/dist-lib/lib/floriday-gateway.server.d.ts +14 -0
  45. package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
  46. package/dist-lib/lib/floriday-gateway.server.js +62 -10
  47. package/dist-lib/lib/floriday-vbn-required.functions.d.ts +820 -8
  48. package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
  49. package/dist-lib/lib/floriday-warehouse.functions.d.ts +1230 -12
  50. package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
  51. package/dist-lib/lib/floriday-writes.functions.d.ts +820 -8
  52. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  53. package/dist-lib/lib/module-health.functions.d.ts +41725 -0
  54. package/dist-lib/lib/module-health.functions.d.ts.map +1 -0
  55. package/dist-lib/lib/module-health.functions.js +261 -0
  56. package/dist-lib/lib/module-runtime.server.d.ts +8 -1
  57. package/dist-lib/lib/module-runtime.server.d.ts.map +1 -1
  58. package/dist-lib/lib/module-runtime.server.js +103 -12
  59. package/dist-lib/lib/stock-sync.functions.d.ts +21302 -31
  60. package/dist-lib/lib/stock-sync.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/stock-sync.functions.js +46 -3
  62. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AAqMA,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,+BA26DL"}
1
+ {"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AAqMA,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,+BAs7DL"}
@@ -405,12 +405,18 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
405
405
  // met 0).
406
406
  let physical = 0;
407
407
  try {
408
- const { data: stockRow } = await supabase
408
+ const stockKeys = [
409
+ product.floriday_trade_item_id,
410
+ product.trade_item_key,
411
+ `product:${product.id}`,
412
+ ].filter((k) => Boolean(k));
413
+ const { data: stockRows } = await supabase
409
414
  .from("trade_item_stock")
410
- .select("physical_stock")
411
- .eq("trade_item_key", `product:${product.id}`)
412
- .maybeSingle();
413
- physical = Number(stockRow?.physical_stock ?? 0);
415
+ .select("trade_item_key,physical_stock")
416
+ .in("trade_item_key", stockKeys);
417
+ const rows = (stockRows ?? []);
418
+ const hit = stockKeys.map((k) => rows.find((r) => r.trade_item_key === k)).find(Boolean) ?? null;
419
+ physical = Number(hit?.physical_stock ?? 0);
414
420
  }
415
421
  catch { /* stock niet gevonden = 0 */ }
416
422
  setData({
@@ -0,0 +1,2 @@
1
+ export declare function ModuleHealthCard(): import("react").JSX.Element;
2
+ //# sourceMappingURL=ModuleHealthCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModuleHealthCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ModuleHealthCard.tsx"],"names":[],"mappings":"AAqBA,wBAAgB,gBAAgB,gCA8D/B"}
@@ -0,0 +1,40 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Statuskaart in de instellingen: laat in gewone taal zien of de module in
4
+ * déze omgeving volledig werkt, en biedt één knop om vastgelopen
5
+ * voorraadwijzigingen weer op gang te helpen.
6
+ */
7
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8
+ import { useServerFn } from "@tanstack/react-start";
9
+ import { AlertTriangle, CheckCircle2, RotateCw, XCircle } from "lucide-react";
10
+ import { toast } from "sonner";
11
+ import { getModuleHealth, recoverStockQueue, } from "../../lib/module-health.functions";
12
+ function StatusIcon({ status }) {
13
+ if (status === "ok")
14
+ return _jsx(CheckCircle2, { className: "mt-0.5 h-4 w-4 shrink-0 text-emerald-600" });
15
+ if (status === "warn")
16
+ return _jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0 text-amber-600" });
17
+ return _jsx(XCircle, { className: "mt-0.5 h-4 w-4 shrink-0 text-destructive" });
18
+ }
19
+ export function ModuleHealthCard() {
20
+ const fetchHealth = useServerFn(getModuleHealth);
21
+ const recover = useServerFn(recoverStockQueue);
22
+ const queryClient = useQueryClient();
23
+ const { data, isLoading } = useQuery({
24
+ queryKey: ["module-health"],
25
+ queryFn: () => fetchHealth({}),
26
+ refetchInterval: 60000,
27
+ });
28
+ const mutation = useMutation({
29
+ mutationFn: () => recover({}),
30
+ onSuccess: (res) => {
31
+ toast.success(res?.restored
32
+ ? `${res.restored} voorraadwijziging${res.restored === 1 ? "" : "en"} opnieuw ingepland.`
33
+ : "Er stond niets vast.");
34
+ void queryClient.invalidateQueries({ queryKey: ["module-health"] });
35
+ void queryClient.invalidateQueries({ queryKey: ["stock-sync-health"] });
36
+ },
37
+ onError: (e) => toast.error(e?.message ?? "Herstellen is niet gelukt."),
38
+ });
39
+ return (_jsxs("section", { className: "rounded-lg border border-border bg-card p-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { children: [_jsx("h3", { className: "text-base font-semibold text-foreground", children: "Status van de voorraadmodule" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Controleert de koppeling, de achtergrondtaken en de synchronisatie naar Floriday." })] }), _jsxs("button", { type: "button", className: "inline-flex shrink-0 items-center rounded-md border border-border px-2.5 py-1.5 text-xs font-medium text-foreground hover:bg-muted disabled:opacity-50", disabled: mutation.isPending, onClick: () => mutation.mutate(), children: [_jsx(RotateCw, { className: `mr-1.5 h-3.5 w-3.5 ${mutation.isPending ? "animate-spin" : ""}` }), "Vastgelopen taken hervatten"] })] }), _jsxs("ul", { className: "mt-4 space-y-2.5", children: [isLoading && _jsx("li", { className: "text-sm text-muted-foreground", children: "Bezig met controleren\u2026" }), data?.checks.map((check) => (_jsxs("li", { className: "flex items-start gap-2 text-sm", children: [_jsx(StatusIcon, { status: check.status }), _jsxs("div", { className: "min-w-0", children: [_jsxs("span", { className: "font-medium text-foreground", children: [check.label, ": "] }), _jsx("span", { className: "text-muted-foreground break-words", children: check.detail }), check.hint && (_jsx("p", { className: "mt-0.5 text-xs text-muted-foreground/80 break-words", children: check.hint }))] })] }, check.id)))] })] }));
40
+ }
@@ -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;AAsF3D,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,+BAmoBA"}
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;AAyF3D,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,+BAmoBA"}
@@ -24,10 +24,13 @@ const COLUMNS = [
24
24
  "Inkomende voorraad",
25
25
  ];
26
26
  /**
27
- * Bepaalt de sleutel in `trade_item_stock`. Sinds de dedup-migratie is dat altijd
28
- * `product:<uuid>`; oude `draft:` / `floriday:` waarden zijn ge-repoint.
27
+ * Bepaalt de canonieke sleutel in `trade_item_stock`: de kale Floriday-UUID
28
+ * zodra het artikel gekoppeld is (die sleutel activeert de push naar Floriday),
29
+ * anders `product:<uuid>`.
29
30
  */
30
31
  function toKey(item) {
32
+ if (item.floridayId)
33
+ return item.floridayId;
31
34
  if (item.tradeItemKey)
32
35
  return item.tradeItemKey;
33
36
  if (item.productId)
@@ -0,0 +1,4 @@
1
+ export declare function StockPushMonitor({ connectionId }: {
2
+ connectionId: string | null;
3
+ }): import("react").JSX.Element;
4
+ //# sourceMappingURL=StockPushMonitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StockPushMonitor.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockPushMonitor.tsx"],"names":[],"mappings":"AAiFA,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,+BAyJjF"}
@@ -0,0 +1,110 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Testpaneel voor de voorraadpush naar Floriday.
4
+ *
5
+ * De push zelf gebeurt niet in dit scherm: elke voorraadwijziging schrijft een
6
+ * opdracht in de gedeelde wachtrij, die de Floriday-Authenticatiemodule elke
7
+ * minuut verwerkt. Dit paneel maakt die keten zichtbaar en laat je een testpush
8
+ * inplannen zonder de voorraad te wijzigen.
9
+ */
10
+ import { useEffect, useMemo, useState } from "react";
11
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
12
+ import { useServerFn } from "@tanstack/react-start";
13
+ import { toast } from "sonner";
14
+ import { ChevronDown, ChevronRight, RefreshCw, Send } from "lucide-react";
15
+ import { supabase } from "../../integrations/supabase/client";
16
+ import { useStockPushJobs, useCentralAuthBlockade } from "../../hooks/useStockPushJobs";
17
+ import { enqueueTestStockPush } from "../../lib/stock-sync.functions";
18
+ const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
19
+ const STATUS_LABEL = {
20
+ pending: "In wachtrij",
21
+ processing: "Bezig",
22
+ completed: "Verstuurd",
23
+ failed: "Mislukt",
24
+ superseded: "Vervangen",
25
+ };
26
+ function statusClass(status) {
27
+ if (status === "completed")
28
+ return "bg-emerald-500/15 text-emerald-700";
29
+ if (status === "failed")
30
+ return "bg-destructive/15 text-destructive";
31
+ if (status === "superseded")
32
+ return "bg-muted text-muted-foreground";
33
+ return "bg-amber-500/15 text-amber-700";
34
+ }
35
+ function timeLabel(iso) {
36
+ if (!iso)
37
+ return "—";
38
+ const d = new Date(iso);
39
+ return Number.isNaN(d.getTime()) ? "—" : d.toLocaleTimeString("nl-NL");
40
+ }
41
+ function useLinkedTradeItems(connectionId) {
42
+ return useQuery({
43
+ queryKey: ["linked-trade-items", connectionId],
44
+ enabled: !!connectionId,
45
+ queryFn: async () => {
46
+ const { data, error } = await supabase
47
+ .from("trade_item_stock")
48
+ .select("trade_item_key, physical_stock")
49
+ .eq("connection_id", connectionId)
50
+ .limit(1000);
51
+ if (error)
52
+ throw error;
53
+ const rows = (data ?? []).filter((r) => UUID_RE.test(String(r.trade_item_key)));
54
+ if (rows.length === 0)
55
+ return [];
56
+ const ids = rows.map((r) => String(r.trade_item_key));
57
+ const { data: cached } = await supabase
58
+ .from("floriday_trade_items_cache")
59
+ .select("floriday_id, data")
60
+ .eq("connection_id", connectionId)
61
+ .in("floriday_id", ids);
62
+ const names = new Map();
63
+ for (const row of (cached ?? [])) {
64
+ const d = row.data;
65
+ const label = d?.tradeItemName ?? d?.name ?? d?.productName ?? null;
66
+ if (label)
67
+ names.set(String(row.floriday_id), String(label));
68
+ }
69
+ return rows
70
+ .filter((r) => names.has(String(r.trade_item_key)))
71
+ .map((r) => ({
72
+ id: String(r.trade_item_key),
73
+ name: names.get(String(r.trade_item_key)),
74
+ stock: Number(r.physical_stock ?? 0),
75
+ }))
76
+ .sort((a, b) => a.name.localeCompare(b.name, "nl"));
77
+ },
78
+ });
79
+ }
80
+ export function StockPushMonitor({ connectionId }) {
81
+ const [open, setOpen] = useState(false);
82
+ const [selected, setSelected] = useState("");
83
+ const queryClient = useQueryClient();
84
+ const jobs = useStockPushJobs(10);
85
+ const blockade = useCentralAuthBlockade();
86
+ const items = useLinkedTradeItems(open ? connectionId : null);
87
+ const testPush = useServerFn(enqueueTestStockPush);
88
+ useEffect(() => {
89
+ if (!selected && items.data && items.data.length > 0) {
90
+ setSelected(items.data[0].id);
91
+ }
92
+ }, [items.data, selected]);
93
+ const mutation = useMutation({
94
+ mutationFn: (tradeItemId) => testPush({ data: { tradeItemId } }),
95
+ onSuccess: (res) => {
96
+ toast.success(`Testpush ingepland (${res?.numberOfPieces ?? 0} stuks). Binnen een minuut verwerkt.`);
97
+ void queryClient.invalidateQueries({ queryKey: ["stock-push-jobs"] });
98
+ void queryClient.invalidateQueries({ queryKey: ["stock-sync-health"] });
99
+ },
100
+ onError: (e) => toast.error(e?.message ?? "Testpush inplannen is niet gelukt."),
101
+ });
102
+ const rows = jobs.data ?? [];
103
+ const summary = useMemo(() => {
104
+ if (rows.length === 0)
105
+ return "nog geen pushes";
106
+ const last = rows[0];
107
+ return `laatste: ${STATUS_LABEL[last.status] ?? last.status} om ${timeLabel(last.createdAt)}`;
108
+ }, [rows]);
109
+ return (_jsxs("section", { className: "rounded-lg border bg-card", children: [_jsxs("button", { type: "button", className: "flex w-full items-center gap-2 px-4 py-3 text-left text-sm font-medium", onClick: () => setOpen((v) => !v), children: [open ? (_jsx(ChevronDown, { className: "h-4 w-4 text-muted-foreground" })) : (_jsx(ChevronRight, { className: "h-4 w-4 text-muted-foreground" })), "Voorraadsync naar Floriday testen", _jsx("span", { className: "ml-auto text-xs font-normal text-muted-foreground", children: summary })] }), open && (_jsxs("div", { className: "space-y-4 border-t px-4 py-4 text-sm", children: [_jsx("p", { className: "text-muted-foreground", children: "Elke voorraadwijziging van een gekoppeld artikel zet automatisch een opdracht in de wachtrij. De Floriday-Authenticatiemodule verwerkt die elke minuut en stuurt dan het aantal stuks en de beschikbaarheid naar Floriday. Hieronder plan je een testopdracht in met de huidige voorraadstand, zonder iets te wijzigen." }), blockade.data?.blocked && (_jsxs("div", { className: "rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-amber-800", children: [_jsx("span", { className: "font-medium", children: "Let op: " }), "de tokenaanvraag bij Central Auth mislukt op dit moment, dus elke push loopt daarop vast. Dit ligt buiten deze module.", blockade.data.message && (_jsxs("span", { className: "block opacity-80", children: ["Melding: ", blockade.data.message] }))] })), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("select", { className: "h-9 min-w-[16rem] rounded-md border bg-background px-2 text-sm", value: selected, onChange: (e) => setSelected(e.target.value), disabled: !connectionId || items.isLoading || (items.data ?? []).length === 0, children: (items.data ?? []).length === 0 ? (_jsx("option", { value: "", children: items.isLoading ? "Artikelen laden…" : "Geen gekoppelde artikelen gevonden" })) : ((items.data ?? []).map((i) => (_jsxs("option", { value: i.id, children: [i.name, " \u2014 ", i.stock, " stuks"] }, i.id)))) }), _jsxs("button", { type: "button", className: "inline-flex h-9 items-center rounded-md bg-primary px-3 text-sm font-medium text-primary-foreground hover:opacity-90 disabled:opacity-50", disabled: !selected || mutation.isPending, onClick: () => selected && mutation.mutate(selected), children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), "Testpush uitvoeren"] }), _jsxs("button", { type: "button", className: "inline-flex h-9 items-center rounded-md border px-3 text-sm font-medium hover:bg-muted disabled:opacity-50", disabled: jobs.isFetching, onClick: () => void jobs.refetch(), children: [_jsx(RefreshCw, { className: `mr-2 h-4 w-4 ${jobs.isFetching ? "animate-spin" : ""}` }), "Ververs"] })] }), _jsx("div", { className: "overflow-x-auto", children: _jsxs("table", { className: "w-full text-left text-xs", children: [_jsx("thead", { className: "text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "py-1 pr-3 font-medium", children: "Artikel" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Stuks" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Status" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Pogingen" }), _jsx("th", { className: "py-1 pr-3 font-medium", children: "Aangemaakt" }), _jsx("th", { className: "py-1 font-medium", children: "Melding" })] }) }), _jsxs("tbody", { children: [rows.length === 0 && (_jsx("tr", { children: _jsx("td", { colSpan: 6, className: "py-3 text-muted-foreground", children: "Er staan nog geen push-opdrachten in de wachtrij." }) })), rows.map((r) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "py-1.5 pr-3", children: r.name }), _jsx("td", { className: "py-1.5 pr-3 tabular-nums", children: r.numberOfPieces }), _jsx("td", { className: "py-1.5 pr-3", children: _jsx("span", { className: `rounded px-1.5 py-0.5 ${statusClass(r.status)}`, children: STATUS_LABEL[r.status] ?? r.status }) }), _jsx("td", { className: "py-1.5 pr-3 tabular-nums", children: r.attempts }), _jsx("td", { className: "py-1.5 pr-3 whitespace-nowrap", children: timeLabel(r.createdAt) }), _jsx("td", { className: "py-1.5 max-w-[24rem] truncate text-muted-foreground", title: r.lastError ?? "", children: r.lastError ?? "—" })] }, r.id)))] })] }) })] }))] }));
110
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAgGA,wBAAgB,YAAY,gCA6O3B"}
1
+ {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAiGA,wBAAgB,YAAY,gCA+O3B"}
@@ -6,6 +6,7 @@ import { AlertTriangle, CheckCircle2, FileText, PackageX, Plus, Search, } from "
6
6
  import { useFloridayConnection } from "../../hooks/useFloridayConnection";
7
7
  import { useVoorraadData } from "../../hooks/useVoorraadData";
8
8
  import { StockSyncStatus } from "./StockSyncStatus";
9
+ import { StockPushMonitor } from "./StockPushMonitor";
9
10
  import { ExcelExportButton } from "./ExcelExportButton";
10
11
  import { StockBulkButtons } from "./StockBulkButtons";
11
12
  import { VoorraadTable } from "./VoorraadTable";
@@ -93,7 +94,7 @@ export function VoorraadPage() {
93
94
  { id: "out_of_stock", label: "Niet op voorraad", count: counts.outOfStock },
94
95
  { id: "below_min", label: "Onder minimum", count: counts.belowMin },
95
96
  ];
96
- 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: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/publicaties", children: "Publicaties" }) }), _jsx(Button, { asChild: true, disabled: !activeId, size: "sm", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] })] }), _jsx(StockSyncStatus, {}), connError && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Floriday-connectie laden mislukt: ", connError] })), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Voorraad laden mislukt: ", error] })), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [_jsx(KpiCard, { icon: _jsx(FileText, { className: "h-4 w-4" }), tone: "primary", label: "Artikelen", count: counts.total, active: stockFilter === "all", onClick: () => toggle("all") }), _jsx(KpiCard, { icon: _jsx(CheckCircle2, { className: "h-4 w-4" }), tone: "emerald", label: "Op voorraad", count: counts.inStock, active: stockFilter === "in_stock", onClick: () => toggle("in_stock") }), _jsx(KpiCard, { icon: _jsx(PackageX, { className: "h-4 w-4" }), tone: "rose", label: "Niet op voorraad", count: counts.outOfStock, active: stockFilter === "out_of_stock", onClick: () => toggle("out_of_stock") }), _jsx(KpiCard, { icon: _jsx(AlertTriangle, { className: "h-4 w-4" }), tone: "amber", label: "Onder minimum", count: counts.belowMin, active: stockFilter === "below_min", onClick: () => toggle("below_min") })] }), _jsxs(Card, { className: "overflow-hidden p-0", children: [_jsx("div", { className: "flex flex-wrap items-center gap-1 border-b px-4", children: TABS.map((t) => {
97
+ 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: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/publicaties", children: "Publicaties" }) }), _jsx(Button, { asChild: true, disabled: !activeId, size: "sm", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] })] }), _jsx(StockSyncStatus, {}), _jsx(StockPushMonitor, { connectionId: activeId ?? null }), connError && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Floriday-connectie laden mislukt: ", connError] })), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Voorraad laden mislukt: ", error] })), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [_jsx(KpiCard, { icon: _jsx(FileText, { className: "h-4 w-4" }), tone: "primary", label: "Artikelen", count: counts.total, active: stockFilter === "all", onClick: () => toggle("all") }), _jsx(KpiCard, { icon: _jsx(CheckCircle2, { className: "h-4 w-4" }), tone: "emerald", label: "Op voorraad", count: counts.inStock, active: stockFilter === "in_stock", onClick: () => toggle("in_stock") }), _jsx(KpiCard, { icon: _jsx(PackageX, { className: "h-4 w-4" }), tone: "rose", label: "Niet op voorraad", count: counts.outOfStock, active: stockFilter === "out_of_stock", onClick: () => toggle("out_of_stock") }), _jsx(KpiCard, { icon: _jsx(AlertTriangle, { className: "h-4 w-4" }), tone: "amber", label: "Onder minimum", count: counts.belowMin, active: stockFilter === "below_min", onClick: () => toggle("below_min") })] }), _jsxs(Card, { className: "overflow-hidden p-0", children: [_jsx("div", { className: "flex flex-wrap items-center gap-1 border-b px-4", children: TABS.map((t) => {
97
98
  const active = stockFilter === t.id;
98
99
  return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
99
100
  ? "text-foreground"
@@ -1 +1 @@
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,+BAsHzF"}
1
+ {"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"AAeA,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,+BAkQzF"}
@@ -1,45 +1,149 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
3
  import { Button, Card, CardContent, Input, Label, Textarea, toast, } from "@flowselections/core";
4
- import { Loader2, Package, Check } from "lucide-react";
4
+ import { Loader2, Package, Check, Factory } from "lucide-react";
5
5
  import { useTradeItemStock, stockKeysFor } from "../../hooks/useTradeItemStock";
6
6
  import { useFloridayContinuousStock } from "../../hooks/useFloridayContinuousStock";
7
+ import { supabase } from "../../integrations/supabase/client";
7
8
  export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }) {
8
- const keys = stockKeysFor({ 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
+ const keys = stockKeysFor({
37
+ tradeItemId: tradeItemId ?? productKeys.floridayId,
38
+ tradeItemKey: productKeys.tradeItemKey,
39
+ draftId,
40
+ productId,
41
+ });
9
42
  const key = keys[0] ?? null;
10
43
  const { row, loading, save } = useTradeItemStock(connectionId, keys);
11
44
  const [values, setValues] = useState(row);
12
45
  const [saving, setSaving] = useState(false);
46
+ const [touched, setTouched] = useState(false);
47
+ const [hasBom, setHasBom] = useState(false);
48
+ const [produceQty, setProduceQty] = useState("");
49
+ const [producing, setProducing] = useState(false);
50
+ // Heeft dit artikel onderdelen (samenstelling)? Alleen dan tonen we de
51
+ // knop "Produceren"; handmatig bijstellen blijft altijd een correctie.
52
+ useEffect(() => {
53
+ let cancelled = false;
54
+ (async () => {
55
+ if (!productId && !tradeItemId) {
56
+ setHasBom(false);
57
+ return;
58
+ }
59
+ let query = supabase.from("product_bom_items").select("id").limit(1);
60
+ query = productId
61
+ ? query.eq("parent_product_id", productId)
62
+ : query.eq("parent_trade_item_floriday_id", tradeItemId);
63
+ const { data } = await query;
64
+ if (!cancelled)
65
+ setHasBom((data ?? []).length > 0);
66
+ })();
67
+ return () => {
68
+ cancelled = true;
69
+ };
70
+ }, [productId, tradeItemId]);
13
71
  const floridayPieces = useFloridayContinuousStock(connectionId, tradeItemId ?? null);
14
72
  useEffect(() => {
15
73
  setValues(row);
74
+ setTouched(false);
16
75
  }, [row.updated_at, row.physical_stock, row.minimum_stock, row.reserved_stock, row.incoming_stock, row.notes]);
17
- // Silent sync: als Floriday een nieuwe voorraad terugmeldt, overschrijven
18
- // we het "Liggend"-veld zonder extra UI-signaal.
76
+ // Floriday mag "Liggend" alleen bijwerken zolang de gebruiker het veld niet
77
+ // zelf heeft aangepast — anders zou een handmatige invoer stil verdwijnen.
19
78
  useEffect(() => {
20
- if (floridayPieces === undefined)
79
+ if (floridayPieces === undefined || touched)
21
80
  return;
22
81
  setValues((v) => v.physical_stock === floridayPieces
23
82
  ? v
24
83
  : { ...v, physical_stock: floridayPieces });
25
- }, [floridayPieces]);
84
+ }, [floridayPieces, touched]);
26
85
  if (!key) {
27
86
  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." }));
28
87
  }
29
88
  const available = Math.max(0, values.physical_stock - values.reserved_stock);
89
+ // Foutmeldingen uit de database zijn soms JSON of technisch; hier maken we
90
+ // er gewone taal van zodat de gebruiker weet wat er moet gebeuren.
91
+ const humanError = (e) => {
92
+ const raw = e?.message ?? String(e);
93
+ try {
94
+ const parsed = JSON.parse(raw);
95
+ const msg = parsed?.message ?? parsed?.error ?? raw;
96
+ if (typeof msg === "string" && msg.toLowerCase().includes("onvoldoende voorraad")) {
97
+ return "Onvoldoende voorraad van een onderdeel. Gebruik 'Produceren' om onderdelen af te schrijven, of pas de voorraad van het onderdeel eerst aan.";
98
+ }
99
+ return typeof msg === "string" ? msg : raw;
100
+ }
101
+ catch {
102
+ return raw;
103
+ }
104
+ };
30
105
  const handleSave = async () => {
31
106
  setSaving(true);
32
107
  try {
33
108
  await save(values);
34
- toast.success("Voorraad bijgewerkt");
109
+ toast.success("Voorraad gecorrigeerd");
35
110
  }
36
111
  catch (e) {
37
- toast.error(`Opslaan mislukt: ${e.message}`);
112
+ toast.error(`Opslaan mislukt: ${humanError(e)}`);
38
113
  }
39
114
  finally {
40
115
  setSaving(false);
41
116
  }
42
117
  };
43
- 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, value: String(values[id] ?? 0), onChange: (e) => setValues((v) => ({ ...v, [id]: Number(e.target.value) || 0 })) })] }));
44
- return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(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: [_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 ?? "", onChange: (e) => setValues((v) => ({ ...v, notes: e.target.value })), placeholder: "Bijv. locatie of opmerking" })] }), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { onClick: handleSave, disabled: saving, children: [saving ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Voorraad opslaan"] }) })] })) })] }));
118
+ const handleProduce = async () => {
119
+ const qty = Number(produceQty);
120
+ if (!Number.isFinite(qty) || qty <= 0) {
121
+ toast.error("Vul een aantal groter dan 0 in");
122
+ return;
123
+ }
124
+ setProducing(true);
125
+ try {
126
+ const { error } = await supabase.rpc("produce_designed_product", {
127
+ p_parent_kind: productId ? "product" : "floriday",
128
+ p_parent_id: (productId ?? tradeItemId),
129
+ p_quantity: qty,
130
+ });
131
+ if (error)
132
+ throw new Error(error.message);
133
+ toast.success(`${qty} stuks geproduceerd; onderdelen afgeschreven`);
134
+ setProduceQty("");
135
+ setTouched(false);
136
+ }
137
+ catch (e) {
138
+ toast.error(`Produceren mislukt: ${humanError(e)}`);
139
+ }
140
+ finally {
141
+ setProducing(false);
142
+ }
143
+ };
144
+ 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, value: String(values[id] ?? 0), onChange: (e) => {
145
+ setTouched(true);
146
+ setValues((v) => ({ ...v, [id]: Number(e.target.value) || 0 }));
147
+ } })] }));
148
+ return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(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: [_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 ?? "", onChange: (e) => setValues((v) => ({ ...v, notes: e.target.value })), placeholder: "Bijv. locatie of opmerking" })] }), _jsx("div", { className: "flex justify-end", children: _jsxs(Button, { onClick: handleSave, disabled: saving, children: [saving ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Voorraad opslaan"] }) }), hasBom && (_jsxs("div", { className: "space-y-2 rounded-md border border-border bg-muted/30 p-3", children: [_jsx("p", { className: "text-sm font-medium", children: "Produceren" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Handmatig aanpassen hierboven is een correctie: onderdelen blijven ongemoeid. Wil je onderdelen w\u00E9l afschrijven, geef dan hier het geproduceerde aantal op." }), _jsxs("div", { className: "flex items-end gap-2", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "produce-qty", children: "Aantal" }), _jsx(Input, { id: "produce-qty", type: "number", min: 1, className: "w-32", value: produceQty, onChange: (e) => setProduceQty(e.target.value) })] }), _jsxs(Button, { variant: "outline", onClick: handleProduce, disabled: producing, children: [producing ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Factory, { className: "mr-2 h-4 w-4" })), "Produceren"] })] })] }))] })) })] }));
45
149
  }
@@ -0,0 +1,23 @@
1
+ export interface StockPushJob {
2
+ id: string;
3
+ tradeItemId: string;
4
+ name: string;
5
+ numberOfPieces: number;
6
+ desiredAvailable: boolean | null;
7
+ status: string;
8
+ attempts: number;
9
+ lastError: string | null;
10
+ createdAt: string;
11
+ updatedAt: string | null;
12
+ sentAt: string | null;
13
+ }
14
+ export declare function useStockPushJobs(limit?: number): import("@tanstack/react-query").UseQueryResult<NoInfer<StockPushJob[]>, Error>;
15
+ /**
16
+ * Signaleert of de tokenaanvraag bij Central Auth structureel faalt. Zolang dat
17
+ * zo is, mislukt elke push — ongeacht wat deze module doet.
18
+ */
19
+ export declare function useCentralAuthBlockade(): import("@tanstack/react-query").UseQueryResult<NoInfer<{
20
+ blocked: boolean;
21
+ message: string | null;
22
+ }>, Error>;
23
+ //# sourceMappingURL=useStockPushJobs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useStockPushJobs.d.ts","sourceRoot":"","sources":["../../src/hooks/useStockPushJobs.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,gBAAgB,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAaD,wBAAgB,gBAAgB,CAAC,KAAK,SAAK,kFA4C1C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB;aACT,OAAO;aAAW,MAAM,GAAG,IAAI;WAoB3D"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Leest de laatste push-opdrachten richting Floriday uit de gedeelde wachtrij,
3
+ * inclusief de artikelnaam uit de Floriday-cache. Bedoeld om een testpush te
4
+ * kunnen volgen zonder databasekennis.
5
+ */
6
+ import { useQuery } from "@tanstack/react-query";
7
+ import { supabase } from "../integrations/supabase/client";
8
+ function nameFromCache(data) {
9
+ if (!data || typeof data !== "object")
10
+ return null;
11
+ return (data.tradeItemName ??
12
+ data.name ??
13
+ data.productName ??
14
+ data.vbnProductName ??
15
+ null);
16
+ }
17
+ export function useStockPushJobs(limit = 10) {
18
+ return useQuery({
19
+ queryKey: ["stock-push-jobs", limit],
20
+ refetchInterval: 15000,
21
+ queryFn: async () => {
22
+ const { data, error } = await supabase
23
+ .from("floriday_continuous_stock_queue")
24
+ .select("id, trade_item_id, number_of_pieces, desired_available, status, attempts, last_error, created_at, updated_at, sent_at")
25
+ .order("created_at", { ascending: false })
26
+ .limit(limit);
27
+ if (error)
28
+ throw error;
29
+ const rows = (data ?? []);
30
+ const ids = Array.from(new Set(rows.map((r) => r.trade_item_id).filter(Boolean)));
31
+ const names = new Map();
32
+ if (ids.length > 0) {
33
+ const { data: cached } = await supabase
34
+ .from("floriday_trade_items_cache")
35
+ .select("floriday_id, data")
36
+ .in("floriday_id", ids);
37
+ for (const row of (cached ?? [])) {
38
+ const label = nameFromCache(row.data);
39
+ if (label)
40
+ names.set(String(row.floriday_id), String(label));
41
+ }
42
+ }
43
+ return rows.map((r) => ({
44
+ id: String(r.id),
45
+ tradeItemId: String(r.trade_item_id ?? ""),
46
+ name: names.get(String(r.trade_item_id)) ?? String(r.trade_item_id ?? "").slice(0, 8),
47
+ numberOfPieces: Number(r.number_of_pieces ?? 0),
48
+ desiredAvailable: r.desired_available ?? null,
49
+ status: String(r.status ?? ""),
50
+ attempts: Number(r.attempts ?? 0),
51
+ lastError: r.last_error ?? null,
52
+ createdAt: String(r.created_at),
53
+ updatedAt: r.updated_at ?? null,
54
+ sentAt: r.sent_at ?? null,
55
+ }));
56
+ },
57
+ });
58
+ }
59
+ /**
60
+ * Signaleert of de tokenaanvraag bij Central Auth structureel faalt. Zolang dat
61
+ * zo is, mislukt elke push — ongeacht wat deze module doet.
62
+ */
63
+ export function useCentralAuthBlockade() {
64
+ return useQuery({
65
+ queryKey: ["central-auth-blockade"],
66
+ refetchInterval: 60000,
67
+ queryFn: async () => {
68
+ const since = new Date(Date.now() - 30 * 60000).toISOString();
69
+ const { data, error } = await supabase
70
+ .from("floriday_token_refresh_log")
71
+ .select("ok, error, attempted_at")
72
+ .gte("attempted_at", since)
73
+ .order("attempted_at", { ascending: false })
74
+ .limit(5);
75
+ if (error)
76
+ return { blocked: false, message: null };
77
+ const rows = (data ?? []);
78
+ if (rows.length === 0)
79
+ return { blocked: false, message: null };
80
+ const latest = rows[0];
81
+ if (latest.ok)
82
+ return { blocked: false, message: null };
83
+ return { blocked: true, message: latest.error ?? null };
84
+ },
85
+ });
86
+ }
@@ -8,21 +8,35 @@ export interface TradeItemStockRow {
8
8
  }
9
9
  export declare function stockKeyFor(params: {
10
10
  tradeItemId?: string | null;
11
+ tradeItemKey?: string | null;
11
12
  draftId?: string | null;
12
13
  productId?: string | null;
13
14
  }): string | null;
14
15
  /**
15
- * Build the ordered list of candidate keys for one artikel. Wij zoeken op
16
- * ALLE bekende varianten (tradeItemId, product:{id}, draft:{id}) omdat de
17
- * voorraadrij historisch onder één van die keys kan zijn aangemaakt (bv.
18
- * concept → gepubliceerd, of gelinkt aan een ERP-product). Bij opslaan
19
- * gebruiken wij altijd de eerste (meest specifieke) key.
16
+ * Eén gedeelde sleutelvolgorde voor de hele module: Floriday-UUID →
17
+ * `products.trade_item_key` → `product:<id>` → `draft:<id>`. Alle schermen
18
+ * (kaart, overzicht, import, wizard) gebruiken deze volgorde, zodat ze nooit
19
+ * een andere voorraadrij kunnen kiezen. De eerste sleutel is tevens de
20
+ * canonieke sleutel waarop wordt opgeslagen.
20
21
  */
21
22
  export declare function stockKeysFor(params: {
22
23
  tradeItemId?: string | null;
24
+ tradeItemKey?: string | null;
23
25
  draftId?: string | null;
24
26
  productId?: string | null;
25
27
  }): string[];
28
+ /**
29
+ * Zoekt de voorraadrij van één artikel op in een reeds geladen map. Sinds de
30
+ * canonicalisatie is er nog maar één rij per artikel (de Floriday-UUID wanneer
31
+ * het artikel gekoppeld is, anders `product:<uuid>`), maar we kijken bewust
32
+ * naar alle bekende varianten zodat oude data nooit als "0" wordt getoond.
33
+ */
34
+ export declare function lookupStock(map: Map<string, TradeItemStockRow>, params: {
35
+ tradeItemKey?: string | null;
36
+ floridayId?: string | null;
37
+ productId?: string | null;
38
+ draftId?: string | null;
39
+ }): TradeItemStockRow | undefined;
26
40
  export declare function useTradeItemStock(connectionId: string | null, keyOrKeys: string | string[] | null): {
27
41
  row: TradeItemStockRow;
28
42
  loading: boolean;
@@ -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,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAKhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAMX;AAED,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI;;;mBA4ElB,iBAAiB;EAmEnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAUzC"}
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;;;mBA4ElB,iBAAiB;EAmEnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAUzC"}