@flowselections/floriday-voorraad 1.0.92 → 1.0.96

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  2. package/dist-lib/components/voorraad/ArtikelWizard.js +21 -41
  3. package/dist-lib/components/voorraad/FloridayTakeoverPanel.d.ts.map +1 -1
  4. package/dist-lib/components/voorraad/FloridayTakeoverPanel.js +18 -1
  5. package/dist-lib/components/voorraad/StockBulkButtons.d.ts.map +1 -1
  6. package/dist-lib/components/voorraad/StockBulkButtons.js +59 -0
  7. package/dist-lib/components/voorraad/StockIntegrityAlert.d.ts.map +1 -1
  8. package/dist-lib/components/voorraad/StockIntegrityAlert.js +5 -5
  9. package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
  10. package/dist-lib/components/voorraad/VoorraadTable.js +16 -1
  11. package/dist-lib/integrations/supabase/types.d.ts +405 -0
  12. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  13. package/dist-lib/lib/__tests__/product-packings.test.d.ts +2 -0
  14. package/dist-lib/lib/__tests__/product-packings.test.d.ts.map +1 -0
  15. package/dist-lib/lib/__tests__/product-packings.test.js +42 -0
  16. package/dist-lib/lib/ensure-article-codes.d.ts.map +1 -1
  17. package/dist-lib/lib/ensure-article-codes.js +2 -12
  18. package/dist-lib/lib/floriday-payload.d.ts +15 -0
  19. package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
  20. package/dist-lib/lib/floriday-payload.js +36 -10
  21. package/dist-lib/lib/fusten.d.ts.map +1 -1
  22. package/dist-lib/lib/fusten.js +6 -1
  23. package/dist-lib/lib/product-packing.d.ts +8 -5
  24. package/dist-lib/lib/product-packing.d.ts.map +1 -1
  25. package/dist-lib/lib/product-packing.js +44 -36
  26. package/dist-lib/lib/product-packings.d.ts +33 -0
  27. package/dist-lib/lib/product-packings.d.ts.map +1 -0
  28. package/dist-lib/lib/product-packings.js +89 -0
  29. package/dist-lib/server-hooks/floriday-follow.d.ts.map +1 -1
  30. package/dist-lib/server-hooks/floriday-follow.js +22 -2
  31. package/package.json +1 -1
  32. package/sql/AGENTS.md +5 -0
  33. package/sql/floriday-voorraad-schema.sql +279 -7
@@ -1 +1 @@
1
- {"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA6MA,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,+BA6uEL"}
1
+ {"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA8MA,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,+BA0tEL"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { saveOwnPackings, packingRowsFromWizard } from "../../lib/product-packings.js";
2
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
4
  import { Link, useNavigate } from "@tanstack/react-router";
4
5
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, Button, Card, CardContent, Checkbox, Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Popover, PopoverContent, PopoverTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, toast, } from "@flowselections/core";
@@ -405,11 +406,12 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
405
406
  ? crypto.randomUUID()
406
407
  : `pk-${Date.now()}`,
407
408
  source: "VBN",
408
- loadCarrier: "DANISH_TROLLEY",
409
+ loadCarrier: "",
409
410
  packagingType: "",
410
- unitsPerPackage: ppc || 1,
411
- packagesPerLayer: cpl || 1,
412
- layersPerLoadCarrier: lpc || 1,
411
+ // Leeg laten wat niet bekend is; nooit aanvullen.
412
+ unitsPerPackage: ppc,
413
+ packagesPerLayer: cpl,
414
+ layersPerLoadCarrier: lpc,
413
415
  isPrimary: true,
414
416
  isCustomerSpecific: false,
415
417
  customerOrganizationIds: [],
@@ -1424,36 +1426,23 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1424
1426
  if (!item) {
1425
1427
  throw new Error("Het product kon niet worden teruggelezen");
1426
1428
  }
1427
- // Lokale kopie pas bijwerken nadat de handelsomgeving bevestigd heeft.
1428
- try {
1429
- await supabase
1430
- .from("floriday_trade_items_cache")
1431
- .update({
1432
- data: item,
1433
- fetched_at: new Date().toISOString(),
1434
- })
1435
- .eq("connection_id", activeId)
1436
- .eq("floriday_id", resolvedTradeItemId);
1437
- }
1438
- catch {
1439
- /* wordt automatisch bijgewerkt bij de volgende synchronisatie */
1440
- }
1429
+ // De Floriday-kopie is van Floriday-authenticatie; Voorraad schrijft er nooit in.
1441
1430
  // Floriday heeft de belading nu bevestigd: direct wegschrijven op
