@flowselections/floriday-voorraad 1.0.41 → 1.0.43
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 +15 -11
- package/dist-lib/components/voorraad/OrderHistoryCard.js +2 -2
- package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts +7 -0
- package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/StockMutationHistoryCard.js +134 -0
- package/dist-lib/components/voorraad/StockPushMonitor.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockPushMonitor.js +90 -12
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +1 -1
- package/dist-lib/hooks/useFloriday.d.ts.map +1 -1
- package/dist-lib/hooks/useFloriday.js +5 -2
- package/dist-lib/hooks/useStockPushJobs.d.ts +2 -0
- package/dist-lib/hooks/useStockPushJobs.d.ts.map +1 -1
- package/dist-lib/hooks/useStockPushJobs.js +18 -5
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +1001 -15
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +1001 -15
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +1001 -15
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +1001 -10
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/floricode-required-features.functions.d.ts +1001 -15
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-2026v1.server.d.ts +1 -1
- package/dist-lib/lib/floriday-2026v1.server.d.ts.map +1 -1
- package/dist-lib/lib/floriday-2026v1.server.js +10 -0
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts +1001 -15
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-env.functions.d.ts +1972 -0
- package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.d.ts +5916 -0
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.server.d.ts +7 -1
- package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.server.js +50 -3
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts +1972 -0
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +2958 -0
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-write-gate.server.d.ts +60 -0
- package/dist-lib/lib/floriday-write-gate.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-write-gate.server.js +196 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts +1972 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/inventory-settings.functions.d.ts +1972 -0
- package/dist-lib/lib/inventory-settings.functions.d.ts.map +1 -1
- package/dist-lib/lib/module-health.functions.d.ts +1092 -0
- package/dist-lib/lib/module-health.functions.d.ts.map +1 -1
- package/dist-lib/lib/stock-sync.functions.d.ts +1972 -0
- package/dist-lib/lib/stock-sync.functions.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -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":"AAsMA,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,2CA89DL"}
|
|
@@ -34,6 +34,7 @@ import { VoorraadStockCard } from "../../components/voorraad/VoorraadStockCard";
|
|
|
34
34
|
import { ProductLinkCard } from "../../components/voorraad/ProductLinkCard";
|
|
35
35
|
import { PriceStatusBadge } from "../../components/voorraad/PriceStatusBadge";
|
|
36
36
|
import { OrderHistoryCard } from "../../components/voorraad/OrderHistoryCard";
|
|
37
|
+
import { StockMutationHistoryCard } from "../../components/voorraad/StockMutationHistoryCard";
|
|
37
38
|
import { ArtikelCompositionCard } from "../../components/voorraad/ArtikelCompositionCard";
|
|
38
39
|
import { StepSamenstelling } from "../../components/voorraad/StepSamenstelling";
|
|
39
40
|
import { FloridayInfoCard } from "../../components/voorraad/FloridayInfoCard";
|
|
@@ -139,7 +140,10 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
139
140
|
// van elke geleerde eis, en de gebruiker nooit dezelfde 400 twee keer krijgt.
|
|
140
141
|
const queryClient = useQueryClient();
|
|
141
142
|
const isEditingDraft = Boolean(initialDraftId);
|
|
142
|
-
|
|
143
|
+
// Een lokaal product dat al aan Floriday gekoppeld is, is géén "nieuw"
|
|
144
|
+
// artikel: bewerken betekent dan bijwerken in Floriday.
|
|
145
|
+
const publishedTradeItemId = tradeItemId ?? productMeta.floridayTradeItemId ?? null;
|
|
146
|
+
const isEditingPublished = Boolean(publishedTradeItemId);
|
|
143
147
|
const isEditingProduct = Boolean(productId);
|
|
144
148
|
const isEditing = isEditingDraft || isEditingPublished || isEditingProduct;
|
|
145
149
|
const lastSavedRef = useRef(null);
|
|
@@ -969,7 +973,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
969
973
|
id: draftId,
|
|
970
974
|
connectionId: activeId,
|
|
971
975
|
data,
|
|
972
|
-
floridayTradeItemId: (
|
|
976
|
+
floridayTradeItemId: (publishedTradeItemId || draftFloridayId || null),
|
|
973
977
|
sync: {
|
|
974
978
|
dirty: true,
|
|
975
979
|
last_attempt_at: new Date().toISOString(),
|
|
@@ -1038,7 +1042,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1038
1042
|
id: draftId,
|
|
1039
1043
|
connectionId: activeId,
|
|
1040
1044
|
data,
|
|
1041
|
-
floridayTradeItemId: (
|
|
1045
|
+
floridayTradeItemId: (publishedTradeItemId || draftFloridayId || null),
|
|
1042
1046
|
sync: { dirty: true, last_error: null },
|
|
1043
1047
|
});
|
|
1044
1048
|
if (localId && localId !== draftId)
|
|
@@ -1104,18 +1108,18 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1104
1108
|
}
|
|
1105
1109
|
setPublishJobId(jobId);
|
|
1106
1110
|
const stepOpts = { characteristicLabels };
|
|
1107
|
-
let resolvedTradeItemId =
|
|
1111
|
+
let resolvedTradeItemId = publishedTradeItemId ?? null;
|
|
1108
1112
|
try {
|
|
1109
1113
|
// 1) Productgegevens -------------------------------------------------
|
|
1110
1114
|
const core = await runPublishStep(jobId, "trade-item", async () => {
|
|
1111
|
-
if (
|
|
1112
|
-
await saveWarehousePreference(
|
|
1115
|
+
if (publishedTradeItemId) {
|
|
1116
|
+
await saveWarehousePreference(publishedTradeItemId, data.warehouseId);
|
|
1113
1117
|
const payload = buildUpdateTradeItemPayload(data);
|
|
1114
|
-
const res = await floriday.updateTradeItem(activeId,
|
|
1118
|
+
const res = await floriday.updateTradeItem(activeId, publishedTradeItemId, payload);
|
|
1115
1119
|
if (!res?.attemptId) {
|
|
1116
1120
|
throw new Error("De wijziging is niet bevestigd door de handelsomgeving");
|
|
1117
1121
|
}
|
|
1118
|
-
return { tradeItemId };
|
|
1122
|
+
return { tradeItemId: publishedTradeItemId };
|
|
1119
1123
|
}
|
|
1120
1124
|
const payload = buildAddTradeItemPayload(data);
|
|
1121
1125
|
const res = await floriday.createTradeItem(activeId, payload);
|
|
@@ -1374,7 +1378,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1374
1378
|
const persistAndPush = useCallback(async (snapshot) => {
|
|
1375
1379
|
if (!activeId)
|
|
1376
1380
|
return false;
|
|
1377
|
-
const pushTargetId = (
|
|
1381
|
+
const pushTargetId = (publishedTradeItemId || draftFloridayId || null);
|
|
1378
1382
|
setSyncState("saving");
|
|
1379
1383
|
setSyncError(null);
|
|
1380
1384
|
// 1. Lokaal opslaan — nooit overslaan.
|
|
@@ -1479,7 +1483,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1479
1483
|
return;
|
|
1480
1484
|
if (draftLoading || publishedLoading || productLoading)
|
|
1481
1485
|
return;
|
|
1482
|
-
const pushTargetId = (
|
|
1486
|
+
const pushTargetId = (publishedTradeItemId || draftFloridayId || null);
|
|
1483
1487
|
if (!draftId && !pushTargetId)
|
|
1484
1488
|
return;
|
|
1485
1489
|
const serialized = JSON.stringify(data);
|
|
@@ -1545,7 +1549,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1545
1549
|
name: data.name || data.vbnName,
|
|
1546
1550
|
articleNumber: data.articleCode,
|
|
1547
1551
|
gtin: data.gtin,
|
|
1548
|
-
} }), _jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: linkedProductId }), _jsx(OrderHistoryCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null, productId: linkedProductId ?? null }), _jsx(ProductLinkCard, { tradeItemKey: localTradeItemKey, productName: data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
|
|
1552
|
+
} }), _jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: linkedProductId }), _jsx(OrderHistoryCard, { tradeItemKey: localTradeItemKey, productName: data.name || data.vbnName || null, productId: linkedProductId ?? null }), _jsx(StockMutationHistoryCard, { tradeItemKey: localTradeItemKey, productId: linkedProductId ?? null }), _jsx(ProductLinkCard, { tradeItemKey: localTradeItemKey, productName: data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
|
|
1549
1553
|
const isOpen = i === step;
|
|
1550
1554
|
const isValid = stepValidity[i];
|
|
1551
1555
|
const isVisited = i < step || isEditing;
|
|
@@ -123,11 +123,11 @@ export function OrderHistoryCard({ tradeItemKey, productName, productId }) {
|
|
|
123
123
|
supabase.removeChannel(channel);
|
|
124
124
|
};
|
|
125
125
|
}, [tradeItemKey, productName, productId]);
|
|
126
|
-
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(ClipboardList, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Bestelgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} bestelling${rows.length === 1 ? "" : "en"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (_jsxs("div", { className: "text-sm text-destructive", children: ["Kon bestelgeschiedenis niet laden: ", error] })), !rows && !error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Bestellingen laden\u2026"] })), rows && rows.length === 0 && !error && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen bestellingen voor dit artikel." })), rows && rows.length > 0 && (_jsx("div", { className: "overflow-x-auto rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-xs uppercase text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 text-left", children: "Ordernummer" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Klant" }), _jsx("th", { className: "px-3 py-2 text-right", children: "Fusten" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Besteldatum" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Status" }), _jsx("th", { className: "px-3 py-2" })] }) }), _jsx("tbody", { children: rows.map((r) => {
|
|
126
|
+
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(ClipboardList, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Bestelgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} bestelling${rows.length === 1 ? "" : "en"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (_jsxs("div", { className: "text-sm text-destructive", children: ["Kon bestelgeschiedenis niet laden: ", error] })), !rows && !error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Bestellingen laden\u2026"] })), rows && rows.length === 0 && !error && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen bestellingen voor dit artikel." })), rows && rows.length > 0 && (_jsx("div", { className: "overflow-x-auto rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-xs uppercase text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 text-left", children: "Ordernummer" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Klant" }), _jsx("th", { className: "px-3 py-2 text-right", children: "Fusten" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Besteldatum" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Status" }), _jsx("th", { className: "px-3 py-2" })] }) }), _jsx("tbody", { children: rows.map((r, i) => {
|
|
127
127
|
const label = normalizeStatus(r.status);
|
|
128
128
|
const tone = STATUS_TONE[label.toLowerCase()] ??
|
|
129
129
|
"bg-slate-100 text-slate-700 border-slate-200";
|
|
130
130
|
const href = `/floriday-verkoop/verkooporders/${r.order_id}`;
|
|
131
|
-
return (_jsxs("tr", { className: "border-t hover:bg-muted/40 cursor-pointer", onClick: () => { window.location.href = href; }, children: [_jsx("td", { className: "px-3 py-2 font-medium", children: _jsx("a", { href: href, className: "text-primary hover:underline", onClick: (e) => e.stopPropagation(), children: r.order_number }) }), _jsx("td", { className: "px-3 py-2", children: r.customer_name ?? "—" }), _jsxs("td", { className: "px-3 py-2 text-right tabular-nums", children: [r.quantity, r.unit ? _jsx("span", { className: "ml-1 text-xs text-muted-foreground", children: r.unit }) : null] }), _jsx("td", { className: "px-3 py-2", children: formatDate(r.order_date ?? r.created_at) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: "outline", className: `border ${tone}`, children: label }) }), _jsx("td", { className: "px-3 py-2 text-right text-muted-foreground", children: _jsx(ExternalLink, { className: "h-3.5 w-3.5 inline" }) })] }, r.order_id));
|
|
131
|
+
return (_jsxs("tr", { className: "border-t hover:bg-muted/40 cursor-pointer", onClick: () => { window.location.href = href; }, children: [_jsx("td", { className: "px-3 py-2 font-medium", children: _jsx("a", { href: href, className: "text-primary hover:underline", onClick: (e) => e.stopPropagation(), children: r.order_number }) }), _jsx("td", { className: "px-3 py-2", children: r.customer_name ?? "—" }), _jsxs("td", { className: "px-3 py-2 text-right tabular-nums", children: [r.quantity, r.unit ? _jsx("span", { className: "ml-1 text-xs text-muted-foreground", children: r.unit }) : null] }), _jsx("td", { className: "px-3 py-2", children: formatDate(r.order_date ?? r.created_at) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: "outline", className: `border ${tone}`, children: label }) }), _jsx("td", { className: "px-3 py-2 text-right text-muted-foreground", children: _jsx(ExternalLink, { className: "h-3.5 w-3.5 inline" }) })] }, `${r.order_id}-${i}`));
|
|
132
132
|
}) })] }) }))] })] }));
|
|
133
133
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
tradeItemKey: string | null;
|
|
3
|
+
productId?: string | null;
|
|
4
|
+
}
|
|
5
|
+
export declare function StockMutationHistoryCard({ tradeItemKey, productId }: Props): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=StockMutationHistoryCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StockMutationHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockMutationHistoryCard.tsx"],"names":[],"mappings":"AAUA,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,2CAiM1E"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Afboekgeschiedenis: wanneer is er voorraad bij- of afgeboekt en waarom.
|
|
4
|
+
* Leest public.stock_mutation_log op de sleutel van dit artikel (inclusief
|
|
5
|
+
* de groepssleutel wanneer het artikel via een groep wordt afgeboekt).
|
|
6
|
+
*/
|
|
7
|
+
import { useEffect, useState } from "react";
|
|
8
|
+
import { Card, CardContent, Badge } from "@flowselections/core";
|
|
9
|
+
import { Loader2, History } from "lucide-react";
|
|
10
|
+
import { supabase } from "../../integrations/supabase/client";
|
|
11
|
+
const TYPE_LABEL = {
|
|
12
|
+
consumption: { label: "Afboeking (verkoop)", tone: "bg-rose-100 text-rose-800 border-rose-200" },
|
|
13
|
+
consumption_blocked: { label: "Afboeking geblokkeerd", tone: "bg-amber-100 text-amber-800 border-amber-200" },
|
|
14
|
+
consumption_skipped: { label: "Afboeking overgeslagen", tone: "bg-amber-100 text-amber-800 border-amber-200" },
|
|
15
|
+
reservation: { label: "Reservering", tone: "bg-blue-100 text-blue-800 border-blue-200" },
|
|
16
|
+
reservation_release: { label: "Reservering vrijgegeven", tone: "bg-blue-100 text-blue-800 border-blue-200" },
|
|
17
|
+
reservation_skipped: { label: "Reservering overgeslagen", tone: "bg-amber-100 text-amber-800 border-amber-200" },
|
|
18
|
+
manual_correction: { label: "Handmatige correctie", tone: "bg-slate-100 text-slate-700 border-slate-200" },
|
|
19
|
+
order_sync: { label: "Verwerking verkooporders", tone: "bg-indigo-100 text-indigo-800 border-indigo-200" },
|
|
20
|
+
import: { label: "Import", tone: "bg-slate-100 text-slate-700 border-slate-200" },
|
|
21
|
+
production: { label: "Productie", tone: "bg-emerald-100 text-emerald-800 border-emerald-200" },
|
|
22
|
+
stock_update: { label: "Voorraad bijgewerkt", tone: "bg-slate-100 text-slate-700 border-slate-200" },
|
|
23
|
+
};
|
|
24
|
+
function formatMoment(v) {
|
|
25
|
+
try {
|
|
26
|
+
const d = new Date(v);
|
|
27
|
+
if (Number.isNaN(d.getTime()))
|
|
28
|
+
return v;
|
|
29
|
+
return d.toLocaleString("nl-NL", {
|
|
30
|
+
day: "2-digit",
|
|
31
|
+
month: "short",
|
|
32
|
+
year: "numeric",
|
|
33
|
+
hour: "2-digit",
|
|
34
|
+
minute: "2-digit",
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return v;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
42
|
+
const [rows, setRows] = useState(null);
|
|
43
|
+
const [error, setError] = useState(null);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
let cancelled = false;
|
|
46
|
+
const load = async () => {
|
|
47
|
+
setError(null);
|
|
48
|
+
if (!tradeItemKey && !productId) {
|
|
49
|
+
setRows([]);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const keys = new Set();
|
|
54
|
+
if (tradeItemKey)
|
|
55
|
+
keys.add(tradeItemKey);
|
|
56
|
+
if (productId)
|
|
57
|
+
keys.add(productId);
|
|
58
|
+
// Groepssleutels waaronder dit artikel ook afgeboekt kan worden.
|
|
59
|
+
const { data: groups } = await supabase
|
|
60
|
+
.from("product_group_items")
|
|
61
|
+
.select("product_group_id")
|
|
62
|
+
.in("trade_item_id", [...keys])
|
|
63
|
+
.limit(20);
|
|
64
|
+
for (const g of (groups ?? [])) {
|
|
65
|
+
keys.add(`group:${g.product_group_id}`);
|
|
66
|
+
}
|
|
67
|
+
const { data, error } = await supabase
|
|
68
|
+
.from("stock_mutation_log")
|
|
69
|
+
.select("id,created_at,mutation_type,qty_delta,physical_before,physical_after,reserved_before,reserved_after,reason,order_id")
|
|
70
|
+
.in("trade_item_key", [...keys])
|
|
71
|
+
.order("created_at", { ascending: false })
|
|
72
|
+
.limit(100);
|
|
73
|
+
if (error)
|
|
74
|
+
throw error;
|
|
75
|
+
if (cancelled)
|
|
76
|
+
return;
|
|
77
|
+
// Ordernummers los ophalen: er is geen databaserelatie tussen de
|
|
78
|
+
// afboekingen en de orders, dus een gecombineerde vraag mislukt.
|
|
79
|
+
const orderIds = Array.from(new Set((data ?? []).map((r) => r.order_id).filter(Boolean)));
|
|
80
|
+
const orderNumbers = new Map();
|
|
81
|
+
if (orderIds.length > 0) {
|
|
82
|
+
const { data: orderRows } = await supabase
|
|
83
|
+
.from("orders")
|
|
84
|
+
.select("id, order_number")
|
|
85
|
+
.in("id", orderIds);
|
|
86
|
+
for (const o of (orderRows ?? [])) {
|
|
87
|
+
orderNumbers.set(String(o.id), String(o.order_number ?? ""));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (cancelled)
|
|
91
|
+
return;
|
|
92
|
+
setRows((data ?? []).map((r) => ({
|
|
93
|
+
id: r.id,
|
|
94
|
+
created_at: r.created_at,
|
|
95
|
+
mutation_type: r.mutation_type ?? "",
|
|
96
|
+
qty_delta: Number(r.qty_delta ?? 0),
|
|
97
|
+
physical_before: r.physical_before === null ? null : Number(r.physical_before),
|
|
98
|
+
physical_after: r.physical_after === null ? null : Number(r.physical_after),
|
|
99
|
+
reserved_before: r.reserved_before === null ? null : Number(r.reserved_before),
|
|
100
|
+
reserved_after: r.reserved_after === null ? null : Number(r.reserved_after),
|
|
101
|
+
reason: r.reason ?? null,
|
|
102
|
+
order_id: r.order_id ?? null,
|
|
103
|
+
order_number: r.order_id ? orderNumbers.get(String(r.order_id)) ?? null : null,
|
|
104
|
+
})));
|
|
105
|
+
}
|
|
106
|
+
catch (e) {
|
|
107
|
+
if (!cancelled)
|
|
108
|
+
setError(e.message);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
load();
|
|
112
|
+
const channel = supabase
|
|
113
|
+
.channel(`stock-mutations-${tradeItemKey ?? productId ?? "none"}`)
|
|
114
|
+
.on("postgres_changes", { event: "*", schema: "public", table: "stock_mutation_log" }, load)
|
|
115
|
+
.subscribe();
|
|
116
|
+
return () => {
|
|
117
|
+
cancelled = true;
|
|
118
|
+
supabase.removeChannel(channel);
|
|
119
|
+
};
|
|
120
|
+
}, [tradeItemKey, productId]);
|
|
121
|
+
return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(History, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Afboekgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} mutatie${rows.length === 1 ? "" : "s"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (_jsxs("div", { className: "text-sm text-destructive", children: ["Kon voorraadmutaties niet laden: ", error] })), !rows && !error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Mutaties laden\u2026"] })), rows && rows.length === 0 && !error && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen voorraadmutaties voor dit artikel." })), rows && rows.length > 0 && (_jsx("div", { className: "overflow-x-auto rounded-md border", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-xs uppercase text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 text-left", children: "Wanneer" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Soort" }), _jsx("th", { className: "px-3 py-2 text-right", children: "Aantal" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Van \u2192 naar" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Order" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Reden" })] }) }), _jsx("tbody", { children: rows.map((r) => {
|
|
122
|
+
const meta = TYPE_LABEL[r.mutation_type] ?? {
|
|
123
|
+
label: r.mutation_type || "Onbekend",
|
|
124
|
+
tone: "bg-slate-100 text-slate-700 border-slate-200",
|
|
125
|
+
};
|
|
126
|
+
const physChanged = r.physical_before !== null &&
|
|
127
|
+
r.physical_after !== null &&
|
|
128
|
+
r.physical_before !== r.physical_after;
|
|
129
|
+
const resChanged = r.reserved_before !== null &&
|
|
130
|
+
r.reserved_after !== null &&
|
|
131
|
+
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 ?? "—" })] }, r.id));
|
|
133
|
+
}) })] }) }))] })] }));
|
|
134
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StockPushMonitor.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockPushMonitor.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"StockPushMonitor.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockPushMonitor.tsx"],"names":[],"mappings":"AAqKA,wBAAgB,gBAAgB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,2CA+LjF"}
|
|
@@ -12,26 +12,96 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
|
12
12
|
import { useServerFn } from "@tanstack/react-start";
|
|
13
13
|
import { toast } from "sonner";
|
|
14
14
|
import { ChevronDown, ChevronRight, RefreshCw, Send } from "lucide-react";
|
|
15
|
+
import { Button, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
|
|
15
16
|
import { supabase } from "../../integrations/supabase/client";
|
|
16
17
|
import { useStockPushJobs, useCentralAuthBlockade } from "../../hooks/useStockPushJobs";
|
|
17
18
|
import { enqueueTestStockPush } from "../../lib/stock-sync.functions";
|
|
18
19
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20
|
+
/** Een verwerker houdt een regel maximaal 5 minuten vast. */
|
|
21
|
+
const LOCK_STALE_MS = 5 * 60000;
|
|
19
22
|
const STATUS_LABEL = {
|
|
20
23
|
pending: "In wachtrij",
|
|
21
24
|
processing: "Bezig",
|
|
25
|
+
sent: "Verstuurd",
|
|
22
26
|
completed: "Verstuurd",
|
|
23
27
|
failed: "Mislukt",
|
|
28
|
+
dead_letter: "Definitief mislukt",
|
|
24
29
|
superseded: "Vervangen",
|
|
25
30
|
};
|
|
26
|
-
function
|
|
27
|
-
if (
|
|
31
|
+
function toneClass(tone) {
|
|
32
|
+
if (tone === "ok")
|
|
28
33
|
return "bg-emerald-500/15 text-emerald-700";
|
|
29
|
-
if (
|
|
34
|
+
if (tone === "error")
|
|
30
35
|
return "bg-destructive/15 text-destructive";
|
|
31
|
-
if (
|
|
36
|
+
if (tone === "muted")
|
|
32
37
|
return "bg-muted text-muted-foreground";
|
|
33
38
|
return "bg-amber-500/15 text-amber-700";
|
|
34
39
|
}
|
|
40
|
+
/** Namen komen soms als taalobject binnen ({ nl: "…" }). */
|
|
41
|
+
function plainName(value) {
|
|
42
|
+
if (typeof value === "string")
|
|
43
|
+
return value.trim() || null;
|
|
44
|
+
if (value && typeof value === "object") {
|
|
45
|
+
const first = value.nl ?? value.en ?? Object.values(value)[0];
|
|
46
|
+
if (typeof first === "string")
|
|
47
|
+
return first.trim() || null;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Vertaalt de technische foutregel naar wat er aan de hand is en wat er moet
|
|
53
|
+
* gebeuren. Bewust concreet: een tokenfout lost zichzelf nooit op.
|
|
54
|
+
*/
|
|
55
|
+
function explainError(raw) {
|
|
56
|
+
if (!raw)
|
|
57
|
+
return null;
|
|
58
|
+
const m = raw.toLowerCase();
|
|
59
|
+
if (m.includes("token") || m.includes("requested path is invalid")) {
|
|
60
|
+
return {
|
|
61
|
+
text: "De Floriday-Authenticatiemodule kon geen geldig token ophalen.",
|
|
62
|
+
action: "Controleer in de Floriday-Authenticatiemodule het adres van Central Auth en of de gepubliceerde versie actueel is. Plan de opdracht daarna opnieuw in.",
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
if (m.includes("credential") || m.includes("central auth")) {
|
|
66
|
+
return {
|
|
67
|
+
text: "Er zijn geen actieve Floriday-inloggegevens voor deze omgeving.",
|
|
68
|
+
action: "Zet in Central Auth de juiste inloggegevens voor deze omgeving actief.",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
if (m.includes("prijs")) {
|
|
72
|
+
return { text: raw, action: "Vul een prijs in bij dit artikel en probeer opnieuw." };
|
|
73
|
+
}
|
|
74
|
+
if (m.includes("cache")) {
|
|
75
|
+
return { text: raw, action: "Synchroniseer de Floriday-artikelen en probeer opnieuw." };
|
|
76
|
+
}
|
|
77
|
+
if (m.includes("429") || m.includes("rate limit") || m.includes("snelheidslimiet")) {
|
|
78
|
+
return { text: "Floriday liet even geen nieuwe berichten toe.", action: "Dit gaat vanzelf; de volgende ronde pakt het op." };
|
|
79
|
+
}
|
|
80
|
+
return { text: raw, action: null };
|
|
81
|
+
}
|
|
82
|
+
/** Status zoals de gebruiker hem hoort te zien, inclusief vastgelopen regels. */
|
|
83
|
+
function displayStatus(job) {
|
|
84
|
+
if (job.status === "processing") {
|
|
85
|
+
const started = job.lockedAt ? new Date(job.lockedAt).getTime() : NaN;
|
|
86
|
+
if (!Number.isFinite(started) || Date.now() - started > LOCK_STALE_MS) {
|
|
87
|
+
return {
|
|
88
|
+
label: "Vastgelopen",
|
|
89
|
+
tone: "error",
|
|
90
|
+
hint: "De verwerker heeft deze opdracht opgepakt maar nooit afgerond. Hij wordt automatisch opnieuw opgepakt.",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return { label: "Bezig", tone: "warn" };
|
|
94
|
+
}
|
|
95
|
+
if (job.status === "sent" || job.status === "completed")
|
|
96
|
+
return { label: "Verstuurd", tone: "ok" };
|
|
97
|
+
if (job.status === "dead_letter")
|
|
98
|
+
return { label: "Definitief mislukt", tone: "error" };
|
|
99
|
+
if (job.status === "failed")
|
|
100
|
+
return { label: "Mislukt", tone: "error" };
|
|
101
|
+
if (job.status === "superseded")
|
|
102
|
+
return { label: "Vervangen", tone: "muted" };
|
|
103
|
+
return { label: STATUS_LABEL[job.status] ?? job.status, tone: "warn" };
|
|
104
|
+
}
|
|
35
105
|
function timeLabel(iso) {
|
|
36
106
|
if (!iso)
|
|
37
107
|
return "—";
|
|
@@ -62,9 +132,12 @@ function useLinkedTradeItems(connectionId) {
|
|
|
62
132
|
const names = new Map();
|
|
63
133
|
for (const row of (cached ?? [])) {
|
|
64
134
|
const d = row.data;
|
|
65
|
-
const label = d?.tradeItemName ??
|
|
135
|
+
const label = plainName(d?.tradeItemName) ??
|
|
136
|
+
plainName(d?.name) ??
|
|
137
|
+
plainName(d?.productName) ??
|
|
138
|
+
plainName(d?.vbnProductName);
|
|
66
139
|
if (label)
|
|
67
|
-
names.set(String(row.floriday_id),
|
|
140
|
+
names.set(String(row.floriday_id), label);
|
|
68
141
|
}
|
|
69
142
|
return rows
|
|
70
143
|
.filter((r) => names.has(String(r.trade_item_key)))
|
|
@@ -85,11 +158,11 @@ export function StockPushMonitor({ connectionId }) {
|
|
|
85
158
|
const blockade = useCentralAuthBlockade();
|
|
86
159
|
const items = useLinkedTradeItems(open ? connectionId : null);
|
|
87
160
|
const testPush = useServerFn(enqueueTestStockPush);
|
|
161
|
+
const itemList = items.data ?? [];
|
|
88
162
|
useEffect(() => {
|
|
89
|
-
if (!selected &&
|
|
90
|
-
setSelected(
|
|
91
|
-
|
|
92
|
-
}, [items.data, selected]);
|
|
163
|
+
if (!selected && itemList.length > 0)
|
|
164
|
+
setSelected(itemList[0].id);
|
|
165
|
+
}, [itemList, selected]);
|
|
93
166
|
const mutation = useMutation({
|
|
94
167
|
mutationFn: (tradeItemId) => testPush({ data: { tradeItemId } }),
|
|
95
168
|
onSuccess: (res) => {
|
|
@@ -104,7 +177,12 @@ export function StockPushMonitor({ connectionId }) {
|
|
|
104
177
|
if (rows.length === 0)
|
|
105
178
|
return "nog geen pushes";
|
|
106
179
|
const last = rows[0];
|
|
107
|
-
return `laatste: ${
|
|
180
|
+
return `laatste: ${displayStatus(last).label} om ${timeLabel(last.createdAt)}`;
|
|
108
181
|
}, [rows]);
|
|
109
|
-
|
|
182
|
+
const stuckCount = rows.filter((r) => displayStatus(r).label === "Vastgelopen").length;
|
|
183
|
+
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-destructive/30 bg-destructive/10 px-3 py-2 text-destructive", children: [_jsx("span", { className: "font-medium", children: "Actie nodig: " }), "de Floriday-Authenticatiemodule krijgt geen geldig token, dus elke push mislukt meteen. Dit lost zichzelf niet op \u2014 controleer daar het adres van Central Auth en de actieve inloggegevens voor deze omgeving.", blockade.data.message && (_jsxs("span", { className: "block opacity-80", children: ["Melding: ", blockade.data.message] }))] })), stuckCount > 0 && (_jsxs("div", { className: "rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-amber-800", children: [stuckCount === 1 ? "Eén opdracht is" : `${stuckCount} opdrachten zijn`, " door de verwerker opgepakt maar nooit afgerond. Ze worden na vijf minuten automatisch opnieuw opgepakt."] })), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs(Select, { value: selected, onValueChange: setSelected, disabled: !connectionId || items.isLoading || itemList.length === 0, children: [_jsx(SelectTrigger, { className: "h-9 w-[22rem]", children: _jsx(SelectValue, { placeholder: items.isLoading ? "Artikelen laden…" : "Geen gekoppelde artikelen gevonden" }) }), _jsx(SelectContent, { className: "max-h-72", children: itemList.map((i) => (_jsx(SelectItem, { value: i.id, children: _jsxs("span", { className: "flex w-full items-center justify-between gap-3", children: [_jsx("span", { className: "truncate", children: i.name }), _jsxs("span", { className: "tabular-nums text-muted-foreground", children: [i.stock, " stuks"] })] }) }, i.id))) })] }), _jsxs(Button, { type: "button", 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", variant: "outline", 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 pr-3 font-medium", children: "Bijgewerkt" }), _jsx("th", { className: "py-1 font-medium", children: "Melding" })] }) }), _jsxs("tbody", { children: [rows.length === 0 && (_jsx("tr", { children: _jsx("td", { colSpan: 7, className: "py-3 text-muted-foreground", children: "Er staan nog geen push-opdrachten in de wachtrij." }) })), rows.map((r) => {
|
|
184
|
+
const status = displayStatus(r);
|
|
185
|
+
const detail = explainError(r.lastError);
|
|
186
|
+
return (_jsxs("tr", { className: "border-t align-top", 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 ${toneClass(status.tone)}`, title: status.hint ?? "", children: status.label }) }), _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 pr-3 whitespace-nowrap", children: timeLabel(r.updatedAt ?? r.createdAt) }), _jsx("td", { className: "py-1.5 max-w-[26rem] text-muted-foreground", children: detail ? (_jsxs("span", { title: r.lastError ?? "", children: [_jsx("span", { className: "block truncate", children: detail.text }), detail.action && (_jsxs("span", { className: "block text-[11px] text-foreground/70", children: ["Wat te doen: ", detail.action] }))] })) : ("—") })] }, r.id));
|
|
187
|
+
})] })] }) })] }))] }));
|
|
110
188
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAqGA,wBAAgB,YAAY,
|
|
1
|
+
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAqGA,wBAAgB,YAAY,4CAqT3B"}
|
|
@@ -117,7 +117,7 @@ export function VoorraadPage() {
|
|
|
117
117
|
{ id: "out_of_stock", label: "Niet op voorraad", count: counts.outOfStock },
|
|
118
118
|
{ id: "below_min", label: "Onder minimum", count: counts.belowMin },
|
|
119
119
|
];
|
|
120
|
-
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) => {
|
|
120
|
+
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/validatie", children: "Validatie" }) }), _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) => {
|
|
121
121
|
const active = stockFilter === t.id;
|
|
122
122
|
return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
|
|
123
123
|
? "text-foreground"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFloriday.d.ts","sourceRoot":"","sources":["../../src/hooks/useFloriday.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAChB,MAAM,EACN,KAAK,CAAC;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC,CACH,CAAC;CACH;AAoMD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,yEA0BhC;AAqBD,wBAAgB,sBAAsB,
|
|
1
|
+
{"version":3,"file":"useFloriday.d.ts","sourceRoot":"","sources":["../../src/hooks/useFloriday.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,mBAAmB,EAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,SAAS,CAAC,EAAE,MAAM,CAChB,MAAM,EACN,KAAK,CAAC;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC,CACH,CAAC;CACH;AAoMD,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,yEA0BhC;AAqBD,wBAAgB,sBAAsB,0EA2CrC;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS;QAMnD,OAAO;cACD,MAAM;YACR;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE;UAKvD;AAED,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kCAAkC,CAAC;AAEtE,wBAAgB,aAAa,CAC3B,MAAM,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,EACxC,KAAK,SAAO;QAOF,OAAO;YACH,MAAM;UACR;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;KAAE,EAAE;UAKxD;AAED;;;;GAIG;AACH,wBAAgB,uCAAuC,SAStD"}
|
|
@@ -245,9 +245,12 @@ export function useTradeItemFormSchema() {
|
|
|
245
245
|
throw new Error(res?.error ??
|
|
246
246
|
"Floriday trade-item schema niet beschikbaar (gateway gaf geen ok=true terug)");
|
|
247
247
|
}
|
|
248
|
+
// Een lege `vbn-product-group` maakt het schema minder compleet, maar mag
|
|
249
|
+
// bewerken en publiceren niet blokkeren: de wizard valt dan terug op de
|
|
250
|
+
// reeds bekende karakteristieken.
|
|
248
251
|
const vbnList = res.codeLists?.["vbn-product-group"] ?? [];
|
|
249
|
-
if (vbnList.length === 0) {
|
|
250
|
-
|
|
252
|
+
if (vbnList.length === 0 && import.meta.env.DEV) {
|
|
253
|
+
console.warn("[floriday-schema] codeLists['vbn-product-group'] is leeg");
|
|
251
254
|
}
|
|
252
255
|
return res;
|
|
253
256
|
},
|
|
@@ -10,6 +10,8 @@ export interface StockPushJob {
|
|
|
10
10
|
createdAt: string;
|
|
11
11
|
updatedAt: string | null;
|
|
12
12
|
sentAt: string | null;
|
|
13
|
+
lockedAt: string | null;
|
|
14
|
+
lockedBy: string | null;
|
|
13
15
|
}
|
|
14
16
|
export declare function useStockPushJobs(limit?: number): import("@tanstack/react-query").UseQueryResult<StockPushJob[], Error>;
|
|
15
17
|
/**
|
|
@@ -1 +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;
|
|
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;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAwBD,wBAAgB,gBAAgB,CAAC,KAAK,SAAK,yEAgD1C;AAED;;;GAGG;AACH,wBAAgB,sBAAsB;aACT,OAAO;aAAW,MAAM,GAAG,IAAI;UAoB3D"}
|
|
@@ -5,13 +5,24 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { useQuery } from "@tanstack/react-query";
|
|
7
7
|
import { supabase } from "../integrations/supabase/client";
|
|
8
|
+
/** Floriday levert namen soms als taalobject ({ nl: "…", en: "…" }). */
|
|
9
|
+
function plainName(value) {
|
|
10
|
+
if (typeof value === "string")
|
|
11
|
+
return value.trim() || null;
|
|
12
|
+
if (value && typeof value === "object") {
|
|
13
|
+
const first = value.nl ?? value.en ?? Object.values(value)[0];
|
|
14
|
+
if (typeof first === "string")
|
|
15
|
+
return first.trim() || null;
|
|
16
|
+
}
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
8
19
|
function nameFromCache(data) {
|
|
9
20
|
if (!data || typeof data !== "object")
|
|
10
21
|
return null;
|
|
11
|
-
return (data.tradeItemName ??
|
|
12
|
-
data.name ??
|
|
13
|
-
data.productName ??
|
|
14
|
-
data.vbnProductName ??
|
|
22
|
+
return (plainName(data.tradeItemName) ??
|
|
23
|
+
plainName(data.name) ??
|
|
24
|
+
plainName(data.productName) ??
|
|
25
|
+
plainName(data.vbnProductName) ??
|
|
15
26
|
null);
|
|
16
27
|
}
|
|
17
28
|
export function useStockPushJobs(limit = 10) {
|
|
@@ -21,7 +32,7 @@ export function useStockPushJobs(limit = 10) {
|
|
|
21
32
|
queryFn: async () => {
|
|
22
33
|
const { data, error } = await supabase
|
|
23
34
|
.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")
|
|
35
|
+
.select("id, trade_item_id, number_of_pieces, desired_available, status, attempts, last_error, created_at, updated_at, sent_at, locked_at, locked_by")
|
|
25
36
|
.order("created_at", { ascending: false })
|
|
26
37
|
.limit(limit);
|
|
27
38
|
if (error)
|
|
@@ -52,6 +63,8 @@ export function useStockPushJobs(limit = 10) {
|
|
|
52
63
|
createdAt: String(r.created_at),
|
|
53
64
|
updatedAt: r.updated_at ?? null,
|
|
54
65
|
sentAt: r.sent_at ?? null,
|
|
66
|
+
lockedAt: r.locked_at ?? null,
|
|
67
|
+
lockedBy: r.locked_by ?? null,
|
|
55
68
|
}));
|
|
56
69
|
},
|
|
57
70
|
});
|