@flowselections/floriday-voorraad 1.0.145 → 1.0.149
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 +3 -1
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelWizard.js +56 -52
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/EditArtikelPage.js +15 -23
- package/dist-lib/components/voorraad/OrderHistoryCard.d.ts +3 -1
- package/dist-lib/components/voorraad/OrderHistoryCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/OrderHistoryCard.js +7 -3
- package/dist-lib/components/voorraad/ProductCustomFieldsCard.d.ts +5 -1
- package/dist-lib/components/voorraad/ProductCustomFieldsCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ProductCustomFieldsCard.js +37 -18
- package/dist-lib/components/voorraad/ProductSalePriceField.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ProductSalePriceField.js +11 -8
- package/dist-lib/components/voorraad/StepSamenstelling.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StepSamenstelling.js +35 -10
- package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts +3 -1
- package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockMutationHistoryCard.js +16 -10
- package/dist-lib/components/voorraad/TrayStickerCard.d.ts +5 -1
- package/dist-lib/components/voorraad/TrayStickerCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/TrayStickerCard.js +64 -66
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts +0 -5
- package/dist-lib/components/voorraad/ValidationOverviewTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ValidationOverviewTable.js +10 -4
- package/dist-lib/components/voorraad/VoorraadBeheerCard.d.ts +3 -0
- package/dist-lib/components/voorraad/VoorraadBeheerCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/VoorraadBeheerCard.js +44 -0
- package/dist-lib/components/voorraad/VoorraadPage.d.ts +1 -13
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +102 -140
- package/dist-lib/components/voorraad/VoorraadSturingCard.d.ts +6 -0
- package/dist-lib/components/voorraad/VoorraadSturingCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/VoorraadSturingCard.js +47 -0
- package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts +6 -0
- package/dist-lib/components/voorraad/artikel/ArtikelPagina.d.ts.map +1 -0
- package/dist-lib/components/voorraad/artikel/ArtikelPagina.js +313 -0
- package/dist-lib/components/voorraad/artikel/TwijfelKiezen.d.ts +7 -0
- package/dist-lib/components/voorraad/artikel/TwijfelKiezen.d.ts.map +1 -0
- package/dist-lib/components/voorraad/artikel/TwijfelKiezen.js +54 -0
- package/dist-lib/components/voorraad/artikel/opslaan-context.d.ts +21 -0
- package/dist-lib/components/voorraad/artikel/opslaan-context.d.ts.map +1 -0
- package/dist-lib/components/voorraad/artikel/opslaan-context.js +15 -0
- package/dist-lib/components/voorraad/lijst/ImporteerKnop.d.ts +5 -0
- package/dist-lib/components/voorraad/lijst/ImporteerKnop.d.ts.map +1 -0
- package/dist-lib/components/voorraad/lijst/ImporteerKnop.js +20 -0
- package/dist-lib/components/voorraad/lijst/LijstExportKnop.d.ts +6 -0
- package/dist-lib/components/voorraad/lijst/LijstExportKnop.d.ts.map +1 -0
- package/dist-lib/components/voorraad/lijst/LijstExportKnop.js +43 -0
- package/dist-lib/components/voorraad/lijst/SnelActieDialogen.d.ts +16 -0
- package/dist-lib/components/voorraad/lijst/SnelActieDialogen.d.ts.map +1 -0
- package/dist-lib/components/voorraad/lijst/SnelActieDialogen.js +95 -0
- package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts +21 -0
- package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.d.ts.map +1 -0
- package/dist-lib/components/voorraad/lijst/VoorraadLijstTabel.js +39 -0
- package/dist-lib/hooks/useVoorraadCustomFields.d.ts +2 -1
- package/dist-lib/hooks/useVoorraadCustomFields.d.ts.map +1 -1
- package/dist-lib/hooks/useVoorraadCustomFields.js +8 -4
- package/dist-lib/hooks/useVoorraadLijst.d.ts +21 -0
- package/dist-lib/hooks/useVoorraadLijst.d.ts.map +1 -0
- package/dist-lib/hooks/useVoorraadLijst.js +71 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +4 -0
- package/dist-lib/integrations/supabase/types.d.ts +748 -1
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/artikel-gewijzigd.d.ts +4 -0
- package/dist-lib/lib/artikel-gewijzigd.d.ts.map +1 -0
- package/dist-lib/lib/artikel-gewijzigd.js +7 -0
- package/dist-lib/lib/artikel-pagina.d.ts +68 -0
- package/dist-lib/lib/artikel-pagina.d.ts.map +1 -0
- package/dist-lib/lib/artikel-pagina.js +97 -0
- package/dist-lib/lib/count-stock.d.ts +17 -0
- package/dist-lib/lib/count-stock.d.ts.map +1 -0
- package/dist-lib/lib/count-stock.js +26 -0
- package/dist-lib/lib/floriday-import-validator.d.ts +9 -0
- package/dist-lib/lib/floriday-import-validator.d.ts.map +1 -1
- package/dist-lib/lib/floriday-import-validator.js +13 -0
- package/dist-lib/lib/floriday-versie-ronde.d.ts +59 -0
- package/dist-lib/lib/floriday-versie-ronde.d.ts.map +1 -0
- package/dist-lib/lib/floriday-versie-ronde.functions.d.ts +10 -0
- package/dist-lib/lib/floriday-versie-ronde.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-versie-ronde.functions.js +22 -0
- package/dist-lib/lib/floriday-versie-ronde.js +106 -0
- package/dist-lib/lib/pending-photos.d.ts +20 -0
- package/dist-lib/lib/pending-photos.d.ts.map +1 -0
- package/dist-lib/lib/pending-photos.js +90 -0
- package/dist-lib/lib/voorraad-custom-fields.d.ts +16 -1
- package/dist-lib/lib/voorraad-custom-fields.d.ts.map +1 -1
- package/dist-lib/lib/voorraad-custom-fields.js +12 -6
- package/dist-lib/lib/voorraad-lijst.d.ts +92 -0
- package/dist-lib/lib/voorraad-lijst.d.ts.map +1 -0
- package/dist-lib/lib/voorraad-lijst.js +86 -0
- package/dist-lib/server-hooks/floriday-stock-reconcile.d.ts.map +1 -1
- package/dist-lib/server-hooks/floriday-stock-reconcile.js +5 -0
- package/dist-lib/server-hooks/floriday-versieronde.d.ts +2 -0
- package/dist-lib/server-hooks/floriday-versieronde.d.ts.map +1 -0
- package/dist-lib/server-hooks/floriday-versieronde.js +29 -0
- package/dist-lib/server-hooks/guard.js +1 -1
- package/package.json +1 -1
- package/sql/AGENTS.md +6 -0
- package/sql/floriday-voorraad-schema.sql +1355 -22
|
@@ -94,6 +94,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
94
94
|
const [loading, setLoading] = useState(true);
|
|
95
95
|
const [saving, setSaving] = useState(false);
|
|
96
96
|
const [rows, setRows] = useState([]);
|
|
97
|
+
const [teVerwijderen, setTeVerwijderen] = useState([]);
|
|
97
98
|
const [names, setNames] = useState(new Map());
|
|
98
99
|
const [activeTab, setActiveTab] = useState("__base__");
|
|
99
100
|
const hasParent = Boolean(productId || floridayId);
|
|
@@ -131,8 +132,10 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
131
132
|
component_trade_item_floriday_id: r.component_trade_item_floriday_id ?? null,
|
|
132
133
|
quantity: Number(r.quantity ?? 1),
|
|
133
134
|
sort_order: Number(r.sort_order ?? 0),
|
|
135
|
+
_orig: { quantity: Number(r.quantity ?? 1), sort_order: Number(r.sort_order ?? 0) },
|
|
134
136
|
}));
|
|
135
137
|
setRows(list);
|
|
138
|
+
setTeVerwijderen([]);
|
|
136
139
|
await resolveNames(list);
|
|
137
140
|
setLoading(false);
|
|
138
141
|
}, [productId, floridayId, hasParent]);
|
|
@@ -230,13 +233,9 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
230
233
|
const row = rows.find((r) => r.id === id);
|
|
231
234
|
if (!row)
|
|
232
235
|
return;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
toast.error("Verwijderen mislukt");
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
236
|
+
// Verwijderen gebeurt pas bij "Opslaan" (bewuste actie met conflictcontrole).
|
|
237
|
+
if (!row._new)
|
|
238
|
+
setTeVerwijderen((l) => [...l, row]);
|
|
240
239
|
setRows((prev) => prev.filter((r) => r.id !== id));
|
|
241
240
|
};
|
|
242
241
|
const saveAll = async () => {
|
|
@@ -245,7 +244,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
245
244
|
return;
|
|
246
245
|
}
|
|
247
246
|
const dirty = rows.filter((r) => r._dirty);
|
|
248
|
-
if (dirty.length === 0)
|
|
247
|
+
if (dirty.length === 0 && teVerwijderen.length === 0)
|
|
249
248
|
return;
|
|
250
249
|
setSaving(true);
|
|
251
250
|
// Verhoudingscontrole: alle lagen meetellen, nooit blokkeren.
|
|
@@ -264,6 +263,32 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
264
263
|
// Controle mislukt: opslaan gaat gewoon door.
|
|
265
264
|
}
|
|
266
265
|
try {
|
|
266
|
+
// Conflictcontrole: bestaande regels die je wijzigt of verwijdert moeten nog
|
|
267
|
+
// dezelfde stand hebben als bij openen.
|
|
268
|
+
const gecontroleerd = [...dirty.filter((r) => !r._new), ...teVerwijderen].filter((r) => r._orig);
|
|
269
|
+
if (gecontroleerd.length) {
|
|
270
|
+
const { data: nu, error: nuErr } = await supabase
|
|
271
|
+
.from("product_bom_items")
|
|
272
|
+
.select("id,quantity,sort_order")
|
|
273
|
+
.in("id", gecontroleerd.map((r) => r.id));
|
|
274
|
+
if (nuErr)
|
|
275
|
+
throw nuErr;
|
|
276
|
+
const huidig = new Map((nu ?? []).map((r) => [r.id, r]));
|
|
277
|
+
const afwijkend = gecontroleerd.filter((r) => {
|
|
278
|
+
const h = huidig.get(r.id);
|
|
279
|
+
return !h || Number(h.quantity) !== r._orig.quantity || Number(h.sort_order) !== r._orig.sort_order;
|
|
280
|
+
});
|
|
281
|
+
if (afwijkend.length) {
|
|
282
|
+
toast.error("Iemand anders heeft deze samenstelling intussen gewijzigd. Er is niets opgeslagen; laad de samenstelling opnieuw.");
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
for (const r of teVerwijderen) {
|
|
287
|
+
const { error } = await supabase.from("product_bom_items").delete().eq("id", r.id);
|
|
288
|
+
if (error)
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
setTeVerwijderen([]);
|
|
267
292
|
const inserts = dirty
|
|
268
293
|
.filter((r) => r._new)
|
|
269
294
|
.map((r) => ({
|
|
@@ -309,7 +334,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
309
334
|
if (error)
|
|
310
335
|
throw error;
|
|
311
336
|
}
|
|
312
|
-
setRows((prev) => prev.map((r) => ({ ...r, _dirty: false })));
|
|
337
|
+
setRows((prev) => prev.map((r) => ({ ...r, _dirty: false, _orig: { quantity: r.quantity, sort_order: r.sort_order } })));
|
|
313
338
|
toast.success("Samenstelling opgeslagen");
|
|
314
339
|
}
|
|
315
340
|
catch (e) {
|
|
@@ -319,7 +344,7 @@ export function StepSamenstelling({ productId, floridayId, packings, packagingTy
|
|
|
319
344
|
setSaving(false);
|
|
320
345
|
}
|
|
321
346
|
};
|
|
322
|
-
const hasUnsaved = rows.some((r) => r._dirty);
|
|
347
|
+
const hasUnsaved = rows.some((r) => r._dirty) || teVerwijderen.length > 0;
|
|
323
348
|
if (!hasParent) {
|
|
324
349
|
return (_jsx("div", { className: "rounded-md border border-dashed p-6 text-center text-sm text-muted-foreground", children: "Sla het artikel eerst op om een samenstelling te configureren." }));
|
|
325
350
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
interface Props {
|
|
2
2
|
tradeItemKey: string | null;
|
|
3
3
|
productId?: string | null;
|
|
4
|
+
/** Sleutels uit voorraad_artikel_detail; scheelt een aparte aanvraag bij het openen. */
|
|
5
|
+
sleutels?: string[];
|
|
4
6
|
}
|
|
5
|
-
export declare function StockMutationHistoryCard({ tradeItemKey, productId }: Props): import("react").JSX.Element;
|
|
7
|
+
export declare function StockMutationHistoryCard({ tradeItemKey, productId, sleutels }: Props): import("react").JSX.Element;
|
|
6
8
|
export {};
|
|
7
9
|
//# sourceMappingURL=StockMutationHistoryCard.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StockMutationHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockMutationHistoryCard.tsx"],"names":[],"mappings":"AAYA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"StockMutationHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockMutationHistoryCard.tsx"],"names":[],"mappings":"AAYA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wFAAwF;IACxF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAiDD,wBAAgB,wBAAwB,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,KAAK,+BAgPpF"}
|
|
@@ -42,7 +42,7 @@ function formatMoment(v) {
|
|
|
42
42
|
return v;
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
45
|
+
export function StockMutationHistoryCard({ tradeItemKey, productId, sleutels }) {
|
|
46
46
|
const [rows, setRows] = useState(null);
|
|
47
47
|
const [error, setError] = useState(null);
|
|
48
48
|
const [expanded, setExpanded] = useState(false);
|
|
@@ -60,14 +60,20 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
|
60
60
|
keys.add(tradeItemKey);
|
|
61
61
|
if (productId)
|
|
62
62
|
keys.add(productId);
|
|
63
|
-
// Groepssleutels waaronder dit artikel ook afgeboekt kan worden.
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
63
|
+
// Groepssleutels waaronder dit artikel ook afgeboekt kan worden (meestal al meegeleverd).
|
|
64
|
+
if (sleutels) {
|
|
65
|
+
for (const k of sleutels)
|
|
66
|
+
keys.add(k);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const { data: groups } = await supabase
|
|
70
|
+
.from("product_group_items")
|
|
71
|
+
.select("product_group_id")
|
|
72
|
+
.in("trade_item_id", [...keys])
|
|
73
|
+
.limit(20);
|
|
74
|
+
for (const g of (groups ?? [])) {
|
|
75
|
+
keys.add(`group:${g.product_group_id}`);
|
|
76
|
+
}
|
|
71
77
|
}
|
|
72
78
|
const { data, error } = await supabase
|
|
73
79
|
.from("stock_mutation_log")
|
|
@@ -135,7 +141,7 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
|
135
141
|
cancelled = true;
|
|
136
142
|
supabase.removeChannel(channel);
|
|
137
143
|
};
|
|
138
|
-
}, [tradeItemKey, productId]);
|
|
144
|
+
}, [tradeItemKey, productId, sleutels?.join("|")]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
139
145
|
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 && (_jsx("div", { className: "text-sm text-destructive", children: 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 && (_jsxs("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: "Door" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Reden" })] }) }), _jsx("tbody", { children: (expanded ? rows : rows.slice(0, 5)).map((r) => {
|
|
140
146
|
const meta = TYPE_LABEL[r.mutation_type] ?? {
|
|
141
147
|
label: r.mutation_type || "Onbekend",
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Traysticker-afbeelding per artikel — los van de Floriday-productfoto.
|
|
3
|
+
* Kiezen of verwijderen wordt pas bewaard met de knop "Opslaan" van de pagina,
|
|
4
|
+
* met controle tegen de stand bij openen.
|
|
5
|
+
*/
|
|
2
6
|
export declare function TrayStickerCard({ productId }: {
|
|
3
7
|
productId: string;
|
|
4
8
|
}): import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TrayStickerCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/TrayStickerCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TrayStickerCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/TrayStickerCard.tsx"],"names":[],"mappings":"AA4BA;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,+BAoInE"}
|
|
@@ -1,100 +1,98 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from "react";
|
|
3
|
-
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle,
|
|
4
|
-
import { Loader2, Trash2, Upload } from "lucide-react";
|
|
3
|
+
import { Button, Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@flowselections/core";
|
|
4
|
+
import { Loader2, Trash2, Undo2, Upload } from "lucide-react";
|
|
5
5
|
import { supabase } from "../../integrations/supabase/client.js";
|
|
6
|
+
import { useOpslagDeelnemer } from "./artikel/opslaan-context.js";
|
|
6
7
|
const BUCKET = "product-tray-stickers";
|
|
7
8
|
// Bucket is privé (publieke buckets zijn geblokkeerd): lange signed URL voor de logistiek-engine.
|
|
8
9
|
const SIGNED_URL_SECONDS = 60 * 60 * 24 * 365 * 10;
|
|
9
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Traysticker-afbeelding per artikel — los van de Floriday-productfoto.
|
|
12
|
+
* Kiezen of verwijderen wordt pas bewaard met de knop "Opslaan" van de pagina,
|
|
13
|
+
* met controle tegen de stand bij openen.
|
|
14
|
+
*/
|
|
10
15
|
export function TrayStickerCard({ productId }) {
|
|
11
16
|
const [row, setRow] = useState(null);
|
|
12
17
|
const [loading, setLoading] = useState(true);
|
|
13
|
-
const [
|
|
18
|
+
const [wachtend, setWachtend] = useState(null);
|
|
14
19
|
const inputRef = useRef(null);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
setLoading(true);
|
|
18
|
-
supabase
|
|
20
|
+
const laad = async () => {
|
|
21
|
+
const { data } = await supabase
|
|
19
22
|
.from("product_tray_stickers")
|
|
20
23
|
.select("*")
|
|
21
24
|
.eq("product_id", productId)
|
|
22
|
-
.maybeSingle()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
.maybeSingle();
|
|
26
|
+
setRow(data ?? null);
|
|
27
|
+
};
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let cancelled = false;
|
|
30
|
+
setLoading(true);
|
|
31
|
+
void laad().finally(() => {
|
|
26
32
|
if (!cancelled)
|
|
27
33
|
setLoading(false);
|
|
28
34
|
});
|
|
29
35
|
return () => {
|
|
30
36
|
cancelled = true;
|
|
31
37
|
};
|
|
38
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
32
39
|
}, [productId]);
|
|
33
|
-
|
|
34
|
-
|
|
40
|
+
useEffect(() => () => {
|
|
41
|
+
if (wachtend?.soort === "nieuw")
|
|
42
|
+
URL.revokeObjectURL(wachtend.voorbeeld);
|
|
43
|
+
}, [wachtend]);
|
|
44
|
+
const opslaan = async () => {
|
|
45
|
+
if (!wachtend)
|
|
46
|
+
return { ok: true };
|
|
47
|
+
let pad = null;
|
|
35
48
|
try {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
let next = null;
|
|
50
|
+
if (wachtend.soort === "nieuw") {
|
|
51
|
+
const file = wachtend.file;
|
|
52
|
+
const ext = file.name.includes(".") ? file.name.slice(file.name.lastIndexOf(".")) : "";
|
|
53
|
+
pad = `${productId}/${crypto.randomUUID()}${ext}`;
|
|
54
|
+
const up = await supabase.storage.from(BUCKET).upload(pad, file, { contentType: file.type || undefined });
|
|
55
|
+
if (up.error)
|
|
56
|
+
throw up.error;
|
|
57
|
+
const signed = await supabase.storage.from(BUCKET).createSignedUrl(pad, SIGNED_URL_SECONDS);
|
|
58
|
+
if (signed.error || !signed.data)
|
|
59
|
+
throw signed.error ?? new Error("Geen URL");
|
|
60
|
+
next = { product_id: productId, bestand_url: signed.data.signedUrl, bestandspad: pad, bestandsnaam: file.name, bestandsgrootte: file.size };
|
|
61
|
+
}
|
|
62
|
+
const { data, error } = await supabase.rpc("voorraad_traysticker_opslaan", {
|
|
63
|
+
p_product_id: productId,
|
|
64
|
+
p_oud_pad: row?.bestandspad ?? null,
|
|
65
|
+
p_nieuw: next,
|
|
40
66
|
});
|
|
41
|
-
if (up.error)
|
|
42
|
-
throw up.error;
|
|
43
|
-
const signed = await supabase.storage.from(BUCKET).createSignedUrl(path, SIGNED_URL_SECONDS);
|
|
44
|
-
if (signed.error || !signed.data)
|
|
45
|
-
throw signed.error ?? new Error("Geen URL");
|
|
46
|
-
const next = {
|
|
47
|
-
product_id: productId,
|
|
48
|
-
bestand_url: signed.data.signedUrl,
|
|
49
|
-
bestandspad: path,
|
|
50
|
-
bestandsnaam: file.name,
|
|
51
|
-
bestandsgrootte: file.size,
|
|
52
|
-
};
|
|
53
|
-
const { error } = await supabase
|
|
54
|
-
.from("product_tray_stickers")
|
|
55
|
-
.upsert(next, { onConflict: "product_id" });
|
|
56
67
|
if (error)
|
|
57
68
|
throw error;
|
|
69
|
+
if (!data.ok) {
|
|
70
|
+
if (pad)
|
|
71
|
+
await supabase.storage.from(BUCKET).remove([pad]);
|
|
72
|
+
setWachtend(null);
|
|
73
|
+
await laad();
|
|
74
|
+
return { ok: false, melding: "Traysticker niet opgeslagen: iemand anders wijzigde hem intussen. De nieuwste stand is geladen." };
|
|
75
|
+
}
|
|
58
76
|
if (row?.bestandspad)
|
|
59
77
|
await supabase.storage.from(BUCKET).remove([row.bestandspad]);
|
|
60
78
|
setRow(next);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
catch (e) {
|
|
64
|
-
toast.error(`Uploaden mislukt: ${e.message}`);
|
|
65
|
-
}
|
|
66
|
-
finally {
|
|
67
|
-
setBusy(false);
|
|
68
|
-
if (inputRef.current)
|
|
69
|
-
inputRef.current.value = "";
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
const remove = async () => {
|
|
73
|
-
if (!row)
|
|
74
|
-
return;
|
|
75
|
-
setBusy(true);
|
|
76
|
-
try {
|
|
77
|
-
const { error } = await supabase
|
|
78
|
-
.from("product_tray_stickers")
|
|
79
|
-
.delete()
|
|
80
|
-
.eq("product_id", productId);
|
|
81
|
-
if (error)
|
|
82
|
-
throw error;
|
|
83
|
-
if (row.bestandspad)
|
|
84
|
-
await supabase.storage.from(BUCKET).remove([row.bestandspad]);
|
|
85
|
-
setRow(null);
|
|
86
|
-
toast.success("Traysticker-afbeelding verwijderd");
|
|
79
|
+
setWachtend(null);
|
|
80
|
+
return { ok: true };
|
|
87
81
|
}
|
|
88
82
|
catch (e) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
setBusy(false);
|
|
83
|
+
if (pad)
|
|
84
|
+
await supabase.storage.from(BUCKET).remove([pad]);
|
|
85
|
+
return { ok: false, melding: `Traysticker: opslaan mislukt (${e.message}).` };
|
|
93
86
|
}
|
|
94
87
|
};
|
|
95
|
-
|
|
88
|
+
useOpslagDeelnemer("traysticker", wachtend !== null, opslaan);
|
|
89
|
+
const toonUrl = wachtend?.soort === "nieuw" ? wachtend.voorbeeld : wachtend?.soort === "weg" ? null : row?.bestand_url;
|
|
90
|
+
const toonNaam = wachtend?.soort === "nieuw" ? wachtend.file.name : row?.bestandsnaam;
|
|
91
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Traysticker-afbeelding" }), _jsx(CardDescription, { children: "Afbeelding die op het traysticker-etiket bij de pakbon op het pickstation wordt geprint. Dit staat los van de productfoto uit Floriday." })] }), _jsxs(CardContent, { className: "space-y-4", children: [loading ? (_jsx(Loader2, { className: "h-4 w-4 animate-spin text-muted-foreground" })) : toonUrl ? (_jsxs("div", { className: "flex items-center gap-4", children: [_jsx("img", { src: toonUrl, alt: "Traysticker", className: "h-24 w-24 rounded border object-contain bg-muted" }), _jsx("div", { className: "text-sm text-muted-foreground", children: _jsx("div", { children: toonNaam }) })] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: wachtend?.soort === "weg" ? "Wordt verwijderd bij Opslaan." : "Nog geen traysticker-afbeelding." })), wachtend && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Niet opgeslagen wijziging; bewaar met \"Opslaan\" bovenaan." })), _jsx("input", { ref: inputRef, type: "file", accept: "image/*", className: "hidden", onChange: (e) => {
|
|
96
92
|
const f = e.target.files?.[0];
|
|
97
93
|
if (f)
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
setWachtend({ soort: "nieuw", file: f, voorbeeld: URL.createObjectURL(f) });
|
|
95
|
+
if (inputRef.current)
|
|
96
|
+
inputRef.current.value = "";
|
|
97
|
+
} }), _jsxs("div", { className: "flex gap-2", children: [_jsxs(Button, { variant: "outline", onClick: () => inputRef.current?.click(), disabled: loading, children: [_jsx(Upload, { className: "mr-2 h-4 w-4" }), row || wachtend?.soort === "nieuw" ? "Andere kiezen" : "Afbeelding kiezen"] }), row && wachtend?.soort !== "weg" && (_jsxs(Button, { variant: "outline", onClick: () => setWachtend({ soort: "weg" }), children: [_jsx(Trash2, { className: "mr-2 h-4 w-4" }), "Verwijderen"] })), wachtend && (_jsxs(Button, { variant: "ghost", onClick: () => setWachtend(null), children: [_jsx(Undo2, { className: "mr-2 h-4 w-4" }), "Ongedaan maken"] }))] })] })] }));
|
|
100
98
|
}
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
import type { TradeItem } from "../../lib/floriday-client.js";
|
|
2
|
-
/**
|
|
3
|
-
* Overzicht van producten die (nog) niet actief op Floriday staan. Er is geen concept-status
|
|
4
|
-
* meer — het gaat puur om welke Floriday-velden ontbreken en of het product klaar is om te
|
|
5
|
-
* activeren. Producten blijven volwaardig in gebruik binnen ERP, ongeacht Floriday-status.
|
|
6
|
-
*/
|
|
7
2
|
export declare function ValidationOverviewTable({ items, connectionId, onChanged, }: {
|
|
8
3
|
items: TradeItem[];
|
|
9
4
|
connectionId: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ValidationOverviewTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationOverviewTable.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"ValidationOverviewTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ValidationOverviewTable.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAoB3D,wBAAgB,uBAAuB,CAAC,EACtC,KAAK,EACL,YAAY,EACZ,SAAS,GACV,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC,+BAwKA"}
|
|
@@ -3,7 +3,7 @@ import { useMemo, useState } from "react";
|
|
|
3
3
|
import { useNavigate } from "@tanstack/react-router";
|
|
4
4
|
import { Badge, Button } from "@flowselections/core";
|
|
5
5
|
import { Link2, Loader2, Pencil, Send } from "lucide-react";
|
|
6
|
-
import { validateDraftForPublish } from "../../lib/floriday-import-validator.js";
|
|
6
|
+
import { conceptMetArtikel, validateDraftForPublish } from "../../lib/floriday-import-validator.js";
|
|
7
7
|
import { updateDraftMeta } from "../../hooks/useLocalTradeItems.js";
|
|
8
8
|
import { floriday } from "../../lib/floriday-client.js";
|
|
9
9
|
import { relinkProducts } from "../../lib/floriday-relink.js";
|
|
@@ -13,6 +13,12 @@ import { toast } from "@flowselections/core";
|
|
|
13
13
|
* meer — het gaat puur om welke Floriday-velden ontbreken en of het product klaar is om te
|
|
14
14
|
* activeren. Producten blijven volwaardig in gebruik binnen ERP, ongeacht Floriday-status.
|
|
15
15
|
*/
|
|
16
|
+
/** Eigen naam en artikelnummer van het artikel (niet het Floriday-concept). */
|
|
17
|
+
function artikelVan(it) {
|
|
18
|
+
const raw = (it.raw ?? {});
|
|
19
|
+
const s = (v) => (typeof v === "string" && v.trim() ? v : null);
|
|
20
|
+
return { name: s(raw.product) ?? s(it.name), articleNumber: s(raw.article_number) ?? s(it.articleNumber) };
|
|
21
|
+
}
|
|
16
22
|
export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
|
|
17
23
|
const navigate = useNavigate();
|
|
18
24
|
const [relinking, setRelinking] = useState(false);
|
|
@@ -53,14 +59,14 @@ export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
|
|
|
53
59
|
item.raw?.floriday_payload ??
|
|
54
60
|
item.raw?.data ??
|
|
55
61
|
{};
|
|
56
|
-
const v = validateDraftForPublish(payload);
|
|
62
|
+
const v = validateDraftForPublish(conceptMetArtikel(payload, artikelVan(item)));
|
|
57
63
|
if (!v.ok) {
|
|
58
64
|
toast.error("Nog niet compleet: " + v.missing.join(", "));
|
|
59
65
|
return;
|
|
60
66
|
}
|
|
61
67
|
try {
|
|
62
68
|
const { buildAddTradeItemPayload } = await import("../../lib/floriday-payload.js");
|
|
63
|
-
const built = buildAddTradeItemPayload(payload);
|
|
69
|
+
const built = buildAddTradeItemPayload(conceptMetArtikel(payload, artikelVan(item)));
|
|
64
70
|
const res = await floriday.createTradeItem(connectionId, built);
|
|
65
71
|
await updateDraftMeta(productId, {
|
|
66
72
|
floridayLinkStatus: "published",
|
|
@@ -85,7 +91,7 @@ export function ValidationOverviewTable({ items, connectionId, onChanged, }) {
|
|
|
85
91
|
it.raw?.floriday_payload ??
|
|
86
92
|
it.raw?.data ??
|
|
87
93
|
{};
|
|
88
|
-
const v = validateDraftForPublish(payload);
|
|
94
|
+
const v = validateDraftForPublish(conceptMetArtikel(payload, artikelVan(it)));
|
|
89
95
|
return (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "p-3 font-mono text-xs", children: it.articleNumber || "—" }), _jsx("td", { className: "p-3", children: it.name }), _jsx("td", { className: "p-3", children: _jsx(Badge, { className: "bg-slate-500/15 text-slate-700 border-slate-500/30", children: "Niet actief op Floriday" }) }), _jsx("td", { className: "p-3 text-xs text-muted-foreground max-w-[260px]", children: reasonFor(it) }), _jsx("td", { className: "p-3", children: v.missing.length === 0 ? (_jsx("span", { className: "text-xs text-emerald-700", children: "Klaar om te activeren" })) : (_jsxs("span", { className: "text-xs text-muted-foreground", title: v.missing.join(", "), children: ["Nog nodig: ", v.missing.slice(0, 3).join(", ").toLowerCase(), v.missing.length > 3 ? ` en nog ${v.missing.length - 3} andere` : "", "."] })) }), _jsx("td", { className: "p-3", children: _jsxs("div", { className: "flex justify-end gap-1", children: [_jsxs(Button, { size: "sm", variant: "outline", onClick: () => {
|
|
90
96
|
const target = it.productId ?? it.localId ?? it.id;
|
|
91
97
|
navigate({ to: "/voorraad/$id", params: { id: target } });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VoorraadBeheerCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadBeheerCard.tsx"],"names":[],"mappings":"AAuBA,mGAAmG;AACnG,wBAAgB,kBAAkB,uCAwDjC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useState } from "react";
|
|
3
|
+
import { Button, Card, CardContent, CardHeader, CardTitle, toast } from "@flowselections/core";
|
|
4
|
+
import { supabase } from "../../integrations/supabase/client.js";
|
|
5
|
+
import { dbFoutTekst } from "../../lib/db-error-message.js";
|
|
6
|
+
import { toonVersieBeheer } from "../../lib/floriday-versie-ronde.js";
|
|
7
|
+
import { floridayVersiesControleren } from "../../lib/floriday-versie-ronde.functions.js";
|
|
8
|
+
const STATUS_TEKST = {
|
|
9
|
+
geslaagd: "geslaagd",
|
|
10
|
+
onvolledig: "nog niet compleet (wordt automatisch opnieuw geprobeerd)",
|
|
11
|
+
mislukt: "mislukt (wordt automatisch opnieuw geprobeerd)",
|
|
12
|
+
};
|
|
13
|
+
/** Inklapbaar blok "Beheer": alleen zichtbaar voor beheerders, alleen informatie plus één knop. */
|
|
14
|
+
export function VoorraadBeheerCard() {
|
|
15
|
+
const [status, setStatus] = useState(null);
|
|
16
|
+
const [bezig, setBezig] = useState(false);
|
|
17
|
+
const laad = useCallback(async () => {
|
|
18
|
+
const { data, error } = await supabase.rpc("voorraad_floriday_versieronde_status");
|
|
19
|
+
if (error) {
|
|
20
|
+
setStatus(null);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
setStatus(data);
|
|
24
|
+
}, []);
|
|
25
|
+
useEffect(() => { void laad(); }, [laad]);
|
|
26
|
+
if (!toonVersieBeheer(status) || !status?.heeft_koppeling)
|
|
27
|
+
return null;
|
|
28
|
+
const start = async () => {
|
|
29
|
+
setBezig(true);
|
|
30
|
+
try {
|
|
31
|
+
await floridayVersiesControleren();
|
|
32
|
+
toast.success("Controle gestart. Dit loopt op de achtergrond.");
|
|
33
|
+
await laad();
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
toast.error(dbFoutTekst(e));
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
setBezig(false);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const l = status.laatste;
|
|
43
|
+
return (_jsx(Card, { children: _jsxs("details", { children: [_jsx("summary", { className: "cursor-pointer list-none", children: _jsx(CardHeader, { children: _jsx(CardTitle, { children: "Beheer" }) }) }), _jsxs(CardContent, { className: "space-y-2 text-sm", children: [_jsx("p", { className: "font-medium", children: "Controle van Floriday-artikelen" }), _jsx("p", { className: "text-muted-foreground", children: "Loopt vanzelf elke nacht. Oude versies van Floriday-artikelen worden geen zichtbaar artikel." }), _jsxs("p", { children: ["Laatste controle:", " ", l ? `${new Date(l.uitgevoerd_at).toLocaleString("nl-NL")} — ${STATUS_TEKST[l.status] ?? l.status}` : "nog niet uitgevoerd", status.bezig && " · nu bezig"] }), l?.melding && l.status !== "geslaagd" && _jsx("p", { className: "text-muted-foreground", children: l.melding }), _jsxs("p", { children: ["Vervangen in Floriday: ", status.vervangen ?? 0, " \u00B7 Vervallen: ", status.vervallen ?? 0, " \u00B7 Apart gezet (geen antwoord van Floriday): ", status.onbekend ?? 0] }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => void start(), disabled: bezig || status.bezig, children: bezig ? "Bezig…" : "Nu opnieuw controleren" })] })] }) }));
|
|
44
|
+
}
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* "Op voorraad" = artikelen met liggende voorraad (fysiek > 0).
|
|
4
|
-
* "Vrij beschikbaar" = artikelen waarvan liggend min gereserveerd > 0.
|
|
5
|
-
*/
|
|
6
|
-
export declare function countStock(items: Array<Pick<TradeItem, "quantity" | "availableQuantity" | "minQuantity">>): {
|
|
7
|
-
total: number;
|
|
8
|
-
inStock: number;
|
|
9
|
-
outOfStock: number;
|
|
10
|
-
belowMin: number;
|
|
11
|
-
fullyReserved: number;
|
|
12
|
-
freeAvailable: number;
|
|
13
|
-
};
|
|
1
|
+
export { countStock } from "../../lib/count-stock.js";
|
|
14
2
|
export declare function VoorraadPage(): import("react").JSX.Element;
|
|
15
3
|
//# sourceMappingURL=VoorraadPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AAwCA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AA2CnD,wBAAgB,YAAY,gCA6M3B"}
|