@flowselections/floriday-voorraad 1.0.127 → 1.0.131
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 +102 -19
- package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/NieuwArtikelPage.js +2 -0
- package/dist-lib/components/voorraad/PriceStatusBadge.d.ts.map +1 -1
- package/dist-lib/components/voorraad/PriceStatusBadge.js +21 -45
- package/dist-lib/components/voorraad/ProductStructureCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ProductStructureCard.js +6 -2
- package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
- package/dist-lib/hooks/useLocalTradeItems.js +4 -0
- package/dist-lib/integrations/supabase/types.d.ts +801 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/__tests__/article-code.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/article-code.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/article-code.test.js +11 -0
- package/dist-lib/lib/__tests__/release-130.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/release-130.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/release-130.test.js +103 -0
- package/dist-lib/lib/article-code.d.ts +12 -0
- package/dist-lib/lib/article-code.d.ts.map +1 -0
- package/dist-lib/lib/article-code.js +29 -0
- package/dist-lib/lib/floriday-2026v1.server.d.ts +1 -8
- package/dist-lib/lib/floriday-2026v1.server.d.ts.map +1 -1
- package/dist-lib/lib/floriday-2026v1.server.js +0 -10
- package/dist-lib/lib/floriday-gateway.functions.d.ts +6 -0
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.js +7 -0
- package/dist-lib/lib/floriday-gateway.server.d.ts +17 -4
- package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.server.js +80 -2
- package/dist-lib/lib/floriday-payload.d.ts.map +1 -1
- package/dist-lib/lib/floriday-payload.js +11 -3
- package/dist-lib/lib/floriday-read-contract.d.ts +49 -0
- package/dist-lib/lib/floriday-read-contract.d.ts.map +1 -0
- package/dist-lib/lib/floriday-read-contract.js +92 -0
- package/dist-lib/lib/price-status.d.ts +14 -0
- package/dist-lib/lib/price-status.d.ts.map +1 -0
- package/dist-lib/lib/price-status.js +16 -0
- package/dist-lib/lib/publish-pipeline.d.ts.map +1 -1
- package/dist-lib/lib/publish-pipeline.js +2 -7
- package/dist-lib/lib/publish-status.d.ts +26 -0
- package/dist-lib/lib/publish-status.d.ts.map +1 -0
- package/dist-lib/lib/publish-status.js +30 -0
- package/dist-lib/server-hooks/floriday-continuous-stock-pull.d.ts.map +1 -1
- package/dist-lib/server-hooks/floriday-continuous-stock-pull.js +37 -50
- package/package.json +1 -1
- package/sql/floriday-voorraad-schema.sql +88 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA2DA,OAAO,EAUL,KAAK,UAAU,EAMhB,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA2DA,OAAO,EAUL,KAAK,UAAU,EAMhB,MAAM,4BAA4B,CAAC;AA2DpC,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAMpF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,UAAU,CA0BrF;AAiED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EACX,SAAS,EACT,WAAW,EACX,OAAO,GACR,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;IACrB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,aAAa,CAAC;CACpB,+BAo2EL"}
|
|
@@ -20,6 +20,9 @@ import { useVbnRegulatoryFeatures } from "../../hooks/useVbnRegulatoryFeatures.j
|
|
|
20
20
|
import { useFloridayVbnRequired } from "../../hooks/useFloridayVbnRequired.js";
|
|
21
21
|
import { saveFloridayVbnRequired } from "../../lib/floriday-vbn-required.functions.js";
|
|
22
22
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
23
|
+
import { fetchFloridayWriteMode } from "../../lib/floriday-write-mode.js";
|
|
24
|
+
import { editTargetText } from "../../lib/publish-status.js";
|
|
25
|
+
import { articleCodeProblem } from "../../lib/article-code.js";
|
|
23
26
|
import { useCodeList, useTradeItemFormSchema } from "../../hooks/useFloriday.js";
|
|
24
27
|
import { useFloridayPriceGroups } from "../../hooks/useFloridayPriceGroups.js";
|
|
25
28
|
import { readTradeItemPrices, syncTradeItemPrices } from "../../lib/trade-item-prices.js";
|
|
@@ -178,7 +181,9 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
178
181
|
const isEditingDraft = Boolean(initialDraftId);
|
|
179
182
|
// Een lokaal product dat al aan Floriday gekoppeld is, is géén "nieuw"
|
|
180
183
|
// artikel: bewerken betekent dan bijwerken in Floriday.
|
|
181
|
-
|
|
184
|
+
// 1.0.130: een artikel met een Floriday-id telt als gepubliceerd, ook als
|
|
185
|
+
// alleen de lokale rij (draftFloridayId) dat id kent.
|
|
186
|
+
const publishedTradeItemId = tradeItemId ?? productMeta.floridayTradeItemId ?? draftFloridayId ?? null;
|
|
182
187
|
const isEditingPublished = Boolean(publishedTradeItemId);
|
|
183
188
|
const isEditingProduct = Boolean(productId);
|
|
184
189
|
const isEditing = isEditingDraft || isEditingPublished || isEditingProduct;
|
|
@@ -422,29 +427,70 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
422
427
|
}
|
|
423
428
|
const cachePackings = cacheData?.packingConfigurations ?? [];
|
|
424
429
|
const payloadHasPacking = Array.isArray(payload.packingConfigurations) && payload.packingConfigurations.length > 0;
|
|
430
|
+
// 1.0.130: zonder Floriday-kopie of opgeslagen invoer eerst de eigen
|
|
431
|
+
// hoofdverpakking (product_packings) gebruiken, inclusief fust en
|
|
432
|
+
// ladingdrager. Kent deze rij die niet, dan die van het artikel met
|
|
433
|
+
// hetzelfde Floriday-id. Nooit aanvullen.
|
|
434
|
+
let ownPrimary = null;
|
|
435
|
+
if (cachePackings.length === 0 && !payloadHasPacking) {
|
|
436
|
+
const pidList = [String(product.id)];
|
|
437
|
+
if (fid) {
|
|
438
|
+
const { data: sisters } = await supabase
|
|
439
|
+
.from("products")
|
|
440
|
+
.select("id")
|
|
441
|
+
.eq("floriday_trade_item_id", fid)
|
|
442
|
+
.limit(5);
|
|
443
|
+
for (const r of (sisters ?? []))
|
|
444
|
+
if (!pidList.includes(String(r.id)))
|
|
445
|
+
pidList.push(String(r.id));
|
|
446
|
+
}
|
|
447
|
+
const { data: pk } = await supabase
|
|
448
|
+
.from("product_packings")
|
|
449
|
+
.select("product_id, vbn_package_code, custom_package_id, load_carrier, pieces_per_package, packages_per_layer, layers_per_load_carrier")
|
|
450
|
+
.in("product_id", pidList)
|
|
451
|
+
.eq("is_primary", true);
|
|
452
|
+
const rows = (pk ?? []);
|
|
453
|
+
ownPrimary = rows.find((r) => r.product_id === pidList[0]) ?? rows[0] ?? null;
|
|
454
|
+
}
|
|
425
455
|
const seededPacking = cachePackings.length > 0
|
|
426
456
|
? cachePackings
|
|
427
457
|
: payloadHasPacking
|
|
428
458
|
? payload.packingConfigurations
|
|
429
|
-
:
|
|
459
|
+
: ownPrimary
|
|
430
460
|
? [
|
|
431
461
|
{
|
|
432
|
-
id:
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
packagesPerLayer: cpl,
|
|
441
|
-
layersPerLoadCarrier: lpc,
|
|
462
|
+
id: `own-${String(product.id)}`,
|
|
463
|
+
source: ownPrimary.custom_package_id ? "SUPPLIER" : "VBN",
|
|
464
|
+
loadCarrier: String(ownPrimary.load_carrier ?? ""),
|
|
465
|
+
packagingType: ownPrimary.vbn_package_code != null ? String(ownPrimary.vbn_package_code) : "",
|
|
466
|
+
packagingMaterialId: ownPrimary.custom_package_id ?? undefined,
|
|
467
|
+
unitsPerPackage: Number(ownPrimary.pieces_per_package) || 0,
|
|
468
|
+
packagesPerLayer: Number(ownPrimary.packages_per_layer) || 0,
|
|
469
|
+
layersPerLoadCarrier: Number(ownPrimary.layers_per_load_carrier) || 0,
|
|
442
470
|
isPrimary: true,
|
|
443
471
|
isCustomerSpecific: false,
|
|
444
472
|
customerOrganizationIds: [],
|
|
445
473
|
},
|
|
446
474
|
]
|
|
447
|
-
:
|
|
475
|
+
: ppc > 0 || cpl > 0 || lpc > 0
|
|
476
|
+
? [
|
|
477
|
+
{
|
|
478
|
+
id: typeof crypto !== "undefined" && "randomUUID" in crypto
|
|
479
|
+
? crypto.randomUUID()
|
|
480
|
+
: `pk-${Date.now()}`,
|
|
481
|
+
source: "VBN",
|
|
482
|
+
loadCarrier: "",
|
|
483
|
+
packagingType: "",
|
|
484
|
+
// Leeg laten wat niet bekend is; nooit aanvullen.
|
|
485
|
+
unitsPerPackage: ppc,
|
|
486
|
+
packagesPerLayer: cpl,
|
|
487
|
+
layersPerLoadCarrier: lpc,
|
|
488
|
+
isPrimary: true,
|
|
489
|
+
isCustomerSpecific: false,
|
|
490
|
+
customerOrganizationIds: [],
|
|
491
|
+
},
|
|
492
|
+
]
|
|
493
|
+
: [];
|
|
448
494
|
const cacheImages = cacheData?.images ?? [];
|
|
449
495
|
const mergedImages = cacheImages.length > 0
|
|
450
496
|
? cacheImages
|
|
@@ -969,8 +1015,21 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
969
1015
|
out[c.code] = c.label || c.code;
|
|
970
1016
|
return out;
|
|
971
1017
|
}, [spec.characteristics]);
|
|
972
|
-
|
|
973
|
-
|
|
1018
|
+
// Met een Floriday-id gaan wijzigingen altijd naar Floriday.
|
|
1019
|
+
const wantsPublish = isEditingPublished || publishToFloriday;
|
|
1020
|
+
const writeModeQuery = useQuery({
|
|
1021
|
+
queryKey: ["floriday-write-mode"],
|
|
1022
|
+
queryFn: fetchFloridayWriteMode,
|
|
1023
|
+
enabled: isEditingPublished,
|
|
1024
|
+
staleTime: 30000,
|
|
1025
|
+
});
|
|
1026
|
+
const editTarget = editTargetText({
|
|
1027
|
+
floridayId: publishedTradeItemId,
|
|
1028
|
+
writeReadOnly: writeModeQuery.isError ? true : writeModeQuery.data?.read_only,
|
|
1029
|
+
writeReason: writeModeQuery.isError
|
|
1030
|
+
? "de Floriday-status kon niet worden gelezen"
|
|
1031
|
+
: writeModeQuery.data?.reason,
|
|
1032
|
+
});
|
|
974
1033
|
const readiness = useMemo(() => {
|
|
975
1034
|
const r = buildReadinessReport({
|
|
976
1035
|
data,
|
|
@@ -1393,6 +1452,18 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1393
1452
|
resolvedTradeItemId =
|
|
1394
1453
|
core.value?.tradeItemId ??
|
|
1395
1454
|
resolvedTradeItemId;
|
|
1455
|
+
// Artikel staat in Floriday: vanaf nu gepubliceerd, ook als een latere
|
|
1456
|
+
// stap (aanbod, controle) mislukt.
|
|
1457
|
+
if (resolvedTradeItemId) {
|
|
1458
|
+
setDraftFloridayId(resolvedTradeItemId);
|
|
1459
|
+
if (savedLocalId) {
|
|
1460
|
+
await markLocalSyncStatus(savedLocalId, {
|
|
1461
|
+
dirty: false,
|
|
1462
|
+
last_error: null,
|
|
1463
|
+
last_synced_at: new Date().toISOString(),
|
|
1464
|
+
}).catch(() => { });
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1396
1467
|
// Nieuw artikel: ingevoerde voorraad, prijs en belading altijd lokaal vastleggen
|
|
1397
1468
|
// (nooit iets overschrijven wat er al staat).
|
|
1398
1469
|
if (!publishedTradeItemId) {
|
|
@@ -1564,8 +1635,8 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1564
1635
|
return { warnings };
|
|
1565
1636
|
}, stepOpts);
|
|
1566
1637
|
if (!verify.ok) {
|
|
1567
|
-
toast.warning("
|
|
1568
|
-
description: "
|
|
1638
|
+
toast.warning("Gepubliceerd; de controle achteraf lukte niet", {
|
|
1639
|
+
description: `${verify.error?.message ?? "Onbekende reden"} — het artikel staat wel in Floriday.`,
|
|
1569
1640
|
});
|
|
1570
1641
|
}
|
|
1571
1642
|
}
|
|
@@ -1587,6 +1658,14 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1587
1658
|
navigate({ to: "/voorraad" });
|
|
1588
1659
|
}
|
|
1589
1660
|
else if (status === "succeeded_with_warnings") {
|
|
1661
|
+
if (draftId) {
|
|
1662
|
+
await markLocalSyncStatus(draftId, {
|
|
1663
|
+
dirty: false,
|
|
1664
|
+
last_error: null,
|
|
1665
|
+
last_synced_at: new Date().toISOString(),
|
|
1666
|
+
}).catch(() => { });
|
|
1667
|
+
}
|
|
1668
|
+
setPendingSync(null);
|
|
1590
1669
|
toast.warning("Gepubliceerd, met aandachtspunten", {
|
|
1591
1670
|
description: "Bekijk wat er nog moet gebeuren in het overzicht hieronder.",
|
|
1592
1671
|
});
|
|
@@ -1820,7 +1899,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1820
1899
|
setDeleteOpen(o);
|
|
1821
1900
|
if (!o)
|
|
1822
1901
|
setDeleteAck(false);
|
|
1823
|
-
}, 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: "Product verwijderen?" }), _jsx(AlertDialogDescription, { asChild: true, children: _jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("div", { children: ["Je staat op het punt om ", _jsx("strong", { children: data.name || "dit artikel" }), data.articleCode ? _jsxs(_Fragment, { children: [" (artikelnummer ", _jsx("code", { children: data.articleCode }), ")"] }) : null, " ", "lokaal uit de voorraad te verwijderen."] }), _jsxs("div", { className: "rounded-md border p-3 bg-muted/40 space-y-1", children: [_jsxs("div", { children: ["Huidige voorraad: ", _jsx("strong", { children: productMeta.physicalStock }), " stuks"] }), _jsxs("div", { children: ["Floriday-koppeling:", " ", productMeta.floridayActive || productMeta.floridayTradeItemId ? (_jsx("span", { className: "text-emerald-700 font-medium", children: "actief" })) : (_jsx("span", { className: "text-muted-foreground", children: "geen" }))] })] }), (productMeta.floridayActive || productMeta.floridayTradeItemId) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-3 text-amber-900", children: [_jsx("strong", { children: "Let op:" }), " dit artikel is gekoppeld met Floriday. Als je alleen de lokale kant verwijdert, blijft het artikel op Floriday bestaan en verschijnt het bij de volgende sync weer terug in de lijst. Trek het artikel daarna zelf in via het Floriday-portaal als je het echt weg wilt."] })), productMeta.physicalStock > 0 && (_jsxs("div", { className: "rounded-md border border-rose-300 bg-rose-50 p-3 text-rose-900", children: ["Er staat nog voorraad (", productMeta.physicalStock, " stuks) op dit product. Deze voorraad wordt \u00F3\u00F3k verwijderd en kan niet automatisch worden hersteld."] })), _jsxs("label", { className: "flex items-start gap-2 pt-1", children: [_jsx(Checkbox, { checked: deleteAck, onCheckedChange: (v) => setDeleteAck(Boolean(v)) }), _jsx("span", { children: "Ik weet zeker dat ik dit product wil verwijderen en begrijp de gevolgen." })] })] }) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { disabled: !deleteAck, onClick: handleDeleteDraft, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Definitief verwijderen" })] })] })] }))] }), isEditing && (data.name || data.vbnName) && (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: "Artikel" }), _jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsx("h1", { className: "text-2xl font-semibold leading-tight", children: data.name || data.vbnName }), (isEditingPublished || draftFloridayId) ? (syncState === "saving" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), " Synchroniseren\u2026"] })) : syncState === "saved" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700", children: [_jsx(Check, { className: "h-3 w-3" }), " Gesynchroniseerd"] })) : syncState === "error" ? (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-700", title: syncError ?? undefined, children: "Sync mislukt \u2014 klik Opslaan om opnieuw te proberen" })) : null) : (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: "Nog niet gepubliceerd naar Floriday" })), _jsx(PriceStatusBadge, { tradeItemId: tradeItemId ?? draftFloridayId ?? null, productId: productId ?? null, offerForSale: data.offerForSale })] }), data.name && data.vbnName && data.name !== data.vbnName && (_jsx("div", { className: "text-sm text-muted-foreground", children: data.vbnName }))] })), pendingSync && (_jsxs(Card, { className: "p-4 border-amber-300 bg-amber-50 text-amber-900 space-y-2", children: [_jsx("div", { className: "font-medium text-sm", children: "Je wijzigingen zijn opgeslagen, maar nog niet doorgegeven aan Floriday" }), _jsxs("div", { className: "text-xs", children: ["Alles wat je hier hebt ingevuld blijft bewaard in dit systeem. Zodra het doorgeven lukt, verdwijnt deze melding vanzelf.", pendingSync.error ? ` Reden: ${pendingSync.error}` : ""] }), _jsx(FloridayStockReasonLine, { productId: linkedProductId ?? productId ?? null }), _jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: retryingSync, onClick: retrySync, children: retryingSync ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin mr-1" }), " Bezig\u2026"] })) : ("Opnieuw proberen") })] })), 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" }),
|
|
1902
|
+
}, 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: "Product verwijderen?" }), _jsx(AlertDialogDescription, { asChild: true, children: _jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("div", { children: ["Je staat op het punt om ", _jsx("strong", { children: data.name || "dit artikel" }), data.articleCode ? _jsxs(_Fragment, { children: [" (artikelnummer ", _jsx("code", { children: data.articleCode }), ")"] }) : null, " ", "lokaal uit de voorraad te verwijderen."] }), _jsxs("div", { className: "rounded-md border p-3 bg-muted/40 space-y-1", children: [_jsxs("div", { children: ["Huidige voorraad: ", _jsx("strong", { children: productMeta.physicalStock }), " stuks"] }), _jsxs("div", { children: ["Floriday-koppeling:", " ", productMeta.floridayActive || productMeta.floridayTradeItemId ? (_jsx("span", { className: "text-emerald-700 font-medium", children: "actief" })) : (_jsx("span", { className: "text-muted-foreground", children: "geen" }))] })] }), (productMeta.floridayActive || productMeta.floridayTradeItemId) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 p-3 text-amber-900", children: [_jsx("strong", { children: "Let op:" }), " dit artikel is gekoppeld met Floriday. Als je alleen de lokale kant verwijdert, blijft het artikel op Floriday bestaan en verschijnt het bij de volgende sync weer terug in de lijst. Trek het artikel daarna zelf in via het Floriday-portaal als je het echt weg wilt."] })), productMeta.physicalStock > 0 && (_jsxs("div", { className: "rounded-md border border-rose-300 bg-rose-50 p-3 text-rose-900", children: ["Er staat nog voorraad (", productMeta.physicalStock, " stuks) op dit product. Deze voorraad wordt \u00F3\u00F3k verwijderd en kan niet automatisch worden hersteld."] })), _jsxs("label", { className: "flex items-start gap-2 pt-1", children: [_jsx(Checkbox, { checked: deleteAck, onCheckedChange: (v) => setDeleteAck(Boolean(v)) }), _jsx("span", { children: "Ik weet zeker dat ik dit product wil verwijderen en begrijp de gevolgen." })] })] }) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: "Annuleren" }), _jsx(AlertDialogAction, { disabled: !deleteAck, onClick: handleDeleteDraft, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: "Definitief verwijderen" })] })] })] }))] }), isEditing && (data.name || data.vbnName) && (_jsxs("div", { className: "space-y-1", children: [_jsx("div", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: "Artikel" }), _jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsx("h1", { className: "text-2xl font-semibold leading-tight", children: data.name || data.vbnName }), (isEditingPublished || draftFloridayId) ? (syncState === "saving" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), " Synchroniseren\u2026"] })) : syncState === "saved" ? (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-emerald-100 text-emerald-700", children: [_jsx(Check, { className: "h-3 w-3" }), " Gesynchroniseerd"] })) : syncState === "error" ? (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-red-100 text-red-700", title: syncError ?? undefined, children: "Sync mislukt \u2014 klik Opslaan om opnieuw te proberen" })) : null) : (_jsx("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: "Nog niet gepubliceerd naar Floriday" })), _jsx(PriceStatusBadge, { tradeItemId: tradeItemId ?? draftFloridayId ?? null, productId: productId ?? null, offerForSale: data.offerForSale })] }), data.name && data.vbnName && data.name !== data.vbnName && (_jsx("div", { className: "text-sm text-muted-foreground", children: data.vbnName }))] })), pendingSync && (_jsxs(Card, { className: "p-4 border-amber-300 bg-amber-50 text-amber-900 space-y-2", children: [_jsx("div", { className: "font-medium text-sm", children: "Je wijzigingen zijn opgeslagen, maar nog niet doorgegeven aan Floriday" }), _jsxs("div", { className: "text-xs", children: ["Alles wat je hier hebt ingevuld blijft bewaard in dit systeem. Zodra het doorgeven lukt, verdwijnt deze melding vanzelf.", pendingSync.error ? ` Reden: ${pendingSync.error}` : ""] }), _jsx(FloridayStockReasonLine, { productId: linkedProductId ?? productId ?? null }), _jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: retryingSync, onClick: retrySync, children: retryingSync ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin mr-1" }), " Bezig\u2026"] })) : ("Opnieuw proberen") })] })), 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" }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [editTarget.text, " Je kunt vrij tussen alle secties navigeren."] })] }), _jsxs(Button, { onClick: () => void 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: () => void 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(ProductStructureCard, { productId: linkedProductId ?? productId ?? null }), _jsx(ArtikelCompositionCard, { productId: linkedProductId ?? productId ?? null, floridayId: tradeItemId ?? draftFloridayId ?? null }), _jsx(FloridayInfoCard, { floridayTradeItemId: tradeItemId ?? draftFloridayId ?? null, local: {
|
|
1824
1903
|
name: data.name || data.vbnName,
|
|
1825
1904
|
articleNumber: data.articleCode,
|
|
1826
1905
|
gtin: data.gtin,
|
|
@@ -2161,7 +2240,11 @@ function StepArtikel({ data, update, collectionsList, setCollectionsList, articl
|
|
|
2161
2240
|
icon: ClipboardList,
|
|
2162
2241
|
},
|
|
2163
2242
|
];
|
|
2164
|
-
return (_jsxs("div", { className: "grid gap-5", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-1.5", children: ["Artikelcode ", !articleCodeLocked && _jsx("span", { className: "text-destructive", children: "*" }), _jsx(InfoTip, { text: "Dit is je eigen referentie naar dit artikel, ook wel bekend als de kweker-artikelcode." })] }), _jsx(Input, { value: data.articleCode, onChange: (e) =>
|
|
2243
|
+
return (_jsxs("div", { className: "grid gap-5", children: [_jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-1.5", children: ["Artikelcode ", !articleCodeLocked && _jsx("span", { className: "text-destructive", children: "*" }), _jsx(InfoTip, { text: "Dit is je eigen referentie naar dit artikel, ook wel bekend als de kweker-artikelcode." })] }), _jsx(Input, { value: data.articleCode, onChange: (e) => {
|
|
2244
|
+
// Een eerdere opslagfout over de code verdwijnt zodra de code wijzigt.
|
|
2245
|
+
toast.dismiss();
|
|
2246
|
+
update("articleCode", e.target.value);
|
|
2247
|
+
}, disabled: articleCodeLocked, readOnly: articleCodeLocked }), !articleCodeLocked && data.articleCode.trim() && articleCodeProblem(data.articleCode, data.name, "manual") && (_jsxs("p", { className: "text-xs text-destructive", children: [articleCodeProblem(data.articleCode, data.name, "manual"), " Vul de echte artikelcode in."] })), articleCodeLocked && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Artikelcode ligt vast in Floriday en kan niet meer gewijzigd worden." }))] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { children: ["Naam ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { value: data.name, onChange: (e) => update("name", e.target.value) })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { className: "flex items-center gap-1.5", children: ["GTIN ", _jsx("span", { className: "text-muted-foreground font-normal", children: "(optioneel)" }), _jsx(InfoTip, { text: "GTIN staat voor 'Global Trade Item Number', een 13-cijferige barcode" })] }), _jsx(Input, { value: data.gtin, onChange: (e) => update("gtin", e.target.value) })] }), _jsxs("div", { className: "space-y-2", children: [_jsxs(Label, { children: ["Collecties ", _jsx("span", { className: "text-muted-foreground font-normal", children: "(optioneel)" })] }), data.collections.length > 0 && (_jsx("div", { className: "flex flex-wrap gap-1.5", children: data.collections.map((c) => (_jsxs("span", { className: "inline-flex items-center gap-1 rounded-full bg-muted px-2 py-1 text-xs", children: [c, _jsx("button", { type: "button", onClick: () => removeCollection(c), className: "hover:text-destructive", children: _jsx(X, { className: "h-3 w-3" }) })] }, c))) })), !showNewCollection ? (_jsxs(Select, { value: "", onValueChange: (v) => {
|
|
2165
2248
|
if (v === "__new__") {
|
|
2166
2249
|
setShowNewCollection(true);
|
|
2167
2250
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,gBAAgB,gCA4X/B"}
|
|
@@ -17,6 +17,7 @@ import { LOCAL_STOCK_TEXT } from "../../lib/stock-scope.js";
|
|
|
17
17
|
import { FloridayConnectionNotice } from "./FloridayConnectionNotice.js";
|
|
18
18
|
import { useLoadCarriers } from "../../hooks/useLoadCarriers.js";
|
|
19
19
|
import { saveOwnPackings } from "../../lib/product-packings.js";
|
|
20
|
+
import { assertArticleCode } from "../../lib/article-code.js";
|
|
20
21
|
import { EMPTY_SIMPLE_PACKING, engineOordeelRegel, missingPackingFields, simplePackingToRow, } from "../../lib/simple-packing.js";
|
|
21
22
|
const STEPS = ["Naam en code", "Voorraad", "Prijs", "Belading"];
|
|
22
23
|
export function NieuwArtikelPage() {
|
|
@@ -95,6 +96,7 @@ export function NieuwArtikelPage() {
|
|
|
95
96
|
const priceNum = price.trim() === "" ? null : Number(price.replace(",", "."));
|
|
96
97
|
if (priceNum !== null && !(priceNum > 0))
|
|
97
98
|
throw new Error("Vul een geldige prijs in (groter dan 0)");
|
|
99
|
+
assertArticleCode(code, name, "manual");
|
|
98
100
|
const packingRow = simplePackingToRow(packing);
|
|
99
101
|
let id = insertedIdRef.current;
|
|
100
102
|
if (!id) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PriceStatusBadge.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PriceStatusBadge.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PriceStatusBadge.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/PriceStatusBadge.tsx"],"names":[],"mappings":"AA0FA,KAAK,KAAK,GAAG;IACX,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACvC,4FAA4F;IAC5F,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;CACvB,CAAC;AAeF,wBAAgB,gBAAgB,CAAC,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,KAAK,sCAsD/E"}
|
|
@@ -4,6 +4,7 @@ import { CircleDollarSign, Clock, MinusCircle, Loader2 } from "lucide-react";
|
|
|
4
4
|
import { useEffect, useState } from "react";
|
|
5
5
|
import { useTradeItemSupplyLine } from "../../hooks/useTradeItemSupplyLine.js";
|
|
6
6
|
import { supabase } from "../../integrations/supabase/client.js";
|
|
7
|
+
import { PRICE_STATUS_LABELS, priceStatus } from "../../lib/price-status.js";
|
|
7
8
|
/**
|
|
8
9
|
* M26: laatst naar Floriday verzonden weekprijs (weekaanbod), uit de wachtrij.
|
|
9
10
|
* M28: dezelfde bron als de verzendgeschiedenis ("Laatst naar Floriday"):
|
|
@@ -56,12 +57,10 @@ function useLastSentPrice(tradeItemId, productId) {
|
|
|
56
57
|
function useResolvedTradeItemId(tradeItemId, productId) {
|
|
57
58
|
const [resolved, setResolved] = useState(tradeItemId ?? null);
|
|
58
59
|
useEffect(() => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
60
|
+
// 1.0.130: met een artikel-id is products.floriday_trade_item_id leidend,
|
|
61
|
+
// zodat elke plek op hetzelfde artikel dezelfde bron leest.
|
|
63
62
|
if (!productId) {
|
|
64
|
-
setResolved(null);
|
|
63
|
+
setResolved(tradeItemId ?? null);
|
|
65
64
|
return;
|
|
66
65
|
}
|
|
67
66
|
let cancelled = false;
|
|
@@ -72,7 +71,7 @@ function useResolvedTradeItemId(tradeItemId, productId) {
|
|
|
72
71
|
.maybeSingle()
|
|
73
72
|
.then(({ data }) => {
|
|
74
73
|
if (!cancelled)
|
|
75
|
-
setResolved(data?.floriday_trade_item_id ?? null);
|
|
74
|
+
setResolved(data?.floriday_trade_item_id ?? tradeItemId ?? null);
|
|
76
75
|
});
|
|
77
76
|
return () => {
|
|
78
77
|
cancelled = true;
|
|
@@ -103,44 +102,20 @@ export function PriceStatusBadge({ tradeItemId, productId, offerForSale }) {
|
|
|
103
102
|
if (isLoading) {
|
|
104
103
|
return (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full bg-muted text-muted-foreground", children: [_jsx(Loader2, { className: "h-3 w-3 animate-spin" }), " Prijsstatus laden\u2026"] }));
|
|
105
104
|
}
|
|
106
|
-
//
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
return (_jsxs("span", { className: "inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full cursor-help bg-emerald-100 text-emerald-700", title: `Weekaanbod verzonden op ${new Date(sent.at).toLocaleString("nl-NL")}`, children: [_jsx(CircleDollarSign, { className: "h-3 w-3" }), " Prijs live in Floriday \u00B7 week ", sent.week ?? "?", " \u00B7 \u20AC ", sent.price.toFixed(2)] }));
|
|
116
|
-
}
|
|
117
|
-
const now = Date.now();
|
|
118
|
-
const endMs = supplyLine?.period_end ? new Date(supplyLine.period_end).getTime() : null;
|
|
105
|
+
// Eén bron, één label: de prijsregel in de Floriday-catalogus.
|
|
106
|
+
const variant = priceStatus(supplyLine ?? null);
|
|
107
|
+
const label = PRICE_STATUS_LABELS[variant];
|
|
108
|
+
const Icon = variant === "live" ? CircleDollarSign : variant === "none" ? MinusCircle : Clock;
|
|
109
|
+
const cls = variant === "live"
|
|
110
|
+
? "bg-emerald-100 text-emerald-700"
|
|
111
|
+
: variant === "none"
|
|
112
|
+
? "bg-muted text-muted-foreground"
|
|
113
|
+
: "bg-amber-100 text-amber-800";
|
|
119
114
|
const qty = supplyLine?.available_quantity ?? 0;
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
let cls = "";
|
|
125
|
-
let variant;
|
|
126
|
-
if (!supplyLine) {
|
|
127
|
-
variant = "none";
|
|
128
|
-
label = "Geen prijs actief";
|
|
129
|
-
Icon = MinusCircle;
|
|
130
|
-
cls = "bg-muted text-muted-foreground";
|
|
131
|
-
}
|
|
132
|
-
else if (isExpired || isDrained) {
|
|
133
|
-
variant = "expired";
|
|
134
|
-
label = isExpired ? "Prijs verlopen" : "Voorraad op";
|
|
135
|
-
Icon = Clock;
|
|
136
|
-
cls = "bg-amber-100 text-amber-800";
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
variant = "live";
|
|
140
|
-
label = "Prijs live in catalogus";
|
|
141
|
-
Icon = CircleDollarSign;
|
|
142
|
-
cls = "bg-emerald-100 text-emerald-700";
|
|
143
|
-
}
|
|
115
|
+
const extra = [
|
|
116
|
+
!offerForSale ? '"Te koop aanbieden" staat uit.' : null,
|
|
117
|
+
sent ? `Laatst verzonden weekaanbod: € ${sent.price.toFixed(2)} · week ${sent.week ?? "?"} (${new Date(sent.at).toLocaleString("nl-NL")}).` : null,
|
|
118
|
+
].filter(Boolean);
|
|
144
119
|
const title = supplyLine
|
|
145
120
|
? [
|
|
146
121
|
`Prijs: ${supplyLine.price_per_piece != null
|
|
@@ -151,6 +126,7 @@ export function PriceStatusBadge({ tradeItemId, productId, offerForSale }) {
|
|
|
151
126
|
]
|
|
152
127
|
.filter(Boolean)
|
|
153
128
|
.join("\n")
|
|
154
|
-
: "Nog geen prijs
|
|
155
|
-
|
|
129
|
+
: "Nog geen prijs in de Floriday-catalogus.";
|
|
130
|
+
const fullTitle = [title, ...extra].join("\n");
|
|
131
|
+
return (_jsxs("span", { className: `inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full cursor-help ${cls}`, title: fullTitle, children: [_jsx(Icon, { className: "h-3 w-3" }), " ", label] }));
|
|
156
132
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProductStructureCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductStructureCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ProductStructureCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductStructureCard.tsx"],"names":[],"mappings":"AAkDA,wBAAgB,oBAAoB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,sCAuQ/E;AAED,oFAAoF;AACpF,wBAAgB,uBAAuB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,sCAkClF"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { isPublishedToFloriday } from "../../lib/publish-status.js";
|
|
2
3
|
/**
|
|
3
4
|
* Overzicht per producttype op de artikelpagina. Alle totalen komen uit de
|
|
4
5
|
* gedeelde databasefunctie `get_product_structure_totals`; hier wordt niets
|
|
@@ -16,6 +17,7 @@ export function ProductStructureCard({ productId }) {
|
|
|
16
17
|
const [totals, setTotals] = useState(null);
|
|
17
18
|
const [setting, setSetting] = useState(null);
|
|
18
19
|
const [queue, setQueue] = useState(null);
|
|
20
|
+
const [floridayId, setFloridayId] = useState(null);
|
|
19
21
|
const [names, setNames] = useState(new Map());
|
|
20
22
|
const [groupHeads, setGroupHeads] = useState([]);
|
|
21
23
|
const [error, setError] = useState(null);
|
|
@@ -25,7 +27,7 @@ export function ProductStructureCard({ productId }) {
|
|
|
25
27
|
let cancelled = false;
|
|
26
28
|
(async () => {
|
|
27
29
|
setLoading(true);
|
|
28
|
-
const [tRes, sRes, qRes] = await Promise.all([
|
|
30
|
+
const [tRes, sRes, qRes, pRes] = await Promise.all([
|
|
29
31
|
supabase.rpc("get_product_structure_totals", { p_product_id: productId }),
|
|
30
32
|
supabase
|
|
31
33
|
.from("floriday_inventory_settings")
|
|
@@ -41,6 +43,7 @@ export function ProductStructureCard({ productId }) {
|
|
|
41
43
|
.order("created_at", { ascending: false })
|
|
42
44
|
.limit(1)
|
|
43
45
|
.maybeSingle(),
|
|
46
|
+
supabase.from("products").select("floriday_trade_item_id").eq("id", productId).maybeSingle(),
|
|
44
47
|
]);
|
|
45
48
|
if (cancelled)
|
|
46
49
|
return;
|
|
@@ -53,6 +56,7 @@ export function ProductStructureCard({ productId }) {
|
|
|
53
56
|
setTotals(t);
|
|
54
57
|
setSetting(sRes.data ?? null);
|
|
55
58
|
setQueue(qRes.data ?? null);
|
|
59
|
+
setFloridayId(pRes.data?.floriday_trade_item_id ?? null);
|
|
56
60
|
const ids = new Set();
|
|
57
61
|
t?.parts?.forEach((p) => p.product_id && ids.add(p.product_id));
|
|
58
62
|
t?.component_of_sets?.forEach((id) => ids.add(id));
|
|
@@ -104,7 +108,7 @@ export function ProductStructureCard({ productId }) {
|
|
|
104
108
|
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx(CardTitle, { children: "Producttype" }), _jsx(Badge, { className: PRODUCT_TYPE_CLASSES[type], title: PRODUCT_TYPE_HELP[type], children: PRODUCT_TYPE_LABELS[type] }), _jsx("span", { title: PRODUCT_TYPE_HELP[type], "aria-label": PRODUCT_TYPE_HELP[type], children: _jsx(Info, { className: "h-4 w-4 text-muted-foreground" }) })] }), _jsx(CardDescription, { children: PRODUCT_TYPE_HELP[type] })] }), _jsxs(CardContent, { className: "space-y-3 text-sm", children: [structureReason && (_jsxs("div", { className: "rounded border border-destructive/30 bg-destructive/10 px-3 py-2 text-destructive", children: ["Actie nodig: ", structureReason] })), type === "GROUP" && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "font-medium", children: [publishedLabel(desired, setting?.packing_size), " \u00B7 som van ", parts.length, " ", parts.length === 1 ? "lid" : "leden", " (", totals?.sum_members ?? 0, " beschikbaar)"] }), _jsx("p", { className: "text-muted-foreground", children: "De leden staan niet los in Floriday; alleen deze groep wordt gepubliceerd. Volgorde = voorraadprioriteit (te wijzigen in de kaart Productgroep hieronder)." }), _jsxs("details", { className: "rounded border", children: [_jsxs("summary", { className: "cursor-pointer px-3 py-2", children: ["Leden tonen (", parts.length, ")"] }), _jsxs("table", { className: "w-full text-xs", children: [_jsx("thead", { className: "text-left text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "p-2", children: "#" }), _jsx("th", { className: "p-2", children: "Lid" }), _jsx("th", { className: "p-2 text-right", children: "Voorraad" }), _jsx("th", { className: "p-2 text-right", children: "Gereserveerd" }), _jsx("th", { className: "p-2 text-right", children: "Beschikbaar" })] }) }), _jsx("tbody", { children: parts.map((p, i) => (_jsxs("tr", { className: "border-t", children: [_jsx("td", { className: "p-2", children: i + 1 }), _jsxs("td", { className: "p-2", children: [p.product_id ? (_jsx(Link, { to: "/voorraad/$id", params: { id: p.product_id }, className: "underline", children: name(p.product_id) })) : ("—"), p.reason && _jsxs("span", { className: "ml-1 text-destructive", children: ["(", ACTION_REASON_LABELS[p.reason] ?? p.reason, ")"] })] }), _jsx("td", { className: "p-2 text-right tabular-nums", children: p.physical ?? 0 }), _jsx("td", { className: "p-2 text-right tabular-nums", children: p.reserved ?? 0 }), _jsx("td", { className: "p-2 text-right tabular-nums", children: p.available ?? 0 })] }, p.product_id ?? i))) })] })] })] })), type === "SET" && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "font-medium", children: ["Berekend: ", totals?.computed ?? 0, " sets", totals?.limiting_product_id && (_jsxs(_Fragment, { children: [" ", "\u00B7 beperkt door: ", name(totals.limiting_product_id), ", ", totals?.computed ?? 0, " sets"] }))] }), _jsx("div", { className: "rounded border border-amber-300 bg-amber-50 px-3 py-2 text-amber-900", children: "Deze set gaat nog niet naar Floriday (\"geen aanbod\") totdat de Logistieke engine bij een bestelling de onderdelen reserveert en afboekt." }), _jsxs("table", { className: "w-full text-xs", children: [_jsx("thead", { className: "text-left text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "p-2", children: "Onderdeel" }), _jsx("th", { className: "p-2 text-right", children: "Per set" }), _jsx("th", { className: "p-2 text-right", children: "Beschikbaar" }), _jsx("th", { className: "p-2 text-right", children: "Genoeg voor" })] }) }), _jsx("tbody", { children: parts.map((p, i) => {
|
|
105
109
|
const limiting = p.product_id && p.product_id === totals?.limiting_product_id;
|
|
106
110
|
return (_jsxs("tr", { className: `border-t ${limiting ? "font-medium" : ""}`, children: [_jsxs("td", { className: "p-2", children: [p.product_id ? (_jsx(Link, { to: "/voorraad/$id", params: { id: p.product_id }, className: "underline", children: name(p.product_id) })) : ("—"), limiting && _jsx("span", { className: "ml-1 text-amber-700", children: "(bepaalt het maximum)" })] }), _jsx("td", { className: "p-2 text-right tabular-nums", children: p.quantity ?? 1 }), _jsx("td", { className: "p-2 text-right tabular-nums", children: p.available ?? 0 }), _jsxs("td", { className: "p-2 text-right tabular-nums", children: [p.possible ?? 0, " sets"] })] }, (p.product_id ?? "") + i));
|
|
107
|
-
}) })] }), _jsx("p", { className: "text-muted-foreground", children: "De onderdelen blijven ook los te koop in Floriday." })] })), groupHeads.length > 0 && (_jsx("div", { className: "rounded border px-3 py-2", children: groupHeads.map((g) => (_jsxs("div", { children: ["Lid van groep", " ", g.headId ? (_jsx(Link, { to: "/voorraad/$id", params: { id: g.headId }, className: "underline font-medium", children: g.name })) : (_jsx("span", { className: "font-medium", children: g.name })), " ", "\u2014 staat niet los in Floriday; de groep wordt gepubliceerd."] }, g.id))) })), (totals?.component_of_sets?.length ?? 0) > 0 && (_jsxs("div", { className: "rounded border px-3 py-2", children: ["Onderdeel van:", " ", totals.component_of_sets.map((id, i) => (_jsxs("span", { children: [i > 0 && ", ", _jsx(Link, { to: "/voorraad/$id", params: { id }, className: "underline", children: name(id) })] }, id))), ". Een verkoop van dit artikel verlaagt ook de berekende stand van die sets."] })), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["Floriday: ", reason ?? (queue?.status === "sent" ? "doorgegeven" : "Nog niet gepubliceerd")] })] })] }));
|
|
111
|
+
}) })] }), _jsx("p", { className: "text-muted-foreground", children: "De onderdelen blijven ook los te koop in Floriday." })] })), groupHeads.length > 0 && (_jsx("div", { className: "rounded border px-3 py-2", children: groupHeads.map((g) => (_jsxs("div", { children: ["Lid van groep", " ", g.headId ? (_jsx(Link, { to: "/voorraad/$id", params: { id: g.headId }, className: "underline font-medium", children: g.name })) : (_jsx("span", { className: "font-medium", children: g.name })), " ", "\u2014 staat niet los in Floriday; de groep wordt gepubliceerd."] }, g.id))) })), (totals?.component_of_sets?.length ?? 0) > 0 && (_jsxs("div", { className: "rounded border px-3 py-2", children: ["Onderdeel van:", " ", totals.component_of_sets.map((id, i) => (_jsxs("span", { children: [i > 0 && ", ", _jsx(Link, { to: "/voorraad/$id", params: { id }, className: "underline", children: name(id) })] }, id))), ". Een verkoop van dit artikel verlaagt ook de berekende stand van die sets."] })), _jsxs("div", { className: "text-xs text-muted-foreground", children: ["Floriday: ", reason ?? (queue?.status === "sent" ? "doorgegeven" : isPublishedToFloriday(floridayId) ? "gepubliceerd" : "Nog niet gepubliceerd")] })] })] }));
|
|
108
112
|
}
|
|
109
113
|
/** Eén regel met de echte reden waarom de voorraad (nog) niet in Floriday staat. */
|
|
110
114
|
export function FloridayStockReasonLine({ productId }) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLocalTradeItems.d.ts","sourceRoot":"","sources":["../../src/hooks/useLocalTradeItems.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAwB,KAAK,kBAAkB,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"useLocalTradeItems.d.ts","sourceRoot":"","sources":["../../src/hooks/useLocalTradeItems.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAwB,KAAK,kBAAkB,EAAE,KAAK,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAIvG;;;;;GAKG;AAEH,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,kBAAkB,CAAC;IAC1C,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,uBAAuB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC1C,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AA0ED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;;;;EA0B9D;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CA4BrF;AAED,MAAM,WAAW,SAAS;IACxB,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,MAAM,CAAC,EAAE,QAAQ,GAAG,cAAc,CAAC;CACpC;AAED,0FAA0F;AAC1F,MAAM,WAAW,eAAe;IAC9B,wEAAwE;IACxE,KAAK,EAAE,OAAO,CAAC;IACf,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,OAAO,GAAG,eAAe,GAAG,IAAI,CAWjF;AAqBD,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,CAAC,CAuGlB;AAED,wEAAwE;AACxE,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAGD;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhF;AAED;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAGpE"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from "react";
|
|
2
2
|
import { supabase } from "../integrations/supabase/client.js";
|
|
3
3
|
import { derivePrimaryPacking } from "../lib/floriday-client.js";
|
|
4
|
+
import { assertArticleCode } from "../lib/article-code.js";
|
|
4
5
|
function asRecord(value) {
|
|
5
6
|
return value && typeof value === "object" && !Array.isArray(value)
|
|
6
7
|
? value
|
|
@@ -150,6 +151,9 @@ export async function upsertLocalTradeItem(params) {
|
|
|
150
151
|
if (!userId)
|
|
151
152
|
throw new Error("Niet ingelogd");
|
|
152
153
|
const payload = params.data ?? {};
|
|
154
|
+
// Handmatig opslaan in het scherm: invultekst of code gelijk aan naam weigeren (concept mag nog leeg zijn).
|
|
155
|
+
if ((payload.articleCode ?? "").trim())
|
|
156
|
+
assertArticleCode(payload.articleCode, payload.name, "manual");
|
|
153
157
|
const linkedProductId = typeof payload.linkedProductId === "string" && payload.linkedProductId
|
|
154
158
|
? String(payload.linkedProductId)
|
|
155
159
|
: null;
|