@flowselections/floriday-voorraad 1.0.15 → 1.0.16
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 +48 -7
- package/dist-lib/components/voorraad/ExcelExportButton.d.ts +2 -1
- package/dist-lib/components/voorraad/ExcelExportButton.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ExcelExportButton.js +24 -5
- package/dist-lib/components/voorraad/ProductLinkCard.d.ts +13 -0
- package/dist-lib/components/voorraad/ProductLinkCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ProductLinkCard.js +159 -0
- package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockBulkButtons.js +88 -7
- package/dist-lib/hooks/useTradeItemSpec.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemSpec.js +4 -1
- package/dist-lib/hooks/useVoorraadData.d.ts +5 -4
- package/dist-lib/hooks/useVoorraadData.d.ts.map +1 -1
- package/dist-lib/hooks/useVoorraadData.js +40 -6
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +1 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +743 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +743 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +743 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +763 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/ensure-article-codes.d.ts +8 -0
- package/dist-lib/lib/ensure-article-codes.d.ts.map +1 -0
- package/dist-lib/lib/ensure-article-codes.js +78 -0
- package/dist-lib/lib/floricode-required-features.functions.d.ts +743 -0
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-client.d.ts +6 -0
- package/dist-lib/lib/floriday-client.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.d.ts +2972 -0
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-payload.d.ts +19 -5
- package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
- package/dist-lib/lib/floriday-payload.js +106 -33
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +32228 -0
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-warehouse.functions.js +98 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts +1616 -130
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.js +34 -6
- 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":"AA6GA,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,GACZ,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAO,2CA2qBjD"}
|
|
@@ -8,13 +8,15 @@ import { useFlorydayCustomers } from "../../hooks/useFlorydayCustomers";
|
|
|
8
8
|
import { floriday } from "../../lib/floriday-client";
|
|
9
9
|
import { listFloricode } from "../../lib/floricode-client";
|
|
10
10
|
import { supabase } from "../../integrations/supabase/client";
|
|
11
|
-
import { buildAddTradeItemPayload, emptyWizardData, floridayCacheToWizardData, PACKAGING_SUPPLIERS, validateStep, } from "../../lib/floriday-payload";
|
|
11
|
+
import { buildAddTradeItemPayload, buildUpdateTradeItemPayload, emptyWizardData, floridayCacheToWizardData, PACKAGING_SUPPLIERS, validateStep, } from "../../lib/floriday-payload";
|
|
12
12
|
import { deleteLocalTradeItem, getLocalTradeItem, upsertLocalTradeItem, } from "../../hooks/useLocalTradeItems";
|
|
13
13
|
import { useTradeItemSpec } from "../../hooks/useTradeItemSpec";
|
|
14
14
|
import { useVbnRegulatoryFeatures } from "../../hooks/useVbnRegulatoryFeatures";
|
|
15
15
|
import { useCodeList, useTradeItemFormSchema } from "../../hooks/useFloriday";
|
|
16
|
-
|
|
16
|
+
const floridayLogo = "/flowselections-assets/Voorraad/floriday-logo.png";
|
|
17
17
|
import { VoorraadStockCard } from "../../components/voorraad/VoorraadStockCard";
|
|
18
|
+
import { ProductLinkCard } from "../../components/voorraad/ProductLinkCard";
|
|
19
|
+
import { stockKeyFor } from "../../hooks/useTradeItemStock";
|
|
18
20
|
const empty = emptyWizardData;
|
|
19
21
|
const STEPS = [
|
|
20
22
|
{ key: "vbn", label: "VBN-code" },
|
|
@@ -23,6 +25,7 @@ const STEPS = [
|
|
|
23
25
|
{ key: "eigenschappen", label: "Eigenschappen" },
|
|
24
26
|
{ key: "klanten", label: "Klanten" },
|
|
25
27
|
{ key: "fotos", label: "Foto's" },
|
|
28
|
+
{ key: "warehouse", label: "Warehouse" },
|
|
26
29
|
{ key: "beladingen", label: "Beladingen" },
|
|
27
30
|
{ key: "verpakking", label: "Aanvullende verpakkingsmaterialen" },
|
|
28
31
|
{ key: "samenstelling", label: "Samenstelling", disabled: true },
|
|
@@ -300,7 +303,8 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
300
303
|
try {
|
|
301
304
|
if (isEditingPublished && tradeItemId) {
|
|
302
305
|
// PUT vervangt het bestaande trade item in Floriday
|
|
303
|
-
|
|
306
|
+
// PUT /trade-items/{id} — body volgens TradeItemUpdate (zonder tradeItemId + supplierArticleCode)
|
|
307
|
+
const payload = buildUpdateTradeItemPayload(data);
|
|
304
308
|
const res = await floriday.updateTradeItem(activeId, tradeItemId, payload);
|
|
305
309
|
if (!res?.attemptId) {
|
|
306
310
|
throw new Error("Gateway gaf geen attemptId terug — wijziging niet bevestigd");
|
|
@@ -309,7 +313,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
309
313
|
try {
|
|
310
314
|
await supabase
|
|
311
315
|
.from("floriday_trade_items_cache")
|
|
312
|
-
.update({ data: payload, updated_at: new Date().toISOString() })
|
|
316
|
+
.update({ data: { ...payload, tradeItemId }, updated_at: new Date().toISOString() })
|
|
313
317
|
.eq("connection_id", activeId)
|
|
314
318
|
.eq("floriday_id", tradeItemId);
|
|
315
319
|
}
|
|
@@ -412,7 +416,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
412
416
|
if (!activeConnection) {
|
|
413
417
|
return (_jsx("main", { className: "p-6", children: _jsx(Card, { className: "p-6", children: "Geen actieve Floriday-connectie gevonden. Voeg er eerst een toe in Instellingen." }) }));
|
|
414
418
|
}
|
|
415
|
-
return (_jsxs("main", { className: "mx-auto w-full max-w-3xl space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsxs(Link, { to: "/voorraad", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar voorraad"] }) }), !isEditingPublished && (_jsxs(Button, { variant: "outline", size: "sm", className: "ml-auto", onClick: saveDraft, disabled: submitting || !vbnValid, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(ClipboardList, { className: "mr-2 h-4 w-4" })), isEditingDraft ? "Concept bijwerken" : "Opslaan als concept"] })), isEditingDraft && (_jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "text-destructive hover:text-destructive", children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Verwijderen"] }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Lokaal artikel verwijderen?" }), _jsx(AlertDialogDescription, { children: "Dit artikel is nog niet gepubliceerd naar Floriday. Verwijderen is definitief." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: handleDeleteDraft, children: "Verwijderen" })] })] })] }))] }), isEditing && (_jsx(Card, { className: "p-4 bg-muted/30 flex items-center gap-3 text-sm", children: isEditingPublished ? (_jsxs(_Fragment, { children: [_jsx("img", { src: floridayLogo
|
|
419
|
+
return (_jsxs("main", { className: "mx-auto w-full max-w-3xl space-y-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Button, { variant: "ghost", size: "sm", asChild: true, children: _jsxs(Link, { to: "/voorraad", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar voorraad"] }) }), !isEditingPublished && (_jsxs(Button, { variant: "outline", size: "sm", className: "ml-auto", onClick: saveDraft, disabled: submitting || !vbnValid, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(ClipboardList, { className: "mr-2 h-4 w-4" })), isEditingDraft ? "Concept bijwerken" : "Opslaan als concept"] })), isEditingDraft && (_jsxs(AlertDialog, { children: [_jsx(AlertDialogTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "sm", className: "text-destructive hover:text-destructive", children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Verwijderen"] }) }), _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: "Lokaal artikel verwijderen?" }), _jsx(AlertDialogDescription, { children: "Dit artikel is nog niet gepubliceerd naar Floriday. Verwijderen is definitief." })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { onClick: handleDeleteDraft, children: "Verwijderen" })] })] })] }))] }), isEditing && (_jsx(Card, { className: "p-4 bg-muted/30 flex items-center gap-3 text-sm", children: isEditingPublished ? (_jsxs(_Fragment, { children: [_jsx("img", { src: floridayLogo, alt: "Floriday", className: "h-6 w-6 shrink-0" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Gepubliceerd artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Wijzigingen worden direct naar Floriday verstuurd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: submit, disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Wijzigingen opslaan"] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Lokaal artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Niet gepubliceerd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: submit, disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), publishToFloriday ? "Publiceren naar Floriday" : "Wijzigingen opslaan"] })] })) })), isEditing && (_jsxs(_Fragment, { children: [_jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null }), _jsx(ProductLinkCard, { tradeItemKey: stockKeyFor({ tradeItemId: tradeItemId ?? null, draftId: draftId ?? null }), productName: data.vbnName || null })] })), _jsx("ol", { className: "space-y-3", children: steps.map((s, i) => {
|
|
416
420
|
const isOpen = i === step;
|
|
417
421
|
const isValid = stepValidity[i];
|
|
418
422
|
const isVisited = i < step || isEditing;
|
|
@@ -431,7 +435,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, } = {}) {
|
|
|
431
435
|
? "border-primary bg-primary text-primary-foreground"
|
|
432
436
|
: status === "invalid"
|
|
433
437
|
? "border-destructive bg-destructive text-destructive-foreground"
|
|
434
|
-
: "border-muted-foreground/30 text-muted-foreground"}`, children: status === "valid" ? (_jsx(Check, { className: "h-4 w-4" })) : status === "invalid" ? ("!") : (i + 1) }), _jsx("span", { className: `min-w-0 truncate font-medium ${status === "untouched" ? "text-muted-foreground" : ""}`, children: s.label }), !isOpen && summary && (_jsx("span", { className: `max-w-xs truncate text-xs ${status === "invalid" ? "text-destructive" : "text-muted-foreground"}`, children: status === "invalid" ? `Onvolledig — ${summary}` : summary }))] }), isOpen && (_jsxs(CardContent, { className: "space-y-4 pt-0", children: [_jsx("div", { "data-wizard-form-content": true, className: "w-full min-w-0", children: _jsx(StepBody, { stepKey: s.key, data: data, update: update, vbnProducts: vbnProducts, featureTypes: featureTypes, packagingTypes: packagingTypes, loadCarriers: loadCarriers, floridayCustomers: floridayCustomers, schemaQuery: schemaQuery, regulatoryQuery: regulatoryQuery, spec: spec, collectionsList: collectionsList, setCollectionsList: setCollectionsList, activeId: activeId, acceptedVbnCodes: acceptedVbnCodes }) }), isLastStep && !isEditingPublished && (_jsxs("div", { className: "rounded-lg border bg-muted/30 p-4 flex items-start gap-3", children: [_jsx("img", { src: floridayLogo
|
|
438
|
+
: "border-muted-foreground/30 text-muted-foreground"}`, children: status === "valid" ? (_jsx(Check, { className: "h-4 w-4" })) : status === "invalid" ? ("!") : (i + 1) }), _jsx("span", { className: `min-w-0 truncate font-medium ${status === "untouched" ? "text-muted-foreground" : ""}`, children: s.label }), !isOpen && summary && (_jsx("span", { className: `max-w-xs truncate text-xs ${status === "invalid" ? "text-destructive" : "text-muted-foreground"}`, children: status === "invalid" ? `Onvolledig — ${summary}` : summary }))] }), isOpen && (_jsxs(CardContent, { className: "space-y-4 pt-0", children: [_jsx("div", { "data-wizard-form-content": true, className: "w-full min-w-0", children: _jsx(StepBody, { stepKey: s.key, data: data, update: update, vbnProducts: vbnProducts, featureTypes: featureTypes, packagingTypes: packagingTypes, loadCarriers: loadCarriers, floridayCustomers: floridayCustomers, schemaQuery: schemaQuery, regulatoryQuery: regulatoryQuery, spec: spec, collectionsList: collectionsList, setCollectionsList: setCollectionsList, activeId: activeId, acceptedVbnCodes: acceptedVbnCodes }) }), isLastStep && !isEditingPublished && (_jsxs("div", { className: "rounded-lg border bg-muted/30 p-4 flex items-start gap-3", children: [_jsx("img", { src: floridayLogo, alt: "Floriday", className: "h-7 w-7 shrink-0 mt-0.5" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx(Label, { htmlFor: "publish-to-floriday", className: "font-medium cursor-pointer", children: "Publiceren naar Floriday" }), _jsx("p", { className: "text-xs text-muted-foreground mt-1", children: publishToFloriday
|
|
435
439
|
? "Het artikel wordt aangemaakt in Floriday en is binnen enkele minuten zichtbaar voor klanten."
|
|
436
440
|
: "Het artikel wordt alleen lokaal opgeslagen. Je kunt het later vanuit de voorraadlijst alsnog publiceren naar Floriday." })] }), _jsx(Switch, { id: "publish-to-floriday", checked: publishToFloriday, onCheckedChange: setPublishToFloriday })] })), (isLastStep || isEditing) && invalidStepCount > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive flex items-center justify-between gap-3", children: [_jsxs("span", { children: ["Nog ", invalidStepCount, " stap", invalidStepCount === 1 ? "" : "pen", " onvolledig"] }), firstInvalidIndex >= 0 && (_jsxs("button", { type: "button", onClick: () => setStep(firstInvalidIndex), className: "underline font-medium", children: ["Ga naar ", steps[firstInvalidIndex].label] }))] })), _jsx("div", { className: "flex justify-end pt-2", children: isLastStep || isEditing ? (_jsxs(Button, { onClick: submit, disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), isEditingPublished
|
|
437
441
|
? "Wijzigingen opslaan"
|
|
@@ -475,6 +479,8 @@ function summaryFor(key, d) {
|
|
|
475
479
|
: "Alle klanten";
|
|
476
480
|
case "fotos":
|
|
477
481
|
return `${d.images.length} foto('s)`;
|
|
482
|
+
case "warehouse":
|
|
483
|
+
return d.warehouseId ? "Gekozen" : "Niet gekozen";
|
|
478
484
|
case "beladingen":
|
|
479
485
|
return d.packingConfigurations.length
|
|
480
486
|
? `${d.packingConfigurations.length} configuratie(s)`
|
|
@@ -508,6 +514,8 @@ function StepBody(p) {
|
|
|
508
514
|
return _jsx(StepKlanten, { ...p });
|
|
509
515
|
case "fotos":
|
|
510
516
|
return _jsx(StepFotos, { ...p });
|
|
517
|
+
case "warehouse":
|
|
518
|
+
return _jsx(StepWarehouse, { ...p });
|
|
511
519
|
case "beladingen":
|
|
512
520
|
return _jsx(StepBeladingen, { ...p });
|
|
513
521
|
case "verpakking":
|
|
@@ -1028,6 +1036,39 @@ function StepFotos({ data, update, activeId }) {
|
|
|
1028
1036
|
e.target.value = "";
|
|
1029
1037
|
} })] }), data.images.length > 0 && (_jsx("div", { className: "grid grid-cols-4 gap-2", children: data.images.map((img, idx) => (_jsxs("div", { className: "relative group rounded-md overflow-hidden border", children: [_jsx("img", { src: img.previewUrl || img.url, alt: img.name, className: "h-24 w-full object-cover" }), _jsx("button", { type: "button", onClick: () => update("images", data.images.filter((_, i) => i !== idx)), className: "absolute top-1 right-1 rounded-full bg-background/80 p-1 opacity-0 group-hover:opacity-100", children: _jsx(X, { className: "h-3 w-3" }) })] }, img.url))) }))] }));
|
|
1030
1038
|
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Warehouse-stap — verplicht in Floriday Suppliers API 2026v1.
|
|
1041
|
+
* Toont een select gevuld uit `floriday_warehouses_cache`. Beheer van
|
|
1042
|
+
* warehouses (aanmaken/bewerken) gebeurt in Instellingen → Warehouses.
|
|
1043
|
+
*/
|
|
1044
|
+
function StepWarehouse({ data, update, activeId }) {
|
|
1045
|
+
const [warehouses, setWarehouses] = useState([]);
|
|
1046
|
+
const [loading, setLoading] = useState(false);
|
|
1047
|
+
useEffect(() => {
|
|
1048
|
+
if (!activeId)
|
|
1049
|
+
return;
|
|
1050
|
+
let cancelled = false;
|
|
1051
|
+
setLoading(true);
|
|
1052
|
+
supabase
|
|
1053
|
+
.from("floriday_warehouses_cache")
|
|
1054
|
+
.select("floriday_id, data")
|
|
1055
|
+
.eq("connection_id", activeId)
|
|
1056
|
+
.then(({ data: rows }) => {
|
|
1057
|
+
if (cancelled)
|
|
1058
|
+
return;
|
|
1059
|
+
const items = (rows ?? []).map((r) => ({
|
|
1060
|
+
id: String(r.floriday_id),
|
|
1061
|
+
name: String(r.data?.name ?? r.floriday_id),
|
|
1062
|
+
}));
|
|
1063
|
+
setWarehouses(items);
|
|
1064
|
+
setLoading(false);
|
|
1065
|
+
});
|
|
1066
|
+
return () => {
|
|
1067
|
+
cancelled = true;
|
|
1068
|
+
};
|
|
1069
|
+
}, [activeId]);
|
|
1070
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx(Label, { children: "Warehouse (verplicht)" }), _jsxs(Select, { value: data.warehouseId || undefined, onValueChange: (v) => update("warehouseId", v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: loading ? "Laden…" : "Kies een warehouse" }) }), _jsx(SelectContent, { children: warehouses.map((w) => (_jsx(SelectItem, { value: w.id, children: w.name }, w.id))) })] })] }), warehouses.length === 0 && !loading && (_jsxs("p", { className: "text-sm text-muted-foreground", children: ["Nog geen warehouses gesynchroniseerd. Ga naar", " ", _jsx(Link, { to: "/instellingen/warehouses", className: "underline", children: "Instellingen \u2192 Warehouses" }), " ", "om er \u00E9\u00E9n aan te maken."] }))] }));
|
|
1071
|
+
}
|
|
1031
1072
|
/**
|
|
1032
1073
|
* Beladingen — Floriday Suppliers API 2026v1
|
|
1033
1074
|
*
|
|
@@ -1201,7 +1242,7 @@ function StepVerpakking({ data, update }) {
|
|
|
1201
1242
|
]);
|
|
1202
1243
|
const remove = (id) => update("additionalPackaging", data.additionalPackaging.filter((p) => p.id !== id));
|
|
1203
1244
|
const edit = (id, patch) => update("additionalPackaging", data.additionalPackaging.map((p) => (p.id === id ? { ...p, ...patch } : p)));
|
|
1204
|
-
return (_jsxs("div", { className: "space-y-3", children: [data.additionalPackaging.map((p) => (_jsxs("div", { className: "grid grid-cols-[1fr_2fr_100px_auto] gap-2 items-end", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Code" }), _jsx(Input, { value: p.code, onChange: (e) => edit(p.id, { code: e.target.value }), placeholder: "bv. HOES" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Omschrijving" }), _jsx(Input, { value: p.description ?? "", onChange: (e) => edit(p.id, { description: e.target.value }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Aantal" }), _jsx(Input, { type: "number", min: 1, value: p.quantity, onChange: (e) => edit(p.id, { quantity: Number(e.target.value) }) })] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => remove(p.id), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, p.id))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: add, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Verpakkingsmateriaal toevoegen"] })] }));
|
|
1245
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsx("p", { className: "text-xs text-muted-foreground bg-muted/40 rounded-md p-2 border", children: "Bestaat je artikel uit een hoes, steker of andere toegevoegde waarde? Voeg deze items lokaal toe voor exportdocumentatie. Floriday Suppliers API 2026v1 kent hiervoor g\u00E9\u00E9n veld op TradeItem \u2014 deze data blijft binnen deze module." }), data.additionalPackaging.map((p) => (_jsxs("div", { className: "grid grid-cols-[1fr_2fr_100px_auto] gap-2 items-end", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Code" }), _jsx(Input, { value: p.code, onChange: (e) => edit(p.id, { code: e.target.value }), placeholder: "bv. HOES" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Omschrijving" }), _jsx(Input, { value: p.description ?? "", onChange: (e) => edit(p.id, { description: e.target.value }) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { className: "text-xs", children: "Aantal" }), _jsx(Input, { type: "number", min: 1, value: p.quantity, onChange: (e) => edit(p.id, { quantity: Number(e.target.value) }) })] }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => remove(p.id), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, p.id))), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: add, children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Verpakkingsmateriaal toevoegen"] })] }));
|
|
1205
1246
|
}
|
|
1206
1247
|
function StepBeschikbaarheid({ data, update }) {
|
|
1207
1248
|
const weeks = Array.from({ length: 52 }, (_, i) => i + 1);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TradeItem } from "../../lib/floriday-client";
|
|
2
|
-
export declare function ExcelExportButton({ items, filename, }: {
|
|
2
|
+
export declare function ExcelExportButton({ items, filename, onExported, }: {
|
|
3
3
|
items: TradeItem[];
|
|
4
4
|
filename?: string;
|
|
5
|
+
onExported?: () => void | Promise<void>;
|
|
5
6
|
}): import("react/jsx-runtime").JSX.Element;
|
|
6
7
|
//# sourceMappingURL=ExcelExportButton.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExcelExportButton.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ExcelExportButton.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"ExcelExportButton.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ExcelExportButton.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAG3D,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,QAAqB,EACrB,UAAU,GACX,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC,2CAgDA"}
|
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as XLSX from "xlsx";
|
|
3
|
-
import { Button } from "@flowselections/core";
|
|
3
|
+
import { Button, toast } from "@flowselections/core";
|
|
4
4
|
import { Download } from "lucide-react";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
import { ensureArticleCodes } from "../../lib/ensure-article-codes";
|
|
6
|
+
export function ExcelExportButton({ items, filename = "voorraad", onExported, }) {
|
|
7
|
+
const handle = async () => {
|
|
8
|
+
let exportItems;
|
|
9
|
+
try {
|
|
10
|
+
exportItems = await ensureArticleCodes(items);
|
|
11
|
+
}
|
|
12
|
+
catch (e) {
|
|
13
|
+
toast.error(`Artikelcodes genereren mislukt: ${e.message}`);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const missing = exportItems.filter((i) => !(i.articleNumber ?? "").trim());
|
|
17
|
+
if (missing.length > 0) {
|
|
18
|
+
toast.error(`${missing.length} artikel(en) missen nog een artikelcode; export afgebroken.`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const generatedCount = exportItems.filter((i, idx) => i.articleNumber !== items[idx].articleNumber).length;
|
|
22
|
+
const rows = exportItems.map((i) => ({
|
|
23
|
+
Artikelcode: i.articleNumber,
|
|
9
24
|
Productnaam: i.name,
|
|
10
25
|
"Liggende voorraad": i.quantity,
|
|
11
26
|
Minimum: i.minQuantity,
|
|
@@ -19,6 +34,10 @@ export function ExcelExportButton({ items, filename = "voorraad", }) {
|
|
|
19
34
|
XLSX.utils.book_append_sheet(wb, ws, "Voorraad");
|
|
20
35
|
const stamp = new Date().toISOString().slice(0, 10);
|
|
21
36
|
XLSX.writeFile(wb, `${filename}-${stamp}.xlsx`);
|
|
37
|
+
if (generatedCount > 0) {
|
|
38
|
+
toast.success(`${generatedCount} artikel(en) kregen automatisch een nieuwe artikelcode.`);
|
|
39
|
+
await onExported?.();
|
|
40
|
+
}
|
|
22
41
|
};
|
|
23
42
|
return (_jsxs(Button, { variant: "outline", onClick: handle, disabled: items.length === 0, children: [_jsx(Download, { className: "mr-2 h-4 w-4" }), "Excel export"] }));
|
|
24
43
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** trade_item_key for this stock row (usually the Floriday id or `draft:<uuid>`). */
|
|
3
|
+
tradeItemKey: string | null;
|
|
4
|
+
/** Optional product name to power auto-linking suggestions. */
|
|
5
|
+
productName?: string | null;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Koppelt ERP-`products` rijen aan één Floriday-voorraadrij (`trade_item_key`).
|
|
9
|
+
* Zonder koppeling doen bestel-triggers niets voor mailbox/manual-orders.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ProductLinkCard({ tradeItemKey, productName }: Props): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=ProductLinkCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductLinkCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductLinkCard.tsx"],"names":[],"mappings":"AAWA,UAAU,KAAK;IACb,qFAAqF;IACrF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AASD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,EAAE,KAAK,2CA4OnE"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
3
|
+
import { Button, Card, CardContent, Input, toast, } from "@flowselections/core";
|
|
4
|
+
import { Link2, Loader2, RefreshCw, Trash2, Wand2 } from "lucide-react";
|
|
5
|
+
import { supabase } from "../../integrations/supabase/client";
|
|
6
|
+
/**
|
|
7
|
+
* Koppelt ERP-`products` rijen aan één Floriday-voorraadrij (`trade_item_key`).
|
|
8
|
+
* Zonder koppeling doen bestel-triggers niets voor mailbox/manual-orders.
|
|
9
|
+
*/
|
|
10
|
+
export function ProductLinkCard({ tradeItemKey, productName }) {
|
|
11
|
+
const [linked, setLinked] = useState([]);
|
|
12
|
+
const [candidates, setCandidates] = useState([]);
|
|
13
|
+
const [query, setQuery] = useState("");
|
|
14
|
+
const [loading, setLoading] = useState(false);
|
|
15
|
+
const [busy, setBusy] = useState(false);
|
|
16
|
+
const reload = useCallback(async () => {
|
|
17
|
+
if (!tradeItemKey)
|
|
18
|
+
return;
|
|
19
|
+
setLoading(true);
|
|
20
|
+
try {
|
|
21
|
+
const { data: linkedRows } = await supabase
|
|
22
|
+
.from("products")
|
|
23
|
+
.select("id, product, barcode, trade_item_key")
|
|
24
|
+
.eq("trade_item_key", tradeItemKey)
|
|
25
|
+
.order("product");
|
|
26
|
+
setLinked(linkedRows ?? []);
|
|
27
|
+
}
|
|
28
|
+
finally {
|
|
29
|
+
setLoading(false);
|
|
30
|
+
}
|
|
31
|
+
}, [tradeItemKey]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
void reload();
|
|
34
|
+
}, [reload]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
let cancelled = false;
|
|
37
|
+
const q = query.trim();
|
|
38
|
+
if (q.length < 2) {
|
|
39
|
+
setCandidates([]);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
(async () => {
|
|
43
|
+
const { data } = await supabase
|
|
44
|
+
.from("products")
|
|
45
|
+
.select("id, product, barcode, trade_item_key")
|
|
46
|
+
.or(`product.ilike.%${q}%,barcode.ilike.%${q}%`)
|
|
47
|
+
.is("trade_item_key", null)
|
|
48
|
+
.limit(20);
|
|
49
|
+
if (!cancelled)
|
|
50
|
+
setCandidates(data ?? []);
|
|
51
|
+
})();
|
|
52
|
+
return () => {
|
|
53
|
+
cancelled = true;
|
|
54
|
+
};
|
|
55
|
+
}, [query]);
|
|
56
|
+
const linkProduct = async (id) => {
|
|
57
|
+
if (!tradeItemKey)
|
|
58
|
+
return;
|
|
59
|
+
setBusy(true);
|
|
60
|
+
try {
|
|
61
|
+
const { error } = await supabase
|
|
62
|
+
.from("products")
|
|
63
|
+
.update({ trade_item_key: tradeItemKey })
|
|
64
|
+
.eq("id", id);
|
|
65
|
+
if (error)
|
|
66
|
+
throw error;
|
|
67
|
+
toast.success("Product gekoppeld");
|
|
68
|
+
setQuery("");
|
|
69
|
+
setCandidates([]);
|
|
70
|
+
await reload();
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
toast.error(`Koppelen mislukt: ${e.message}`);
|
|
74
|
+
}
|
|
75
|
+
finally {
|
|
76
|
+
setBusy(false);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const unlinkProduct = async (id) => {
|
|
80
|
+
setBusy(true);
|
|
81
|
+
try {
|
|
82
|
+
const { error } = await supabase
|
|
83
|
+
.from("products")
|
|
84
|
+
.update({ trade_item_key: null })
|
|
85
|
+
.eq("id", id);
|
|
86
|
+
if (error)
|
|
87
|
+
throw error;
|
|
88
|
+
toast.success("Koppeling verwijderd");
|
|
89
|
+
await reload();
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
toast.error(`Ontkoppelen mislukt: ${e.message}`);
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
setBusy(false);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
const autoLinkByName = async () => {
|
|
99
|
+
if (!tradeItemKey || !productName)
|
|
100
|
+
return;
|
|
101
|
+
setBusy(true);
|
|
102
|
+
try {
|
|
103
|
+
const { data, error } = await supabase
|
|
104
|
+
.from("products")
|
|
105
|
+
.select("id, product")
|
|
106
|
+
.ilike("product", productName)
|
|
107
|
+
.is("trade_item_key", null);
|
|
108
|
+
if (error)
|
|
109
|
+
throw error;
|
|
110
|
+
const matches = data ?? [];
|
|
111
|
+
if (matches.length === 0) {
|
|
112
|
+
toast.info(`Geen niet-gekoppelde producten met naam "${productName}" gevonden.`);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const ids = matches.map((m) => m.id);
|
|
116
|
+
const { error: updErr } = await supabase
|
|
117
|
+
.from("products")
|
|
118
|
+
.update({ trade_item_key: tradeItemKey })
|
|
119
|
+
.in("id", ids);
|
|
120
|
+
if (updErr)
|
|
121
|
+
throw updErr;
|
|
122
|
+
toast.success(`${ids.length} product(en) gekoppeld op naam`);
|
|
123
|
+
await reload();
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
toast.error(`Auto-koppelen mislukt: ${e.message}`);
|
|
127
|
+
}
|
|
128
|
+
finally {
|
|
129
|
+
setBusy(false);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
const backfillReservations = async () => {
|
|
133
|
+
setBusy(true);
|
|
134
|
+
try {
|
|
135
|
+
const { data, error } = await supabase.rpc("backfill_open_order_reservations");
|
|
136
|
+
if (error)
|
|
137
|
+
throw error;
|
|
138
|
+
const rows = data ?? [];
|
|
139
|
+
const n = rows[0]?.processed_items ?? 0;
|
|
140
|
+
toast.success(`Backfill klaar: ${n} orderregel(s) verwerkt`);
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
toast.error(`Backfill mislukt: ${e.message}`);
|
|
144
|
+
}
|
|
145
|
+
finally {
|
|
146
|
+
setBusy(false);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
const helpText = useMemo(() => {
|
|
150
|
+
if (!tradeItemKey) {
|
|
151
|
+
return "Sla het artikel eerst op om producten te kunnen koppelen.";
|
|
152
|
+
}
|
|
153
|
+
if (linked.length === 0) {
|
|
154
|
+
return "Nog geen ERP-product gekoppeld. Zonder koppeling worden mailbox-orders niet automatisch van de voorraad afgeschreven.";
|
|
155
|
+
}
|
|
156
|
+
return "Bestellingen op deze ERP-producten reserveren automatisch op deze voorraadrij.";
|
|
157
|
+
}, [tradeItemKey, linked.length]);
|
|
158
|
+
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(Link2, { className: "h-4 w-4" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "font-medium", children: "Gekoppelde ERP-producten" }), _jsx("div", { className: "text-xs text-muted-foreground", children: helpText })] }), _jsxs("div", { className: "flex gap-2", children: [productName && (_jsxs(Button, { variant: "outline", size: "sm", onClick: autoLinkByName, disabled: !tradeItemKey || busy, title: "Zoek ERP-producten met exact deze naam en koppel ze", children: [_jsx(Wand2, { className: "h-4 w-4 mr-1" }), "Auto op naam"] })), _jsxs(Button, { variant: "outline", size: "sm", onClick: backfillReservations, disabled: busy, title: "Reserveer openstaande orders retroactief", children: [_jsx(RefreshCw, { className: "h-4 w-4 mr-1" }), "Backfill"] })] })] }), _jsx(CardContent, { className: "pt-0 space-y-3", 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" }), " Laden\u2026"] })) : (_jsxs(_Fragment, { children: [linked.length > 0 && (_jsx("ul", { className: "divide-y rounded-md border", children: linked.map((p) => (_jsxs("li", { className: "flex items-center justify-between gap-2 px-3 py-2 text-sm", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate font-medium", children: p.product ?? "(zonder naam)" }), p.barcode && (_jsxs("div", { className: "text-xs text-muted-foreground", children: ["Barcode: ", p.barcode] }))] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => unlinkProduct(p.id), disabled: busy, children: _jsx(Trash2, { className: "h-4 w-4" }) })] }, p.id))) })), _jsxs("div", { className: "space-y-2", children: [_jsx(Input, { placeholder: "Zoek ERP-product op naam of SKU\u2026", value: query, onChange: (e) => setQuery(e.target.value), disabled: !tradeItemKey || busy }), candidates.length > 0 && (_jsx("ul", { className: "divide-y rounded-md border max-h-56 overflow-auto", children: candidates.map((p) => (_jsxs("li", { className: "flex items-center justify-between gap-2 px-3 py-2 text-sm", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "truncate", children: p.product ?? "(zonder naam)" }), p.barcode && (_jsxs("div", { className: "text-xs text-muted-foreground", children: ["Barcode: ", p.barcode] }))] }), _jsx(Button, { size: "sm", onClick: () => linkProduct(p.id), disabled: busy, children: "Koppel" })] }, p.id))) }))] })] })) })] }));
|
|
159
|
+
}
|
|
@@ -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;
|
|
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;AAoF3D,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,2CAikBA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useMemo, useRef, useState } from "react";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import * as XLSX from "xlsx";
|
|
4
4
|
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Checkbox, Label, toast, } from "@flowselections/core";
|
|
5
5
|
import { Download, FileDown, Upload, Loader2 } from "lucide-react";
|
|
@@ -8,6 +8,7 @@ import { emptyWizardData } from "../../lib/floriday-payload";
|
|
|
8
8
|
import { upsertLocalTradeItem, updateDraftMeta } from "../../hooks/useLocalTradeItems";
|
|
9
9
|
import { validateDraftForPublish } from "../../lib/floriday-import-validator";
|
|
10
10
|
import { floriday } from "../../lib/floriday-client";
|
|
11
|
+
import { ensureArticleCodes } from "../../lib/ensure-article-codes";
|
|
11
12
|
const COLUMNS = [
|
|
12
13
|
"Artikelcode",
|
|
13
14
|
"Artikelnaam",
|
|
@@ -20,8 +21,17 @@ const COLUMNS = [
|
|
|
20
21
|
"Gereserveerde voorraad",
|
|
21
22
|
"Inkomende voorraad",
|
|
22
23
|
];
|
|
24
|
+
/**
|
|
25
|
+
* Determine which trade_item_stock key to write to. The stock table is keyed
|
|
26
|
+
* by whatever `products.trade_item_key` (or the draft/floriday id) resolved
|
|
27
|
+
* to when the row was created — never by the synthetic UI id `erp:<uuid>`.
|
|
28
|
+
*/
|
|
23
29
|
function toKey(item) {
|
|
24
|
-
|
|
30
|
+
if (item.tradeItemKey)
|
|
31
|
+
return item.tradeItemKey;
|
|
32
|
+
if (item.localId)
|
|
33
|
+
return `draft:${item.localId}`;
|
|
34
|
+
return item.id;
|
|
25
35
|
}
|
|
26
36
|
function parseNumber(val) {
|
|
27
37
|
if (val === undefined || val === null || val === "")
|
|
@@ -49,6 +59,20 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
49
59
|
const allowNegative = true;
|
|
50
60
|
const [autoPublish, setAutoPublish] = useState(false);
|
|
51
61
|
const [result, setResult] = useState(null);
|
|
62
|
+
const [pastRuns, setPastRuns] = useState([]);
|
|
63
|
+
const loadPastRuns = useCallback(async () => {
|
|
64
|
+
const { data, error } = await supabase
|
|
65
|
+
.from("stock_import_runs")
|
|
66
|
+
.select("id,filename,total_rows,updated_rows,created_rows,skipped_rows,created_at,errors,mutations")
|
|
67
|
+
.order("created_at", { ascending: false })
|
|
68
|
+
.limit(10);
|
|
69
|
+
if (!error)
|
|
70
|
+
setPastRuns((data ?? []));
|
|
71
|
+
}, []);
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (open)
|
|
74
|
+
void loadPastRuns();
|
|
75
|
+
}, [open, loadPastRuns]);
|
|
52
76
|
const byCode = useMemo(() => {
|
|
53
77
|
const map = new Map();
|
|
54
78
|
for (const it of items) {
|
|
@@ -73,11 +97,25 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
73
97
|
}
|
|
74
98
|
return map;
|
|
75
99
|
}, [items]);
|
|
76
|
-
const downloadTemplate = () => {
|
|
77
|
-
|
|
100
|
+
const downloadTemplate = async () => {
|
|
101
|
+
let exportItems;
|
|
102
|
+
try {
|
|
103
|
+
exportItems = await ensureArticleCodes(items);
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
toast.error(`Artikelcodes genereren mislukt: ${e.message}`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const missing = exportItems.filter((i) => !(i.articleNumber ?? "").trim());
|
|
110
|
+
if (missing.length > 0) {
|
|
111
|
+
toast.error(`${missing.length} artikel(en) missen nog een artikelcode; export afgebroken.`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const generatedCount = exportItems.filter((i, idx) => i.articleNumber !== items[idx].articleNumber).length;
|
|
115
|
+
const rows = exportItems.map((i) => {
|
|
78
116
|
const d = i.raw?.data;
|
|
79
117
|
return {
|
|
80
|
-
Artikelcode: i.articleNumber
|
|
118
|
+
Artikelcode: i.articleNumber,
|
|
81
119
|
Artikelnaam: i.name,
|
|
82
120
|
"VBN-code": d?.vbnCode ?? i.raw?.vbnProductCode ?? "",
|
|
83
121
|
"Botanische naam": d?.botanicalName ?? "",
|
|
@@ -97,6 +135,10 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
97
135
|
XLSX.utils.book_append_sheet(wb, ws, "Voorraad");
|
|
98
136
|
const stamp = new Date().toISOString().slice(0, 10);
|
|
99
137
|
XLSX.writeFile(wb, `voorraad-template-${stamp}.xlsx`);
|
|
138
|
+
if (generatedCount > 0) {
|
|
139
|
+
toast.success(`${generatedCount} artikel(en) kregen automatisch een nieuwe artikelcode.`);
|
|
140
|
+
await onImported();
|
|
141
|
+
}
|
|
100
142
|
};
|
|
101
143
|
const downloadErrors = () => {
|
|
102
144
|
if (!result?.errors.length)
|
|
@@ -121,6 +163,7 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
121
163
|
setProgress(0);
|
|
122
164
|
setResult(null);
|
|
123
165
|
const errors = [];
|
|
166
|
+
const mutations = [];
|
|
124
167
|
let updated = 0;
|
|
125
168
|
let created = 0;
|
|
126
169
|
let linked = 0;
|
|
@@ -205,6 +248,14 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
205
248
|
trade_item_key: toKey(existing),
|
|
206
249
|
...applied,
|
|
207
250
|
});
|
|
251
|
+
mutations.push({
|
|
252
|
+
articleCode: code,
|
|
253
|
+
articleName: existing.name,
|
|
254
|
+
oldPhysical: existing.quantity,
|
|
255
|
+
newPhysical: applied.physical_stock,
|
|
256
|
+
oldReserved: existing.reservedQuantity,
|
|
257
|
+
newReserved: applied.reserved_stock,
|
|
258
|
+
});
|
|
208
259
|
updated++;
|
|
209
260
|
setProgress(Math.round(((idx + 1) / rows.length) * 100));
|
|
210
261
|
continue;
|
|
@@ -325,7 +376,37 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
325
376
|
});
|
|
326
377
|
}
|
|
327
378
|
}
|
|
328
|
-
|
|
379
|
+
const finalResult = {
|
|
380
|
+
updated,
|
|
381
|
+
created,
|
|
382
|
+
linked,
|
|
383
|
+
published,
|
|
384
|
+
waiting,
|
|
385
|
+
errors,
|
|
386
|
+
mutations,
|
|
387
|
+
};
|
|
388
|
+
setResult(finalResult);
|
|
389
|
+
// Persisteer het importverslag zodat het achteraf herbekeken kan worden.
|
|
390
|
+
try {
|
|
391
|
+
const { error: logErr } = await supabase.from("stock_import_runs").insert({
|
|
392
|
+
user_id: userId,
|
|
393
|
+
connection_id: activeConnectionId,
|
|
394
|
+
filename: file.name,
|
|
395
|
+
total_rows: rows.length,
|
|
396
|
+
updated_rows: updated,
|
|
397
|
+
created_rows: created,
|
|
398
|
+
skipped_rows: errors.length,
|
|
399
|
+
errors: errors,
|
|
400
|
+
mutations: mutations,
|
|
401
|
+
});
|
|
402
|
+
if (logErr)
|
|
403
|
+
console.warn("Import log opslaan mislukt", logErr);
|
|
404
|
+
else
|
|
405
|
+
void loadPastRuns();
|
|
406
|
+
}
|
|
407
|
+
catch (logE) {
|
|
408
|
+
console.warn("Import log opslaan mislukt", logE);
|
|
409
|
+
}
|
|
329
410
|
if (updated + created > 0) {
|
|
330
411
|
toast.success(`${updated} bijgewerkt · ${created} nieuw`);
|
|
331
412
|
await onImported();
|
|
@@ -349,7 +430,7 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
|
|
|
349
430
|
const f = e.target.files?.[0];
|
|
350
431
|
if (f)
|
|
351
432
|
void handleFile(f);
|
|
352
|
-
} }), importing && (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "h-2 w-full overflow-hidden rounded bg-muted", children: _jsx("div", { className: "h-full bg-primary transition-all", style: { width: `${progress}%` } }) }), _jsxs("p", { className: "text-xs text-muted-foreground flex items-center gap-2", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), "Bezig met importeren\u2026 ", progress, "%"] })] })), result && (_jsxs("div", { className: "rounded-md border bg-muted/30 p-3 text-sm space-y-2", children: [_jsx("div", { className: "font-medium", children: "Importoverzicht" }), _jsxs("ul", { className: "text-xs space-y-1", children: [_jsxs("li", { children: ["Bestaande artikelen bijgewerkt:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.updated })] }), _jsxs("li", { children: ["Nieuwe artikelen aangemaakt:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.created })] }), _jsxs("li", { children: ["Automatisch gekoppeld met Floriday:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.linked })] }), _jsxs("li", { children: ["Gepubliceerd naar Floriday:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.published })] }), _jsxs("li", { children: ["Wacht op Floriday-publicatie (gegevens incompleet):", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.waiting })] }), _jsxs("li", { children: ["Fouten:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.errors.length })] })] }), result.errors.length > 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "max-h-40 overflow-y-auto rounded border bg-background p-2 text-xs space-y-1", children: [result.errors.slice(0, 20).map((e, i) => (_jsxs("div", { className: "text-destructive", children: ["Regel ", e.row, " \u2014 ", e.articleCode || "(geen code)", " \u2014", " ", e.reden] }, i))), result.errors.length > 20 && (_jsxs("div", { className: "text-muted-foreground", children: ["\u2026 en ", result.errors.length - 20, " meer. Download het foutbestand voor de volledige lijst."] }))] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: downloadErrors, children: [_jsx(Download, { className: "mr-2 h-4 w-4" }), "Download foutbestand"] })] }))] }))] }), _jsx(DialogFooter, { children: _jsx(Button, { variant: "ghost", onClick: () => {
|
|
433
|
+
} }), importing && (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "h-2 w-full overflow-hidden rounded bg-muted", children: _jsx("div", { className: "h-full bg-primary transition-all", style: { width: `${progress}%` } }) }), _jsxs("p", { className: "text-xs text-muted-foreground flex items-center gap-2", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), "Bezig met importeren\u2026 ", progress, "%"] })] })), result && (_jsxs("div", { className: "rounded-md border bg-muted/30 p-3 text-sm space-y-2", children: [_jsx("div", { className: "font-medium", children: "Importoverzicht" }), _jsxs("ul", { className: "text-xs space-y-1", children: [_jsxs("li", { children: ["Bestaande artikelen bijgewerkt:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.updated })] }), _jsxs("li", { children: ["Nieuwe artikelen aangemaakt:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.created })] }), _jsxs("li", { children: ["Automatisch gekoppeld met Floriday:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.linked })] }), _jsxs("li", { children: ["Gepubliceerd naar Floriday:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.published })] }), _jsxs("li", { children: ["Wacht op Floriday-publicatie (gegevens incompleet):", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.waiting })] }), _jsxs("li", { children: ["Fouten:", " ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: result.errors.length })] })] }), result.errors.length > 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "max-h-40 overflow-y-auto rounded border bg-background p-2 text-xs space-y-1", children: [result.errors.slice(0, 20).map((e, i) => (_jsxs("div", { className: "text-destructive", children: ["Regel ", e.row, " \u2014 ", e.articleCode || "(geen code)", " \u2014", " ", e.reden] }, i))), result.errors.length > 20 && (_jsxs("div", { className: "text-muted-foreground", children: ["\u2026 en ", result.errors.length - 20, " meer. Download het foutbestand voor de volledige lijst."] }))] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: downloadErrors, children: [_jsx(Download, { className: "mr-2 h-4 w-4" }), "Download foutbestand"] })] }))] })), !importing && pastRuns.length > 0 && (_jsxs("div", { className: "rounded-md border bg-background p-3 text-sm space-y-2", children: [_jsx("div", { className: "font-medium", children: "Recente imports" }), _jsx("ul", { className: "text-xs space-y-1 max-h-48 overflow-y-auto", children: pastRuns.map((r) => (_jsxs("li", { className: "flex justify-between gap-2 border-b last:border-0 py-1", children: [_jsxs("span", { className: "truncate", children: [_jsx("span", { className: "text-muted-foreground", children: new Date(r.created_at).toLocaleString("nl-NL") }), " ", "\u2014 ", r.filename ?? "(zonder naam)"] }), _jsxs("span", { className: "shrink-0 tabular-nums text-muted-foreground", children: [r.updated_rows, "\u21BB \u00B7 ", r.created_rows, "+ \u00B7 ", r.skipped_rows, "\u2715"] })] }, r.id))) })] }))] }), _jsx(DialogFooter, { children: _jsx(Button, { variant: "ghost", onClick: () => {
|
|
353
434
|
setOpen(false);
|
|
354
435
|
setResult(null);
|
|
355
436
|
}, disabled: importing, children: "Sluiten" }) })] }) })] }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTradeItemSpec.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemSpec.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,eAAe,CAAC;AAEzE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,aAAa,EAAE,uBAAuB,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,eAAe,EAAE,kBAAkB,EAAE,CAAC;IACtC,2DAA2D;IAC3D,2BAA2B,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzC,0FAA0F;IAC1F,2BAA2B,EAAE,OAAO,CAAC;IACrC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mGAAmG;IACnG,0BAA0B,EAAE,OAAO,CAAC;IACpC,4FAA4F;IAC5F,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,uBAAuB,EAAE,OAAO,CAAC;CAClC;AA8bD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAC/C,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,cAAc,GAAG,SAAS,EAClC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,YAAY,GAAG,IAAI,EACjC,kBAAkB,CAAC,EAAE,sBAAsB,EAAE,GAAG,IAAI,GACnD,aAAa,
|
|
1
|
+
{"version":3,"file":"useTradeItemSpec.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemSpec.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,eAAe,CAAC;AAEzE,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;IAClB,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,yEAAyE;IACzE,aAAa,EAAE,uBAAuB,EAAE,CAAC;CAC1C;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,eAAe,EAAE,kBAAkB,EAAE,CAAC;IACtC,2DAA2D;IAC3D,2BAA2B,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACzC,0FAA0F;IAC1F,2BAA2B,EAAE,OAAO,CAAC;IACrC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,mGAAmG;IACnG,0BAA0B,EAAE,OAAO,CAAC;IACpC,4FAA4F;IAC5F,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,uBAAuB,EAAE,OAAO,CAAC;CAClC;AA8bD,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,CAAC;IAC/C,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,cAAc,GAAG,SAAS,EAClC,OAAO,EAAE,MAAM,EACf,YAAY,EAAE,YAAY,GAAG,IAAI,EACjC,kBAAkB,CAAC,EAAE,sBAAsB,EAAE,GAAG,IAAI,GACnD,aAAa,CAsEf"}
|
|
@@ -402,7 +402,10 @@ export function useTradeItemSpec(schema, vbnCode, productGroup, regulatoryFeatur
|
|
|
402
402
|
fromRegulatory = regulatoryFeatures.map((f) => ({
|
|
403
403
|
code: f.code,
|
|
404
404
|
label: f.label,
|
|
405
|
-
|
|
405
|
+
// Floriday's server-side validator rejeert ook "advised" karakteristieken
|
|
406
|
+
// (bv. S62) met "Characteristic X is required". Behandel required+advised
|
|
407
|
+
// beide als verplicht om stille 400's te voorkomen.
|
|
408
|
+
required: f.regulation === "required" || f.regulation === "advised",
|
|
406
409
|
listName: f.code,
|
|
407
410
|
allowedValues: f.values,
|
|
408
411
|
}));
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type TradeItem } from "../lib/floriday-client";
|
|
2
2
|
/**
|
|
3
|
-
* Reads trade items from
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* Reads trade items from three sources and merges them into one voorraad list:
|
|
4
|
+
* 1. `products` rows tied to a `product:<id>` key (pure ERP items).
|
|
5
|
+
* 2. `trade_item_drafts` (lokaal concept, mogelijk in publiceerflow).
|
|
6
|
+
* 3. `floriday_trade_items_cache` (gepubliceerd artikel).
|
|
7
|
+
* Voorraadwaarden komen uit `trade_item_stock` en overschrijven cache-defaults.
|
|
7
8
|
*/
|
|
8
9
|
export declare function useVoorraadData(connectionId: string | null): {
|
|
9
10
|
items: TradeItem[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useVoorraadData.d.ts","sourceRoot":"","sources":["../../src/hooks/useVoorraadData.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"useVoorraadData.d.ts","sourceRoot":"","sources":["../../src/hooks/useVoorraadData.ts"],"names":[],"mappings":"AAEA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAkD5E;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;;;;;EA6E1D"}
|