1442
1431
  // het product, zodat andere modules niet hoeven te wachten op de
1443
1432
  // 5-minuten-cron. De trigger/cron blijft het vangnet.
1444
1433
  const targetProductId = productId ?? draftId ?? null;
1445
1434
  const confirmedPacking = pickPrimaryPacking(item?.packingConfigurations);
1446
- if (targetProductId && confirmedPacking) {
1435
+ if (targetProductId) {
1447
1436
  try {
1448
1437
  await writeConfirmedPacking({
1449
1438
  productId: targetProductId,
1450
- packing: confirmedPacking,
1451
- source: "wizard_publish",
1439
+ connectionId: activeId,
1440
+ item,
1452
1441
  });
1453
1442
  setPackingStatus({
1454
- known: true,
1455
- confirmedAt: new Date().toISOString(),
1456
- source: "wizard_publish",
1443
+ known: Boolean(confirmedPacking),
1444
+ confirmedAt: confirmedPacking ? new Date().toISOString() : null,
1445
+ source: "product_packings:floriday",
1457
1446
  packing: confirmedPacking,
1458
1447
  });
1459
1448
  }
@@ -1553,6 +1542,10 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1553
1542
  if (draftFloridayId && data.warehouseId) {
1554
1543
  await saveWarehousePreference(draftFloridayId, data.warehouseId);
1555
1544
  }
1545
+ // Eigen artikel (zonder Floriday): verpakkingen uit de wizard opslaan.
1546
+ if (newId && !draftFloridayId && !publishedTradeItemId) {
1547
+ await saveOwnPackings(newId, packingRowsFromWizard(data.packingConfigurations));
1548
+ }
1556
1549
  setDraftId(newId);
1557
1550
  toast.success(draftId ? "Artikel bijgewerkt" : "Artikel opgeslagen");
1558
1551
  navigate({ to: "/voorraad" });
@@ -1586,6 +1579,9 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1586
1579
  });
1587
1580
  if (localId && localId !== draftId)
1588
1581
  setDraftId(localId);
1582
+ if (localId && !pushTargetId) {
1583
+ await saveOwnPackings(localId, packingRowsFromWizard(snapshot.packingConfigurations));
1584
+ }
1589
1585
  }
1590
1586
  catch (e) {
1591
1587
  const msg = e.message || "Onbekende fout";
@@ -1624,23 +1620,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1624
1620
  if (!res?.attemptId) {
1625
1621
  throw new Error("Gateway gaf geen attemptId terug");
1626
1622
  }
1627
- try {
1628
- const cachePayload = {
1629
- ...buildAddTradeItemPayload(snapshot),
1630
- tradeItemId: pushTargetId,
1631
- };
1632
- await supabase
1633
- .from("floriday_trade_items_cache")
1634
- .update({
1635
- data: cachePayload,
1636
- fetched_at: new Date().toISOString(),
1637
- })
1638
- .eq("connection_id", activeId)
1639
- .eq("floriday_id", pushTargetId);
1640
- }
1641
- catch {
1642
- /* niet kritiek; sync corrigeert */
1643
- }
1623
+ // Geen eigen kopie in floriday_trade_items_cache: Floriday-auth vult die.
1644
1624
  if (localId) {
1645
1625
  await markLocalSyncStatus(localId, {
1646
1626
  dirty: false,
@@ -1 +1 @@
1
- {"version":3,"file":"FloridayTakeoverPanel.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayTakeoverPanel.tsx"],"names":[],"mappings":"AA4CA,wBAAgB,sBAAsB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,sCAoCvF;AAED,wBAAgB,qBAAqB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,sCAoStF"}
1
+ {"version":3,"file":"FloridayTakeoverPanel.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayTakeoverPanel.tsx"],"names":[],"mappings":"AA6CA,wBAAgB,sBAAsB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,sCAoCvF;AAED,wBAAgB,qBAAqB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,sCAkTtF"}
@@ -7,6 +7,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
7
7
  * Toont ook "Floriday leidend voor X, ERP voor Y" en de alleen-lezen-stand
8
8
  * (uit Floriday-authenticatie: floriday_write_mode()).
9
9
  */
10
+ import { copyPackingsFromFloriday, PACKING_INCOMPLETE_TEXT } from "../../lib/product-packings.js";
10
11
  import { useCallback, useEffect, useMemo, useState } from "react";
11
12
  import { useServerFn } from "@tanstack/react-start";
12
13
  import { toast } from "sonner";
@@ -143,11 +144,27 @@ export function FloridayTakeoverPanel({ connectionId }) {
143
144
  stock_source: r.stockSource, price_source: r.priceSource, offer_forms: r.forms,
144
145
  }));
145
146
  const { data, error } = await sb.rpc("floriday_takeover_apply", { p_connection_id: connectionId, p_items: items });
146
- setBusy(false);
147
147
  if (error) {
148
+ setBusy(false);
148
149
  toast.error("Overnemen is niet gelukt; er is niets veranderd.", { description: error.message });
149
150
  return;
150
151
  }
152
+ // Nieuw aangemaakte artikelen: verpakkingen exact uit de Floriday-kopie overnemen.
153
+ // Bestaande artikelen volgen pas na akkoord op de herstellijst.
154
+ const createdIds = chosen.filter((r) => r.action === "aanmaken").map((r) => r.tradeItemId);
155
+ if (createdIds.length) {
156
+ try {
157
+ const { data: prods } = await sb.from("products").select("id").in("floriday_trade_item_id", createdIds);
158
+ const res = await copyPackingsFromFloriday(sb, connectionId, (prods ?? []).map((p) => p.id));
159
+ if (res.no_packings_in_floriday) {
160
+ toast.warning(`${res.no_packings_in_floriday} artikelen hebben in Floriday geen verpakking. ${PACKING_INCOMPLETE_TEXT}.`);
161
+ }
162
+ }
163
+ catch (e) {
164
+ toast.error("Verpakkingen konden niet worden overgenomen.", { description: e.message });
165
+ }
166
+ }
167
+ setBusy(false);
151
168
  toast.success(`${data?.items ?? 0} artikelen overgenomen${data?.created_products ? `, ${data.created_products} nieuw` : ""}.`);
152
169
  setRows(null);
153
170
  loadRuns();
@@ -1 +1 @@
1
- {"version":3,"file":"StockBulkButtons.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockBulkButtons.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAwG3D,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,kBAAkB,EAClB,UAAU,GACX,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC,+BAmoBA"}
1
+ {"version":3,"file":"StockBulkButtons.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockBulkButtons.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAgH3D,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,+BA4qBA"}
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { positiveIntOrNull, saveOwnPackings } from "../../lib/product-packings.js";
2
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
4
  import * as XLSX from "xlsx";
4
5
  import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, toast, } from "@flowselections/core";
@@ -26,7 +27,14 @@ const COLUMNS = [
26
27
  "Minimum voorraad",
27
28
  "Gereserveerde voorraad",
28
29
  "Inkomende voorraad",
30
+ "Fust (VBN-code)",
31
+ "Ladingdrager",
32
+ "Stuks per fust",
33
+ "Fusten per laag",
34
+ "Lagen per ladingdrager",
29
35
  ];
36
+ /** Verpakkingskolommen (alleen eigen artikelen zonder Floriday); leeg blijft leeg. */
37
+ const PACKING_COLUMNS = ["Fust (VBN-code)", "Ladingdrager", "Stuks per fust", "Fusten per laag", "Lagen per ladingdrager"];
30
38
  const ACTIVE = ["ontvangen", "bezig", "gestopt"];
31
39
  const rpc = (fn, args) => supabase.rpc(fn, args);
32
40
  function parseNumber(val) {
@@ -65,6 +73,7 @@ const STATUS_LABEL = {
65
73
  };
66
74
  export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
67
75
  const fileRef = useRef(null);
76
+ const packingByCode = useRef(new Map());
68
77
  const [open, setOpen] = useState(false);
69
78
  const [busy, setBusy] = useState(null);
70
79
  const [progress, setProgress] = useState(null);
@@ -205,6 +214,7 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
205
214
  const wb = XLSX.read(buf, { type: "array" });
206
215
  const sheet = wb.Sheets[wb.SheetNames[0]];
207
216
  const rows = XLSX.utils.sheet_to_json(sheet, { defval: "" });
217
+ packingByCode.current = new Map();
208
218
  const out = [];
209
219
  let existing = 0;
210
220
  let fresh = 0;
@@ -245,6 +255,29 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
245
255
  }
246
256
  base[key] = p.value;
247
257
  }
258
+ if (PACKING_COLUMNS.some((c) => String(row[c] ?? "").trim() !== "")) {
259
+ const read = (c) => {
260
+ const raw = String(row[c] ?? "").trim();
261
+ const v = positiveIntOrNull(raw);
262
+ if (raw !== "" && v === null)
263
+ throw new Error(`${c}: geen geldig heel getal groter dan 0`);
264
+ return v;
265
+ };
266
+ try {
267
+ packingByCode.current.set(code, [{
268
+ vbn_package_code: read("Fust (VBN-code)"),
269
+ load_carrier: String(row["Ladingdrager"] ?? "").trim() || null,
270
+ pieces_per_package: read("Stuks per fust"),
271
+ packages_per_layer: read("Fusten per laag"),
272
+ layers_per_load_carrier: read("Lagen per ladingdrager"),
273
+ is_primary: true,
274
+ }]);
275
+ }
276
+ catch (err) {
277
+ out.push({ ...base, error: err.message });
278
+ return;
279
+ }
280
+ }
248
281
  const custom = readCustomValues(row);
249
282
  base.custom_values = Object.keys(custom).length ? custom : null;
250
283
  const ex = byCode.get(code);
@@ -344,6 +377,32 @@ export function StockBulkButtons({ items, activeConnectionId, onImported, }) {
344
377
  done = data.done_chunks ?? done + 1;
345
378
  setProgress({ done, total, rows: Math.min(done * chunkSize, totalRows), totalRows });
346
379
  }
380
+ // Verpakkingen uit het bestand vastleggen, alleen voor eigen artikelen (zonder Floriday).
381
+ const packingErrors = [];
382
+ const codes = [...packingByCode.current.keys()];
383
+ for (let i = 0; i < codes.length; i += 200) {
384
+ const part = codes.slice(i, i + 200);
385
+ const { data: prods } = await supabase
386
+ .from("products")
387
+ .select("id,article_number,floriday_trade_item_id")
388
+ .in("article_number", part);
389
+ for (const pr of (prods ?? [])) {
390
+ if (pr.floriday_trade_item_id)
391
+ continue; // Floriday-artikel: verpakking komt uit Floriday
392
+ const rows = packingByCode.current.get(String(pr.article_number));
393
+ if (!rows)
394
+ continue;
395
+ try {
396
+ await saveOwnPackings(pr.id, rows);
397
+ }
398
+ catch (err) {
399
+ packingErrors.push(`${pr.article_number}: ${err.message}`);
400
+ }
401
+ }
402
+ }
403
+ packingByCode.current = new Map();
404
+ if (packingErrors.length)
405
+ toast.error(`Verpakking niet opgeslagen bij ${packingErrors.length} artikelen`, { description: packingErrors.slice(0, 5).join("; ") });
347
406
  const { data: fin } = await supabase
348
407
  .from("stock_import_runs")
349
408
  .select("updated_rows,created_rows,skipped_rows")
@@ -1 +1 @@
1
- {"version":3,"file":"StockIntegrityAlert.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockIntegrityAlert.tsx"],"names":[],"mappings":"AAoCA,wBAAgB,mBAAmB,uCA2GlC"}
1
+ {"version":3,"file":"StockIntegrityAlert.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockIntegrityAlert.tsx"],"names":[],"mappings":"AAqCA,wBAAgB,mBAAmB,uCAiIlC"}
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /**
3
3
  * Rode melding wanneer in één transactie twee of meer artikelen van een
4
4
  * positieve fysieke voorraad naar 0 zijn gegaan (bewaking in de database:
@@ -23,11 +23,11 @@ export function StockIntegrityAlert() {
23
23
  const load = useCallback(async () => {
24
24
  const { data, error } = await supabase
25
25
  .from("stock_integrity_alerts")
26
- .select("id, created_at, item_count, items")
26
+ .select("id, created_at, item_count, items, alert_kind")
27
27
  .is("acknowledged_at", null)
28
- .gte("item_count", 2)
28
+ .or("alert_kind.eq.FLORIDAY_NOT_UPDATED,item_count.gte.2")
29
29
  .order("created_at", { ascending: false })
30
- .limit(5);
30
+ .limit(10);
31
31
  if (error) {
32
32
  console.error("Voorraadbewaking laden mislukt:", error.message);
33
33
  return;
@@ -83,5 +83,5 @@ export function StockIntegrityAlert() {
83
83
  };
84
84
  if (alerts.length === 0)
85
85
  return null;
86
- return (_jsx("div", { className: "space-y-2", children: alerts.map((a) => (_jsxs("div", { className: "flex items-start gap-3 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: [_jsx(AlertOctagon, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { className: "flex-1 space-y-1", children: [_jsxs("p", { className: "font-medium", children: [a.item_count, " artikelen gingen tegelijk naar voorraad 0 (", new Date(a.created_at).toLocaleString("nl-NL"), "). Controleer of dit klopt."] }), _jsx("ul", { className: "text-xs text-foreground/80", children: (Array.isArray(a.items) ? a.items : []).slice(0, 10).map((it, i) => (_jsxs("li", { children: [label(it, names), ": was ", it.physical_before, it.change_source ? ` (bron ${it.change_source})` : " (bron onbekend)"] }, `${it.trade_item_key}-${i}`))) })] }), _jsx("button", { type: "button", className: "rounded-md border border-destructive/40 bg-background px-3 py-1 text-xs font-medium text-foreground hover:bg-muted", onClick: () => void acknowledge(a.id), children: "Gezien" })] }, a.id))) }));
86
+ return (_jsx("div", { className: "space-y-2", children: alerts.map((a) => (_jsxs("div", { className: "flex items-start gap-3 rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: [_jsx(AlertOctagon, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsx("div", { className: "flex-1 space-y-1", children: a.alert_kind === "FLORIDAY_NOT_UPDATED" ? (_jsxs(_Fragment, { children: [_jsxs("p", { className: "font-medium", children: ["Verkoop niet in Floriday verwerkt \u2013 pas de Floriday-voorraad aan (", new Date(a.created_at).toLocaleString("nl-NL"), ")."] }), _jsx("p", { className: "text-xs text-foreground/80", children: "Floriday leidt deze artikelen. Deze verkoop kwam niet via Floriday, dus Floriday weet er niets van." }), _jsx("ul", { className: "text-xs text-foreground/80", children: (Array.isArray(a.items) ? a.items : []).slice(0, 10).map((it, i) => (_jsxs("li", { children: [label(it, names), ": ", it.qty ?? "?", " stuks", it.order_number ? ` (order ${it.order_number})` : ""] }, `${it.trade_item_key}-${i}`))) })] })) : (_jsxs(_Fragment, { children: [_jsxs("p", { className: "font-medium", children: [a.item_count, " artikelen gingen tegelijk naar voorraad 0 (", new Date(a.created_at).toLocaleString("nl-NL"), "). Controleer of dit klopt."] }), _jsx("ul", { className: "text-xs text-foreground/80", children: (Array.isArray(a.items) ? a.items : []).slice(0, 10).map((it, i) => (_jsxs("li", { children: [label(it, names), ": was ", it.physical_before, it.change_source ? ` (bron ${it.change_source})` : " (bron onbekend)"] }, `${it.trade_item_key}-${i}`))) })] })) }), _jsx("button", { type: "button", className: "rounded-md border border-destructive/40 bg-background px-3 py-1 text-xs font-medium text-foreground hover:bg-muted", onClick: () => void acknowledge(a.id), children: "Gezien" })] }, a.id))) }));
87
87
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AA6EzE,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,EACP,aAAa,EACb,QAAQ,EACR,cAAc,EACd,WAAW,EACX,cAAc,GACf,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,+BAuSA"}
1
+ {"version":3,"file":"VoorraadTable.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadTable.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAI3D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AA8EzE,oFAAoF;AACpF,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,GAAG,IAAI,CAE3D;AAED,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,OAAO,EACP,aAAa,EACb,QAAQ,EACR,cAAc,EACd,WAAW,EACX,cAAc,GACf,EAAE;IACD,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IAC9C,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACvB,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,WAAW,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IAC7C,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C,+BAwTA"}
@@ -7,6 +7,7 @@ import { supabase } from "../../integrations/supabase/client.js";
7
7
  import { useVoorraadCustomFields } from "../../hooks/useVoorraadCustomFields.js";
8
8
  import { formatCustomValue } from "../../lib/voorraad-custom-fields.js";
9
9
  import { InventoryStatusBadge } from "./InventoryStatusBadge.js";
10
+ import { loadIncompletePackingIds, PACKING_INCOMPLETE_TEXT } from "../../lib/product-packings.js";
10
11
  import { PRODUCT_TYPE_CLASSES, PRODUCT_TYPE_HELP, PRODUCT_TYPE_LABELS, toStructureType } from "../../lib/product-types.js";
11
12
  const columns = [
12
13
  { key: "quantity", label: "Liggend" },
@@ -89,6 +90,20 @@ export function settingKeyOf(item) {
89
90
  }
90
91
  export function VoorraadTable({ items, loading, settingsByKey, selected, onToggleSelect, onToggleAll, onOpenSettings, }) {
91
92
  const navigate = useNavigate();
93
+ // Artikelen zonder volledige primaire verpakking (karindeling kan ze niet plannen).
94
+ const [incompletePacking, setIncompletePacking] = useState(new Set());
95
+ const productIdsKey = items.map((i) => i.productId ?? "").join(",");
96
+ useEffect(() => {
97
+ let cancelled = false;
98
+ const ids = items.map((i) => i.productId).filter((x) => Boolean(x));
99
+ loadIncompletePackingIds(ids)
100
+ .then((set) => { if (!cancelled)
101
+ setIncompletePacking(set); })
102
+ .catch(() => { if (!cancelled)
103
+ setIncompletePacking(new Set()); });
104
+ return () => { cancelled = true; };
105
+ // eslint-disable-next-line react-hooks/exhaustive-deps
106
+ }, [productIdsKey]);
92
107
  const { fields } = useVoorraadCustomFields();
93
108
  const { visible, toggle } = useVisibleColumns();
94
109
  const allExtraColumns = fields.filter((f) => f.show_in_columns);
@@ -135,7 +150,7 @@ export function VoorraadTable({ items, loading, settingsByKey, selected, onToggl
135
150
  open();
136
151
  }
137
152
  }
138
- : undefined, "aria-label": clickable ? `Bewerk ${item.name}` : undefined, children: [onToggleSelect && (_jsx("td", { className: "p-3", onClick: (e) => e.stopPropagation(), children: _jsx(Checkbox, { checked: settingProductId ? selected?.has(settingProductId) : false, disabled: !settingProductId, onCheckedChange: () => settingProductId && onToggleSelect(settingProductId), "aria-label": `Selecteer ${item.name}` }) })), _jsx("td", { className: "p-2", children: thumbUrl ? (_jsx("img", { src: thumbUrl, alt: item.name, className: "h-10 w-10 rounded object-cover", loading: "lazy" })) : (_jsx("div", { className: "h-10 w-10 rounded bg-muted flex items-center justify-center", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) })) }), _jsxs("td", { className: "p-3", children: [_jsxs("div", { className: "flex flex-col items-start gap-1", children: [_jsx("span", { className: "font-medium text-foreground", children: item.name }), _jsxs("div", { role: "status", "aria-label": floriday
153
+ : undefined, "aria-label": clickable ? `Bewerk ${item.name}` : undefined, children: [onToggleSelect && (_jsx("td", { className: "p-3", onClick: (e) => e.stopPropagation(), children: _jsx(Checkbox, { checked: settingProductId ? selected?.has(settingProductId) : false, disabled: !settingProductId, onCheckedChange: () => settingProductId && onToggleSelect(settingProductId), "aria-label": `Selecteer ${item.name}` }) })), _jsx("td", { className: "p-2", children: thumbUrl ? (_jsx("img", { src: thumbUrl, alt: item.name, className: "h-10 w-10 rounded object-cover", loading: "lazy" })) : (_jsx("div", { className: "h-10 w-10 rounded bg-muted flex items-center justify-center", children: _jsx(Package, { className: "h-4 w-4 text-muted-foreground" }) })) }), _jsxs("td", { className: "p-3", children: [_jsxs("div", { className: "flex flex-col items-start gap-1", children: [_jsx("span", { className: "font-medium text-foreground", children: item.name }), item.productId && incompletePacking.has(item.productId) && (_jsx("span", { className: "text-[11px] font-medium text-destructive", children: PACKING_INCOMPLETE_TEXT })), _jsxs("div", { role: "status", "aria-label": floriday
139
154
  ? "Online in Floriday"
140
155
  : "Niet online in Floriday", title: floriday
141
156
  ? "Online in Floriday"