@flowselections/floriday-voorraad 1.0.28 → 1.0.29
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 +2 -1
- package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelWizard.js +555 -316
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts +2 -1
- package/dist-lib/components/voorraad/EditArtikelPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/EditArtikelPage.js +3 -3
- package/dist-lib/components/voorraad/FustenPage.d.ts +2 -0
- package/dist-lib/components/voorraad/FustenPage.d.ts.map +1 -0
- package/dist-lib/components/voorraad/FustenPage.js +97 -0
- package/dist-lib/components/voorraad/LogistiekTakenCard.d.ts +2 -0
- package/dist-lib/components/voorraad/LogistiekTakenCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/LogistiekTakenCard.js +54 -0
- package/dist-lib/components/voorraad/PublicatiesPage.d.ts +2 -0
- package/dist-lib/components/voorraad/PublicatiesPage.d.ts.map +1 -0
- package/dist-lib/components/voorraad/PublicatiesPage.js +60 -0
- package/dist-lib/components/voorraad/PublishProgressCard.d.ts +12 -0
- package/dist-lib/components/voorraad/PublishProgressCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/PublishProgressCard.js +30 -0
- package/dist-lib/components/voorraad/PublishReadinessCard.d.ts +10 -0
- package/dist-lib/components/voorraad/PublishReadinessCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/PublishReadinessCard.js +12 -0
- package/dist-lib/components/voorraad/StockSyncStatus.d.ts +2 -0
- package/dist-lib/components/voorraad/StockSyncStatus.d.ts.map +1 -0
- package/dist-lib/components/voorraad/StockSyncStatus.js +23 -0
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +5 -1
- package/dist-lib/components/voorraad/VoorraadTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadTable.js +1 -1
- package/dist-lib/hooks/useFusten.d.ts +17 -0
- package/dist-lib/hooks/useFusten.d.ts.map +1 -0
- package/dist-lib/hooks/useFusten.js +81 -0
- package/dist-lib/hooks/useLocalTradeItems.d.ts +14 -4
- package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
- package/dist-lib/hooks/useLocalTradeItems.js +83 -5
- package/dist-lib/hooks/useLogisticsTasks.d.ts +13 -0
- package/dist-lib/hooks/useLogisticsTasks.d.ts.map +1 -0
- package/dist-lib/hooks/useLogisticsTasks.js +155 -0
- package/dist-lib/hooks/usePublishJob.d.ts +13 -0
- package/dist-lib/hooks/usePublishJob.d.ts.map +1 -0
- package/dist-lib/hooks/usePublishJob.js +67 -0
- package/dist-lib/hooks/useRegisterModuleBaseUrl.d.ts +2 -0
- package/dist-lib/hooks/useRegisterModuleBaseUrl.d.ts.map +1 -0
- package/dist-lib/hooks/useRegisterModuleBaseUrl.js +31 -0
- package/dist-lib/hooks/useStockSyncHealth.d.ts +8 -0
- package/dist-lib/hooks/useStockSyncHealth.d.ts.map +1 -0
- package/dist-lib/hooks/useStockSyncHealth.js +42 -0
- package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemStock.js +37 -1
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +5 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +1849 -50
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +1849 -50
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +1849 -50
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +1870 -41
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/floricode-required-features.functions.d.ts +1849 -50
- package/dist-lib/lib/floricode-required-features.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-client.d.ts +1 -1
- package/dist-lib/lib/floriday-client.d.ts.map +1 -1
- package/dist-lib/lib/floriday-client.js +1 -2
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts +1849 -50
- package/dist-lib/lib/floriday-customer-offer.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-customer-offer.functions.js +14 -19
- package/dist-lib/lib/floriday-env.functions.d.ts +35712 -0
- package/dist-lib/lib/floriday-env.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-env.functions.js +16 -0
- package/dist-lib/lib/floriday-env.server.d.ts +12 -0
- package/dist-lib/lib/floriday-env.server.d.ts.map +1 -1
- package/dist-lib/lib/floriday-env.server.js +15 -2
- package/dist-lib/lib/floriday-gateway.functions.d.ts +26934 -82
- package/dist-lib/lib/floriday-gateway.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-gateway.functions.js +12 -3
- package/dist-lib/lib/floriday-readiness.server.d.ts +11 -0
- package/dist-lib/lib/floriday-readiness.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-readiness.server.js +43 -0
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts +3698 -100
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +5442 -45
- package/dist-lib/lib/floriday-warehouse.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-warehouse.functions.js +3 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts +3698 -100
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/fusten.d.ts +87 -0
- package/dist-lib/lib/fusten.d.ts.map +1 -0
- package/dist-lib/lib/fusten.js +389 -0
- package/dist-lib/lib/logistics-engine.d.ts +63 -0
- package/dist-lib/lib/logistics-engine.d.ts.map +1 -0
- package/dist-lib/lib/logistics-engine.js +57 -0
- package/dist-lib/lib/logistics-events.d.ts +18 -0
- package/dist-lib/lib/logistics-events.d.ts.map +1 -0
- package/dist-lib/lib/logistics-events.js +61 -0
- package/dist-lib/lib/module-runtime-config.functions.d.ts +14 -0
- package/dist-lib/lib/module-runtime-config.functions.d.ts.map +1 -0
- package/dist-lib/lib/module-runtime-config.functions.js +107 -0
- package/dist-lib/lib/publish-errors.d.ts +37 -0
- package/dist-lib/lib/publish-errors.d.ts.map +1 -0
- package/dist-lib/lib/publish-errors.js +183 -0
- package/dist-lib/lib/publish-pipeline.d.ts +91 -0
- package/dist-lib/lib/publish-pipeline.d.ts.map +1 -0
- package/dist-lib/lib/publish-pipeline.js +212 -0
- package/dist-lib/lib/publish-readiness.d.ts +42 -0
- package/dist-lib/lib/publish-readiness.d.ts.map +1 -0
- package/dist-lib/lib/publish-readiness.js +102 -0
- package/package.json +2 -2
|
@@ -12,16 +12,23 @@ import { buildAddTradeItemPayload, buildUpdateTradeItemPayload, buildPrimaryPack
|
|
|
12
12
|
import { AdditionalPackagingDialog } from "./AdditionalPackagingDialog";
|
|
13
13
|
import { publishCustomerOfferFn } from "../../lib/floriday-customer-offer.functions";
|
|
14
14
|
import { upsertSupplyLineForTradeItem } from "../../lib/floriday-supply-line";
|
|
15
|
-
import { deleteLocalTradeItem, getLocalTradeItem, upsertLocalTradeItem, } from "../../hooks/useLocalTradeItems";
|
|
15
|
+
import { deleteLocalTradeItem, getLocalTradeItem, markLocalSyncStatus, readLocalSyncStatus, upsertLocalTradeItem, } from "../../hooks/useLocalTradeItems";
|
|
16
16
|
import { useTradeItemSpec } from "../../hooks/useTradeItemSpec";
|
|
17
|
-
import { isMissingCharacteristicsError } from "../../lib/floriday-writes.functions";
|
|
18
17
|
import { useVbnRegulatoryFeatures } from "../../hooks/useVbnRegulatoryFeatures";
|
|
19
18
|
import { useFloridayVbnRequired } from "../../hooks/useFloridayVbnRequired";
|
|
20
19
|
import { saveFloridayVbnRequired } from "../../lib/floriday-vbn-required.functions";
|
|
21
|
-
import { useQueryClient } from "@tanstack/react-query";
|
|
20
|
+
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
22
21
|
import { useCodeList, useTradeItemFormSchema } from "../../hooks/useFloriday";
|
|
23
22
|
import { useFloridayPriceGroups } from "../../hooks/useFloridayPriceGroups";
|
|
24
23
|
import { readTradeItemPrices, syncTradeItemPrices } from "../../lib/trade-item-prices";
|
|
24
|
+
import { createPublishJob, finishPublishJob, markStepSkipped, rememberPublishRejection, runPublishStep, } from "../../lib/publish-pipeline";
|
|
25
|
+
import { translatePublishError, } from "../../lib/publish-errors";
|
|
26
|
+
import { buildReadinessReport } from "../../lib/publish-readiness";
|
|
27
|
+
import { usePublishJob } from "../../hooks/usePublishJob";
|
|
28
|
+
import { PublishReadinessCard } from "./PublishReadinessCard";
|
|
29
|
+
import { PublishProgressCard } from "./PublishProgressCard";
|
|
30
|
+
import { gatewayReadFn } from "../../lib/floriday-gateway.functions";
|
|
31
|
+
import { getFloridayReadinessFn } from "../../lib/floriday-env.functions";
|
|
25
32
|
const floridayLogo = "/flowselections-assets/Voorraad/floriday-logo.png";
|
|
26
33
|
import { VoorraadStockCard } from "../../components/voorraad/VoorraadStockCard";
|
|
27
34
|
import { ProductLinkCard } from "../../components/voorraad/ProductLinkCard";
|
|
@@ -95,14 +102,23 @@ async function lookupProductGroupForVbn(vbnCode) {
|
|
|
95
102
|
.maybeSingle();
|
|
96
103
|
return data ? groupOfRecord(data) : null;
|
|
97
104
|
}
|
|
98
|
-
export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId, } = {}) {
|
|
105
|
+
export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId, resumeJobId, } = {}) {
|
|
99
106
|
const navigate = useNavigate();
|
|
100
107
|
const { activeId, activeConnection, loading: connLoading } = useFloridayConnection();
|
|
108
|
+
const connectionReadiness = useQuery({
|
|
109
|
+
queryKey: ["floriday-readiness", activeId, activeConnection?.active_environment],
|
|
110
|
+
queryFn: () => getFloridayReadinessFn(),
|
|
111
|
+
enabled: Boolean(activeId),
|
|
112
|
+
staleTime: 30000,
|
|
113
|
+
retry: false,
|
|
114
|
+
});
|
|
101
115
|
const { customers: floridayCustomers } = useFlorydayCustomers(activeId);
|
|
102
116
|
const { priceGroups } = useFloridayPriceGroups(activeId);
|
|
103
117
|
const [step, setStep] = useState(0);
|
|
104
118
|
const [data, setData] = useState(empty);
|
|
105
119
|
const [submitting, setSubmitting] = useState(false);
|
|
120
|
+
const [publishJobId, setPublishJobId] = useState(resumeJobId ?? null);
|
|
121
|
+
const { job: publishJob, steps: publishSteps } = usePublishJob(publishJobId);
|
|
106
122
|
const [publishToFloriday, setPublishToFloriday] = useState(true);
|
|
107
123
|
const [draftId, setDraftId] = useState(initialDraftId);
|
|
108
124
|
const [draftFloridayId, setDraftFloridayId] = useState(null);
|
|
@@ -111,6 +127,9 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
111
127
|
const [productLoading, setProductLoading] = useState(Boolean(productId));
|
|
112
128
|
const [syncState, setSyncState] = useState("idle");
|
|
113
129
|
const [syncError, setSyncError] = useState(null);
|
|
130
|
+
// Lokaal opgeslagen, maar (nog) niet bevestigd door Floriday.
|
|
131
|
+
const [pendingSync, setPendingSync] = useState(null);
|
|
132
|
+
const [retryingSync, setRetryingSync] = useState(false);
|
|
114
133
|
const [productMeta, setProductMeta] = useState({ floridayActive: false, floridayTradeItemId: null, physicalStock: 0 });
|
|
115
134
|
const [deleteAck, setDeleteAck] = useState(false);
|
|
116
135
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
@@ -324,6 +343,20 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
324
343
|
console.warn("Floriday-cache laden mislukt", e);
|
|
325
344
|
}
|
|
326
345
|
}
|
|
346
|
+
// Lokale wijzigingen die nog niet door Floriday zijn bevestigd zijn
|
|
347
|
+
// leidend: anders zou de (verouderde) Floriday-kopie de invoer van de
|
|
348
|
+
// gebruiker overschrijven en lijken beladingen/foto's "verwijderd".
|
|
349
|
+
const localSync = readLocalSyncStatus(customFields);
|
|
350
|
+
if (localSync?.dirty) {
|
|
351
|
+
cacheData = null;
|
|
352
|
+
setPendingSync({
|
|
353
|
+
error: localSync.last_error ?? null,
|
|
354
|
+
at: localSync.last_attempt_at ?? null,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
setPendingSync(null);
|
|
359
|
+
}
|
|
327
360
|
const cachePackings = cacheData?.packingConfigurations ?? [];
|
|
328
361
|
const payloadHasPacking = Array.isArray(payload.packingConfigurations) && payload.packingConfigurations.length > 0;
|
|
329
362
|
const seededPacking = cachePackings.length > 0
|
|
@@ -486,14 +519,45 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
486
519
|
toast.error("Artikel niet gevonden in de cache");
|
|
487
520
|
return;
|
|
488
521
|
}
|
|
522
|
+
// Lokale rij met nog niet doorgegeven wijzigingen? Die is leidend.
|
|
523
|
+
let localRow = null;
|
|
524
|
+
try {
|
|
525
|
+
const { data: prod } = await supabase
|
|
526
|
+
.from("products")
|
|
527
|
+
.select("id,custom_fields")
|
|
528
|
+
.eq("floriday_trade_item_id", tradeItemId)
|
|
529
|
+
.maybeSingle();
|
|
530
|
+
localRow = prod ?? null;
|
|
531
|
+
}
|
|
532
|
+
catch {
|
|
533
|
+
/* geen lokale rij; cache is dan leidend */
|
|
534
|
+
}
|
|
535
|
+
if (cancelled)
|
|
536
|
+
return;
|
|
537
|
+
const localSync = readLocalSyncStatus(localRow?.custom_fields);
|
|
538
|
+
const localPayload = localRow?.custom_fields?.floriday_payload;
|
|
539
|
+
if (localRow?.id)
|
|
540
|
+
setDraftId(localRow.id);
|
|
541
|
+
if (localSync?.dirty) {
|
|
542
|
+
setPendingSync({
|
|
543
|
+
error: localSync.last_error ?? null,
|
|
544
|
+
at: localSync.last_attempt_at ?? null,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
setPendingSync(null);
|
|
549
|
+
}
|
|
489
550
|
const wizardData = floridayCacheToWizardData(row.data);
|
|
490
|
-
const
|
|
491
|
-
|
|
551
|
+
const baseData = localSync?.dirty && localPayload
|
|
552
|
+
? { ...wizardData, ...localPayload }
|
|
553
|
+
: wizardData;
|
|
554
|
+
const productGroup = baseData.productGroup ??
|
|
555
|
+
(await lookupProductGroupForVbn(baseData.vbnCode));
|
|
492
556
|
const prefWarehouseId = pref?.warehouse_id ?? "";
|
|
493
|
-
const cachedWarehouseId =
|
|
557
|
+
const cachedWarehouseId = baseData.warehouseId ?? "";
|
|
494
558
|
const preferredWarehouseFallback = prefWarehouseId || cachedWarehouseId ? null : await resolvePreferredWarehouseId();
|
|
495
559
|
setData({
|
|
496
|
-
...
|
|
560
|
+
...baseData,
|
|
497
561
|
productGroup,
|
|
498
562
|
warehouseId: prefWarehouseId || cachedWarehouseId || preferredWarehouseFallback || "",
|
|
499
563
|
});
|
|
@@ -808,20 +872,94 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
808
872
|
}, 50);
|
|
809
873
|
return () => window.clearTimeout(id);
|
|
810
874
|
}, [step]);
|
|
811
|
-
|
|
875
|
+
// ---- Publiceren -----------------------------------------------------
|
|
876
|
+
// Elke publicatie is één opdracht met vaste deelstappen. Geslaagde stappen
|
|
877
|
+
// worden bij opnieuw proberen overgeslagen, tijdelijke storingen worden
|
|
878
|
+
// automatisch herhaald en alles is terug te zien bij "Publicaties".
|
|
879
|
+
const characteristicLabels = useMemo(() => {
|
|
880
|
+
const out = {};
|
|
881
|
+
for (const c of spec.characteristics)
|
|
882
|
+
out[c.code] = c.label || c.code;
|
|
883
|
+
return out;
|
|
884
|
+
}, [spec.characteristics]);
|
|
885
|
+
const wantsPublish = (isEditingPublished && Boolean(tradeItemId)) ||
|
|
886
|
+
(!isEditingPublished && publishToFloriday);
|
|
887
|
+
const readiness = useMemo(() => buildReadinessReport({
|
|
888
|
+
data,
|
|
889
|
+
spec,
|
|
890
|
+
connectionOk: Boolean(activeId) && connectionReadiness.data?.ready === true,
|
|
891
|
+
connectionLoading: connLoading || connectionReadiness.isLoading,
|
|
892
|
+
environment: activeConnection?.active_environment ?? null,
|
|
893
|
+
connectionMessage: connectionReadiness.data?.message ?? null,
|
|
894
|
+
schemaReady: schemaQuery.isSuccess,
|
|
895
|
+
learnedCharacteristicCodes: serverRequiredCodes,
|
|
896
|
+
characteristicLabels,
|
|
897
|
+
wantsPublish,
|
|
898
|
+
}), [
|
|
899
|
+
data,
|
|
900
|
+
spec,
|
|
901
|
+
activeId,
|
|
902
|
+
connLoading,
|
|
903
|
+
connectionReadiness.data,
|
|
904
|
+
connectionReadiness.isLoading,
|
|
905
|
+
activeConnection?.active_environment,
|
|
906
|
+
schemaQuery.isSuccess,
|
|
907
|
+
serverRequiredCodes,
|
|
908
|
+
characteristicLabels,
|
|
909
|
+
wantsPublish,
|
|
910
|
+
]);
|
|
911
|
+
const goToStepKey = useCallback((key) => {
|
|
912
|
+
const idx = visibleSteps(data).findIndex((s) => s.key === key);
|
|
913
|
+
if (idx >= 0)
|
|
914
|
+
setStep(idx);
|
|
915
|
+
}, [data]);
|
|
916
|
+
const handleStepFailure = useCallback(async (friendly) => {
|
|
917
|
+
if (!friendly)
|
|
918
|
+
return;
|
|
919
|
+
if (friendly.code === "MISSING_CHARACTERISTICS" && friendly.missingCodes?.length) {
|
|
920
|
+
if (data.vbnCode) {
|
|
921
|
+
try {
|
|
922
|
+
const res = await saveFloridayVbnRequired({
|
|
923
|
+
data: { vbnCode: data.vbnCode, codes: friendly.missingCodes },
|
|
924
|
+
});
|
|
925
|
+
queryClient.setQueryData(["floriday-vbn-required", data.vbnCode], {
|
|
926
|
+
vbnCode: res.vbnCode,
|
|
927
|
+
requiredCodes: res.requiredCodes,
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
catch {
|
|
931
|
+
/* leren mag stilletjes falen */
|
|
932
|
+
}
|
|
933
|
+
void rememberPublishRejection({
|
|
934
|
+
vbnCode: data.vbnCode,
|
|
935
|
+
scope: "characteristic",
|
|
936
|
+
keys: friendly.missingCodes,
|
|
937
|
+
lastError: friendly.detail,
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
goToStepKey("eigenschappen");
|
|
941
|
+
}
|
|
942
|
+
else if (friendly.action?.stepKey) {
|
|
943
|
+
goToStepKey(friendly.action.stepKey);
|
|
944
|
+
}
|
|
945
|
+
toast.error(friendly.message, {
|
|
946
|
+
description: friendly.hint,
|
|
947
|
+
});
|
|
948
|
+
}, [data.vbnCode, goToStepKey, queryClient]);
|
|
949
|
+
const submit = async (options) => {
|
|
812
950
|
if (!activeId) {
|
|
813
|
-
toast.error("Geen actieve
|
|
951
|
+
toast.error("Geen actieve verbinding met de handelsomgeving", {
|
|
952
|
+
description: "Stel de verbinding in bij Instellingen.",
|
|
953
|
+
});
|
|
814
954
|
return;
|
|
815
955
|
}
|
|
816
|
-
// Publish-gate: blokkeer zolang het trade-item schema niet succesvol
|
|
817
|
-
// geladen is — anders kunnen verplichte karakteristieken ontbreken.
|
|
818
|
-
const wantsPublish = (isEditingPublished && Boolean(tradeItemId)) ||
|
|
819
|
-
(!isEditingPublished && publishToFloriday);
|
|
820
956
|
if (wantsPublish && !schemaQuery.isSuccess) {
|
|
821
|
-
toast.error("
|
|
957
|
+
toast.error("De productregels worden nog opgehaald", {
|
|
958
|
+
description: "Wacht heel even en probeer het opnieuw.",
|
|
959
|
+
});
|
|
822
960
|
return;
|
|
823
961
|
}
|
|
824
|
-
//
|
|
962
|
+
// Strikte controle van alle stappen voor verzending.
|
|
825
963
|
for (const s of steps) {
|
|
826
964
|
const v = validateStepLocal(s.key, data);
|
|
827
965
|
if (!v.ok) {
|
|
@@ -832,68 +970,226 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
832
970
|
return;
|
|
833
971
|
}
|
|
834
972
|
}
|
|
973
|
+
// Lokaal bewaren (niet publiceren) blijft een simpele actie zonder opdracht.
|
|
974
|
+
if (!wantsPublish) {
|
|
975
|
+
setSubmitting(true);
|
|
976
|
+
try {
|
|
977
|
+
const newId = await upsertLocalTradeItem({
|
|
978
|
+
id: draftId,
|
|
979
|
+
connectionId: activeId,
|
|
980
|
+
data,
|
|
981
|
+
});
|
|
982
|
+
setDraftId(newId);
|
|
983
|
+
toast.success(draftId
|
|
984
|
+
? "Wijzigingen opgeslagen (nog niet gepubliceerd)"
|
|
985
|
+
: "Artikel opgeslagen (nog niet gepubliceerd)");
|
|
986
|
+
navigate({ to: "/voorraad" });
|
|
987
|
+
}
|
|
988
|
+
catch (e) {
|
|
989
|
+
const friendly = translatePublishError(e);
|
|
990
|
+
toast.error(friendly.message, { description: friendly.hint });
|
|
991
|
+
}
|
|
992
|
+
finally {
|
|
993
|
+
setSubmitting(false);
|
|
994
|
+
}
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
// Publiceren begint altijd met een lokale opslag, zodat een afgebroken of
|
|
998
|
+
// mislukte publicatie nooit tot verlies van ingevoerde gegevens leidt.
|
|
999
|
+
try {
|
|
1000
|
+
const localId = await upsertLocalTradeItem({
|
|
1001
|
+
id: draftId,
|
|
1002
|
+
connectionId: activeId,
|
|
1003
|
+
data,
|
|
1004
|
+
floridayTradeItemId: (isEditingPublished && tradeItemId) ? tradeItemId : (draftFloridayId || null),
|
|
1005
|
+
sync: { dirty: true, last_error: null },
|
|
1006
|
+
});
|
|
1007
|
+
if (localId && localId !== draftId)
|
|
1008
|
+
setDraftId(localId);
|
|
1009
|
+
}
|
|
1010
|
+
catch (e) {
|
|
1011
|
+
toast.error(`Lokaal opslaan mislukt: ${e.message}`);
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
try {
|
|
1015
|
+
const serverConnection = await getFloridayReadinessFn();
|
|
1016
|
+
const browserEnvironment = activeConnection?.active_environment;
|
|
1017
|
+
if (!serverConnection.ready) {
|
|
1018
|
+
toast.error(serverConnection.message, {
|
|
1019
|
+
description: "Controleer de verbinding bij Instellingen.",
|
|
1020
|
+
});
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (serverConnection.connectionId !== activeId ||
|
|
1024
|
+
(browserEnvironment && serverConnection.environment !== browserEnvironment)) {
|
|
1025
|
+
toast.error("De verbinding is gewijzigd", {
|
|
1026
|
+
description: "Vernieuw de pagina en probeer opnieuw.",
|
|
1027
|
+
});
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
catch {
|
|
1032
|
+
toast.error("De verbinding kan niet worden gecontroleerd", {
|
|
1033
|
+
description: "Vernieuw de pagina en probeer opnieuw.",
|
|
1034
|
+
});
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
835
1037
|
setSubmitting(true);
|
|
1038
|
+
const environment = activeConnection?.active_environment;
|
|
1039
|
+
if (environment !== "staging" && environment !== "live") {
|
|
1040
|
+
toast.error("De gekozen omgeving is niet geldig", {
|
|
1041
|
+
description: "Kies de testomgeving of liveomgeving bij Instellingen.",
|
|
1042
|
+
});
|
|
1043
|
+
setSubmitting(false);
|
|
1044
|
+
return;
|
|
1045
|
+
}
|
|
1046
|
+
const intent = isEditingPublished ? "update" : "create";
|
|
1047
|
+
const activeStepKeys = ["trade-item", "verify"];
|
|
1048
|
+
if (data.offerForSale) {
|
|
1049
|
+
activeStepKeys.push("stock", "offer", "supply-line", "prices");
|
|
1050
|
+
}
|
|
1051
|
+
else if (isEditingPublished) {
|
|
1052
|
+
activeStepKeys.push("prices");
|
|
1053
|
+
}
|
|
1054
|
+
let jobId = options?.resumeJobId ?? null;
|
|
1055
|
+
if (!jobId) {
|
|
1056
|
+
const job = await createPublishJob({
|
|
1057
|
+
connectionId: activeId,
|
|
1058
|
+
environment,
|
|
1059
|
+
intent,
|
|
1060
|
+
title: data.name || data.vbnName || "Product",
|
|
1061
|
+
productId: productId ?? null,
|
|
1062
|
+
tradeItemId: tradeItemId ?? null,
|
|
1063
|
+
snapshot: { articleCode: data.articleCode, vbnCode: data.vbnCode },
|
|
1064
|
+
activeSteps: activeStepKeys,
|
|
1065
|
+
});
|
|
1066
|
+
jobId = job?.id ?? null;
|
|
1067
|
+
}
|
|
1068
|
+
setPublishJobId(jobId);
|
|
1069
|
+
const stepOpts = { characteristicLabels };
|
|
1070
|
+
let resolvedTradeItemId = tradeItemId ?? null;
|
|
836
1071
|
try {
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
try {
|
|
848
|
-
const cachePayload = {
|
|
849
|
-
...buildAddTradeItemPayload(data),
|
|
850
|
-
tradeItemId,
|
|
851
|
-
};
|
|
852
|
-
await supabase
|
|
853
|
-
.from("floriday_trade_items_cache")
|
|
854
|
-
.update({
|
|
855
|
-
data: cachePayload,
|
|
856
|
-
fetched_at: new Date().toISOString(),
|
|
857
|
-
})
|
|
858
|
-
.eq("connection_id", activeId)
|
|
859
|
-
.eq("floriday_id", tradeItemId);
|
|
1072
|
+
// 1) Productgegevens -------------------------------------------------
|
|
1073
|
+
const core = await runPublishStep(jobId, "trade-item", async () => {
|
|
1074
|
+
if (isEditingPublished && tradeItemId) {
|
|
1075
|
+
await saveWarehousePreference(tradeItemId, data.warehouseId);
|
|
1076
|
+
const payload = buildUpdateTradeItemPayload(data);
|
|
1077
|
+
const res = await floriday.updateTradeItem(activeId, tradeItemId, payload);
|
|
1078
|
+
if (!res?.attemptId) {
|
|
1079
|
+
throw new Error("De wijziging is niet bevestigd door de handelsomgeving");
|
|
1080
|
+
}
|
|
1081
|
+
return { tradeItemId };
|
|
860
1082
|
}
|
|
861
|
-
|
|
862
|
-
|
|
1083
|
+
const payload = buildAddTradeItemPayload(data);
|
|
1084
|
+
const res = await floriday.createTradeItem(activeId, payload);
|
|
1085
|
+
if (!res?.attemptId) {
|
|
1086
|
+
throw new Error("Het aanmaken is niet bevestigd door de handelsomgeving");
|
|
863
1087
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
if (
|
|
1088
|
+
const createdId = res.tradeItemId ?? payload.tradeItemId;
|
|
1089
|
+
await saveWarehousePreference(createdId, data.warehouseId);
|
|
1090
|
+
if (draftId) {
|
|
867
1091
|
try {
|
|
868
|
-
|
|
869
|
-
data.packingConfigurations[0];
|
|
870
|
-
if (primary?.id) {
|
|
871
|
-
await upsertSupplyLineForTradeItem({
|
|
872
|
-
connectionId: activeId,
|
|
873
|
-
tradeItemId,
|
|
874
|
-
pricePerPiece: data.salePricePerPiece,
|
|
875
|
-
packingConfigurationId: primary.id,
|
|
876
|
-
availableQuantity: data.saleStockPieces,
|
|
877
|
-
customerOrganizationIds: data.customerOrganizationIds,
|
|
878
|
-
periodStart: data.offerStartDate
|
|
879
|
-
? new Date(`${data.offerStartDate}T00:00:00Z`).toISOString()
|
|
880
|
-
: undefined,
|
|
881
|
-
});
|
|
882
|
-
toast.success("Prijs bijgewerkt in Floriday-catalogus");
|
|
883
|
-
}
|
|
1092
|
+
await deleteLocalTradeItem(draftId);
|
|
884
1093
|
}
|
|
885
|
-
catch
|
|
886
|
-
|
|
1094
|
+
catch {
|
|
1095
|
+
/* niet kritiek */
|
|
887
1096
|
}
|
|
888
1097
|
}
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1098
|
+
return { tradeItemId: createdId };
|
|
1099
|
+
}, stepOpts);
|
|
1100
|
+
if (!core.ok) {
|
|
1101
|
+
await handleStepFailure(core.error);
|
|
1102
|
+
if (jobId)
|
|
1103
|
+
await finishPublishJob(jobId);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
resolvedTradeItemId =
|
|
1107
|
+
core.value?.tradeItemId ??
|
|
1108
|
+
resolvedTradeItemId;
|
|
1109
|
+
const primary = data.packingConfigurations.find((p) => p.isPrimary) ??
|
|
1110
|
+
data.packingConfigurations[0];
|
|
1111
|
+
// 2) Voorraad ---------------------------------------------------------
|
|
1112
|
+
if (activeStepKeys.includes("stock") && resolvedTradeItemId) {
|
|
1113
|
+
await runPublishStep(jobId, "stock", async () => {
|
|
1114
|
+
const key = stockKeyFor({ tradeItemId: resolvedTradeItemId });
|
|
1115
|
+
if (!key || !(data.saleStockPieces > 0))
|
|
1116
|
+
return { skipped: true };
|
|
1117
|
+
const { data: existing } = await supabase
|
|
1118
|
+
.from("trade_item_stock")
|
|
1119
|
+
.select("trade_item_key")
|
|
1120
|
+
.eq("connection_id", activeId)
|
|
1121
|
+
.eq("trade_item_key", key)
|
|
1122
|
+
.maybeSingle();
|
|
1123
|
+
if (existing)
|
|
1124
|
+
return { kept: true };
|
|
1125
|
+
const userRes = await supabase.auth.getUser();
|
|
1126
|
+
const userId = userRes.data.user?.id;
|
|
1127
|
+
if (!userId)
|
|
1128
|
+
throw new Error("Unauthorized");
|
|
1129
|
+
const { error } = await supabase.from("trade_item_stock").insert({
|
|
1130
|
+
user_id: userId,
|
|
1131
|
+
connection_id: activeId,
|
|
1132
|
+
trade_item_key: key,
|
|
1133
|
+
physical_stock: data.saleStockPieces,
|
|
1134
|
+
minimum_stock: 0,
|
|
1135
|
+
reserved_stock: 0,
|
|
1136
|
+
incoming_stock: 0,
|
|
1137
|
+
});
|
|
1138
|
+
if (error)
|
|
1139
|
+
throw new Error(error.message);
|
|
1140
|
+
return { created: true };
|
|
1141
|
+
}, stepOpts);
|
|
1142
|
+
}
|
|
1143
|
+
// 3) Aanbod -----------------------------------------------------------
|
|
1144
|
+
if (activeStepKeys.includes("offer") && resolvedTradeItemId && !isEditingPublished) {
|
|
1145
|
+
await runPublishStep(jobId, "offer", async () => {
|
|
1146
|
+
const packingBase = buildPrimaryPackingBase(data);
|
|
1147
|
+
await publishCustomerOfferFn({
|
|
1148
|
+
data: {
|
|
1149
|
+
customerOfferId: crypto.randomUUID(),
|
|
1150
|
+
tradeItemId: resolvedTradeItemId,
|
|
1151
|
+
title: data.name || data.vbnName || "Aanbod",
|
|
1152
|
+
description: "",
|
|
1153
|
+
pricePerPiece: data.salePricePerPiece,
|
|
1154
|
+
currency: "EUR",
|
|
1155
|
+
despatchWarehouseId: data.warehouseId,
|
|
1156
|
+
numberOfPieces: data.saleStockPieces,
|
|
1157
|
+
allowedCustomerOrganizationIds: data.customerOrganizationIds,
|
|
1158
|
+
packingConfiguration: packingBase,
|
|
1159
|
+
orderStart: new Date(`${data.offerStartDate}T00:00:00Z`).toISOString(),
|
|
1160
|
+
},
|
|
1161
|
+
});
|
|
1162
|
+
return { published: true };
|
|
1163
|
+
}, stepOpts);
|
|
1164
|
+
}
|
|
1165
|
+
else if (jobId && activeStepKeys.includes("offer")) {
|
|
1166
|
+
await markStepSkipped(jobId, "offer");
|
|
1167
|
+
}
|
|
1168
|
+
// 4) Prijs online ------------------------------------------------------
|
|
1169
|
+
if (activeStepKeys.includes("supply-line") && resolvedTradeItemId) {
|
|
1170
|
+
await runPublishStep(jobId, "supply-line", async () => {
|
|
1171
|
+
if (!primary?.id || !(data.salePricePerPiece > 0))
|
|
1172
|
+
return { skipped: true };
|
|
1173
|
+
const sl = await upsertSupplyLineForTradeItem({
|
|
1174
|
+
connectionId: activeId,
|
|
1175
|
+
tradeItemId: resolvedTradeItemId,
|
|
1176
|
+
pricePerPiece: data.salePricePerPiece,
|
|
1177
|
+
packingConfigurationId: primary.id,
|
|
1178
|
+
availableQuantity: data.saleStockPieces,
|
|
1179
|
+
customerOrganizationIds: data.customerOrganizationIds,
|
|
1180
|
+
periodStart: data.offerStartDate
|
|
1181
|
+
? new Date(`${data.offerStartDate}T00:00:00Z`).toISOString()
|
|
1182
|
+
: undefined,
|
|
1183
|
+
});
|
|
1184
|
+
return { supplyLineId: sl.supplyLineId ?? null };
|
|
1185
|
+
}, stepOpts);
|
|
1186
|
+
}
|
|
1187
|
+
// 5) Prijsafspraken ----------------------------------------------------
|
|
1188
|
+
if (activeStepKeys.includes("prices") && resolvedTradeItemId) {
|
|
1189
|
+
await runPublishStep(jobId, "prices", async () => {
|
|
894
1190
|
const priceSync = await syncTradeItemPrices({
|
|
895
|
-
tradeItemId,
|
|
896
|
-
tradeItemKey:
|
|
1191
|
+
tradeItemId: resolvedTradeItemId,
|
|
1192
|
+
tradeItemKey: resolvedTradeItemId,
|
|
897
1193
|
availableQuantity: data.saleStockPieces,
|
|
898
1194
|
currency: "EUR",
|
|
899
1195
|
periodStart: data.offerStartDate
|
|
@@ -906,199 +1202,82 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
906
1202
|
});
|
|
907
1203
|
const failed = priceSync.groupResults.filter((r) => !r.ok);
|
|
908
1204
|
if (failed.length) {
|
|
909
|
-
|
|
1205
|
+
throw new Error(`Deze prijsafspraken zijn niet doorgekomen: ${failed
|
|
1206
|
+
.map((r) => r.name)
|
|
1207
|
+
.join(", ")}`);
|
|
910
1208
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
}
|
|
914
|
-
}
|
|
915
|
-
catch (e) {
|
|
916
|
-
toast.error(`Prijzen synchroniseren mislukt: ${e.message}`);
|
|
917
|
-
}
|
|
1209
|
+
return { groups: priceSync.groupResults.length };
|
|
1210
|
+
}, stepOpts);
|
|
918
1211
|
}
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
const
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
await deleteLocalTradeItem(draftId);
|
|
1212
|
+
// 6) Controle achteraf --------------------------------------------------
|
|
1213
|
+
if (resolvedTradeItemId) {
|
|
1214
|
+
const verify = await runPublishStep(jobId, "verify", async () => {
|
|
1215
|
+
const res = (await gatewayReadFn({
|
|
1216
|
+
data: {
|
|
1217
|
+
path: `/suppliers-api-2026v1/trade-items/${resolvedTradeItemId}`,
|
|
1218
|
+
},
|
|
1219
|
+
}));
|
|
1220
|
+
const item = res?.data;
|
|
1221
|
+
if (!item) {
|
|
1222
|
+
throw new Error("Het product kon niet worden teruggelezen");
|
|
931
1223
|
}
|
|
932
|
-
|
|
933
|
-
// Niet kritiek; rij verdwijnt na volgende reload
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
// ---- Direct-sale flow -----------------------------------------
|
|
937
|
-
// Alleen doorzetten als de gebruiker de "Aanbieden"-stap geactiveerd
|
|
938
|
-
// heeft. Falen mag de TradeItem-succes NIET terugdraaien — daarom
|
|
939
|
-
// in een aparte try/catch en apart toast bericht.
|
|
940
|
-
if (data.offerForSale) {
|
|
941
|
-
const tradeItemId = createdTradeItemId;
|
|
942
|
-
// 1) Seed lokale voorraad (physical_stock) — de continuous-stock
|
|
943
|
-
// trigger pusht dit naar Floriday zodra de trade item in de
|
|
944
|
-
// cache verschijnt.
|
|
945
|
-
// GUARD: nooit een BESTAANDE voorraad-rij overschrijven. Als
|
|
946
|
-
// er al een rij is (bv. bij herpubliceren of bij een lokaal
|
|
947
|
-
// product met historische voorraad), laten we die met rust
|
|
948
|
-
// om ongewild op 0 zetten te voorkomen. Voorraad wordt
|
|
949
|
-
// beheerd via de Voorraadkaart, niet via de wizard.
|
|
950
|
-
try {
|
|
951
|
-
const key = stockKeyFor({ tradeItemId });
|
|
952
|
-
if (key && data.saleStockPieces > 0) {
|
|
953
|
-
const { data: existing } = await supabase
|
|
954
|
-
.from("trade_item_stock")
|
|
955
|
-
.select("trade_item_key")
|
|
956
|
-
.eq("connection_id", activeId)
|
|
957
|
-
.eq("trade_item_key", key)
|
|
958
|
-
.maybeSingle();
|
|
959
|
-
if (!existing) {
|
|
960
|
-
const userRes = await supabase.auth.getUser();
|
|
961
|
-
const userId = userRes.data.user?.id;
|
|
962
|
-
if (userId) {
|
|
963
|
-
await supabase
|
|
964
|
-
.from("trade_item_stock")
|
|
965
|
-
.insert({
|
|
966
|
-
user_id: userId,
|
|
967
|
-
connection_id: activeId,
|
|
968
|
-
trade_item_key: key,
|
|
969
|
-
physical_stock: data.saleStockPieces,
|
|
970
|
-
minimum_stock: 0,
|
|
971
|
-
reserved_stock: 0,
|
|
972
|
-
incoming_stock: 0,
|
|
973
|
-
});
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
catch (e) {
|
|
979
|
-
toast.error(`Voorraad opslaan mislukt: ${e.message}`);
|
|
980
|
-
}
|
|
981
|
-
// 2) CustomerOffer publiceren (POST /customer-offers)
|
|
982
|
-
try {
|
|
983
|
-
const packingBase = buildPrimaryPackingBase(data);
|
|
984
|
-
await publishCustomerOfferFn({
|
|
985
|
-
data: {
|
|
986
|
-
customerOfferId: crypto.randomUUID(),
|
|
987
|
-
tradeItemId,
|
|
988
|
-
title: data.name || data.vbnName || "Aanbod",
|
|
989
|
-
description: "",
|
|
990
|
-
pricePerPiece: data.salePricePerPiece,
|
|
991
|
-
currency: "EUR",
|
|
992
|
-
despatchWarehouseId: data.warehouseId,
|
|
993
|
-
numberOfPieces: data.saleStockPieces,
|
|
994
|
-
allowedCustomerOrganizationIds: data.customerOrganizationIds,
|
|
995
|
-
packingConfiguration: packingBase,
|
|
996
|
-
orderStart: new Date(`${data.offerStartDate}T00:00:00Z`).toISOString(),
|
|
997
|
-
},
|
|
998
|
-
});
|
|
999
|
-
toast.success("Artikel aangemaakt en aangeboden in Floriday");
|
|
1000
|
-
}
|
|
1001
|
-
catch (e) {
|
|
1002
|
-
toast.error(`Aanbod publiceren mislukt: ${e.message}`);
|
|
1003
|
-
}
|
|
1004
|
-
// 3) Supply-line pushen naar de Authenticatie-module gateway
|
|
1005
|
-
// zodat het product met prijs online komt in de Floriday-catalogus.
|
|
1006
|
-
// Idempotent op supplyLineId — herprijzen hergebruikt dezelfde regel.
|
|
1224
|
+
// Lokale kopie pas bijwerken nadat de handelsomgeving bevestigd heeft.
|
|
1007
1225
|
try {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
availableQuantity: data.saleStockPieces,
|
|
1017
|
-
customerOrganizationIds: data.customerOrganizationIds,
|
|
1018
|
-
periodStart: data.offerStartDate
|
|
1019
|
-
? new Date(`${data.offerStartDate}T00:00:00Z`).toISOString()
|
|
1020
|
-
: undefined,
|
|
1021
|
-
});
|
|
1022
|
-
if (sl.supplyLineId) {
|
|
1023
|
-
toast.success("Prijs online gezet in Floriday-catalogus");
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
catch (e) {
|
|
1028
|
-
toast.error(`Prijs online zetten mislukt: ${e.message}`);
|
|
1226
|
+
await supabase
|
|
1227
|
+
.from("floriday_trade_items_cache")
|
|
1228
|
+
.update({
|
|
1229
|
+
data: item,
|
|
1230
|
+
fetched_at: new Date().toISOString(),
|
|
1231
|
+
})
|
|
1232
|
+
.eq("connection_id", activeId)
|
|
1233
|
+
.eq("floriday_id", resolvedTradeItemId);
|
|
1029
1234
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
const primary = data.packingConfigurations.find((p) => p.isPrimary) ??
|
|
1033
|
-
data.packingConfigurations[0];
|
|
1034
|
-
const priceSync = await syncTradeItemPrices({
|
|
1035
|
-
tradeItemId,
|
|
1036
|
-
tradeItemKey: tradeItemId,
|
|
1037
|
-
availableQuantity: data.saleStockPieces,
|
|
1038
|
-
currency: "EUR",
|
|
1039
|
-
periodStart: data.offerStartDate
|
|
1040
|
-
? new Date(`${data.offerStartDate}T00:00:00Z`).toISOString()
|
|
1041
|
-
: undefined,
|
|
1042
|
-
basePricePerPiece: data.salePricePerPiece,
|
|
1043
|
-
pricesByGroupId: data.priceGroupPrices ?? {},
|
|
1044
|
-
priceGroups,
|
|
1045
|
-
packingConfigurationId: primary?.id,
|
|
1046
|
-
});
|
|
1047
|
-
const failed = priceSync.groupResults.filter((r) => !r.ok);
|
|
1048
|
-
if (failed.length) {
|
|
1049
|
-
toast.error(`Prijsgroep(en) niet gepusht: ${failed.map((r) => r.name).join(", ")}`);
|
|
1050
|
-
}
|
|
1235
|
+
catch {
|
|
1236
|
+
/* wordt automatisch bijgewerkt bij de volgende synchronisatie */
|
|
1051
1237
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1238
|
+
const warnings = [];
|
|
1239
|
+
const remoteName = item?.tradeItemName ?? item?.name;
|
|
1240
|
+
if (remoteName && data.name && remoteName !== data.name) {
|
|
1241
|
+
warnings.push("De artikelnaam wijkt af");
|
|
1054
1242
|
}
|
|
1243
|
+
return { warnings };
|
|
1244
|
+
}, stepOpts);
|
|
1245
|
+
if (!verify.ok) {
|
|
1246
|
+
toast.warning("Het product is opgeslagen, maar nog niet bevestigd", {
|
|
1247
|
+
description: "Controleer het later bij Publicaties.",
|
|
1248
|
+
});
|
|
1055
1249
|
}
|
|
1056
|
-
else {
|
|
1057
|
-
toast.success("Artikel aangemaakt in Floriday");
|
|
1058
|
-
}
|
|
1059
|
-
}
|
|
1060
|
-
else {
|
|
1061
|
-
const newId = await upsertLocalTradeItem({
|
|
1062
|
-
id: draftId,
|
|
1063
|
-
connectionId: activeId,
|
|
1064
|
-
data,
|
|
1065
|
-
});
|
|
1066
|
-
setDraftId(newId);
|
|
1067
|
-
toast.success(draftId
|
|
1068
|
-
? "Wijzigingen opgeslagen (nog niet gepubliceerd)"
|
|
1069
|
-
: "Artikel opgeslagen (nog niet gepubliceerd)");
|
|
1070
1250
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
if (data.vbnCode) {
|
|
1082
|
-
try {
|
|
1083
|
-
const res = await saveFloridayVbnRequired({
|
|
1084
|
-
data: { vbnCode: data.vbnCode, codes: missingCodes },
|
|
1085
|
-
});
|
|
1086
|
-
queryClient.setQueryData(["floriday-vbn-required", data.vbnCode], { vbnCode: res.vbnCode, requiredCodes: res.requiredCodes });
|
|
1087
|
-
}
|
|
1088
|
-
catch { /* cache-write mag stilletjes falen */ }
|
|
1251
|
+
const status = jobId ? await finishPublishJob(jobId, {
|
|
1252
|
+
tradeItemId: resolvedTradeItemId,
|
|
1253
|
+
}) : "succeeded";
|
|
1254
|
+
if (status === "succeeded") {
|
|
1255
|
+
if (draftId) {
|
|
1256
|
+
await markLocalSyncStatus(draftId, {
|
|
1257
|
+
dirty: false,
|
|
1258
|
+
last_error: null,
|
|
1259
|
+
last_synced_at: new Date().toISOString(),
|
|
1260
|
+
}).catch(() => { });
|
|
1089
1261
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
toast.error(`Floriday vereist nog: ${missingCodes.join(", ")}. Vul deze eigenschappen in en probeer opnieuw.`);
|
|
1262
|
+
setPendingSync(null);
|
|
1263
|
+
toast.success(isEditingPublished ? "Product bijgewerkt" : "Product gepubliceerd");
|
|
1264
|
+
navigate({ to: "/voorraad" });
|
|
1094
1265
|
}
|
|
1095
|
-
else if (
|
|
1096
|
-
toast.
|
|
1266
|
+
else if (status === "succeeded_with_warnings") {
|
|
1267
|
+
toast.warning("Gepubliceerd, met aandachtspunten", {
|
|
1268
|
+
description: "Bekijk wat er nog moet gebeuren in het overzicht hieronder.",
|
|
1269
|
+
});
|
|
1097
1270
|
}
|
|
1098
1271
|
else {
|
|
1099
|
-
toast.error(
|
|
1272
|
+
toast.error("Publiceren is niet gelukt");
|
|
1100
1273
|
}
|
|
1101
1274
|
}
|
|
1275
|
+
catch (e) {
|
|
1276
|
+
const friendly = translatePublishError(e, { characteristicLabels });
|
|
1277
|
+
await handleStepFailure(friendly);
|
|
1278
|
+
if (jobId)
|
|
1279
|
+
await finishPublishJob(jobId);
|
|
1280
|
+
}
|
|
1102
1281
|
finally {
|
|
1103
1282
|
setSubmitting(false);
|
|
1104
1283
|
}
|
|
@@ -1151,12 +1330,113 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1151
1330
|
setSubmitting(false);
|
|
1152
1331
|
}
|
|
1153
1332
|
};
|
|
1154
|
-
// Auto-save:
|
|
1155
|
-
//
|
|
1156
|
-
//
|
|
1157
|
-
//
|
|
1158
|
-
|
|
1159
|
-
|
|
1333
|
+
// Auto-save: wijzigingen worden ALTIJD eerst lokaal vastgelegd en pas daarna
|
|
1334
|
+
// naar Floriday gestuurd. Mislukt die push, dan blijft de invoer bewaard en
|
|
1335
|
+
// wordt het artikel gemarkeerd als "nog niet doorgegeven"; bij het opnieuw
|
|
1336
|
+
// openen is de lokale versie dan leidend.
|
|
1337
|
+
const persistAndPush = useCallback(async (snapshot) => {
|
|
1338
|
+
if (!activeId)
|
|
1339
|
+
return false;
|
|
1340
|
+
const pushTargetId = (isEditingPublished && tradeItemId) ? tradeItemId : (draftFloridayId || null);
|
|
1341
|
+
setSyncState("saving");
|
|
1342
|
+
setSyncError(null);
|
|
1343
|
+
// 1. Lokaal opslaan — nooit overslaan.
|
|
1344
|
+
let localId = draftId;
|
|
1345
|
+
try {
|
|
1346
|
+
localId = await upsertLocalTradeItem({
|
|
1347
|
+
id: draftId,
|
|
1348
|
+
connectionId: activeId,
|
|
1349
|
+
data: snapshot,
|
|
1350
|
+
floridayTradeItemId: pushTargetId,
|
|
1351
|
+
sync: { dirty: Boolean(pushTargetId), last_error: null },
|
|
1352
|
+
});
|
|
1353
|
+
if (localId && localId !== draftId)
|
|
1354
|
+
setDraftId(localId);
|
|
1355
|
+
}
|
|
1356
|
+
catch (e) {
|
|
1357
|
+
const msg = e.message || "Onbekende fout";
|
|
1358
|
+
setSyncError(msg);
|
|
1359
|
+
setSyncState("error");
|
|
1360
|
+
toast.error(`Lokaal opslaan mislukt: ${msg}`);
|
|
1361
|
+
return false;
|
|
1362
|
+
}
|
|
1363
|
+
const finishLocalOnly = () => {
|
|
1364
|
+
setSyncState("local");
|
|
1365
|
+
return true;
|
|
1366
|
+
};
|
|
1367
|
+
if (!pushTargetId) {
|
|
1368
|
+
setPendingSync(null);
|
|
1369
|
+
if (localId) {
|
|
1370
|
+
void markLocalSyncStatus(localId, {
|
|
1371
|
+
dirty: false,
|
|
1372
|
+
last_synced_at: null,
|
|
1373
|
+
}).catch(() => { });
|
|
1374
|
+
}
|
|
1375
|
+
return finishLocalOnly();
|
|
1376
|
+
}
|
|
1377
|
+
if (!snapshot.productGroup)
|
|
1378
|
+
return finishLocalOnly();
|
|
1379
|
+
// Geen warehouse gekozen? Alleen lokaal bewaren; zodra de gebruiker een
|
|
1380
|
+
// magazijn kiest gaat de sync verder.
|
|
1381
|
+
const whId = (snapshot.warehouseId ?? "").trim();
|
|
1382
|
+
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(whId);
|
|
1383
|
+
if (!isUuid)
|
|
1384
|
+
return finishLocalOnly();
|
|
1385
|
+
// 2. Doorgeven aan Floriday.
|
|
1386
|
+
try {
|
|
1387
|
+
await saveWarehousePreference(pushTargetId, snapshot.warehouseId);
|
|
1388
|
+
const payload = buildUpdateTradeItemPayload(snapshot);
|
|
1389
|
+
const res = await floriday.updateTradeItem(activeId, pushTargetId, payload);
|
|
1390
|
+
if (!res?.attemptId) {
|
|
1391
|
+
throw new Error("Gateway gaf geen attemptId terug");
|
|
1392
|
+
}
|
|
1393
|
+
try {
|
|
1394
|
+
const cachePayload = {
|
|
1395
|
+
...buildAddTradeItemPayload(snapshot),
|
|
1396
|
+
tradeItemId: pushTargetId,
|
|
1397
|
+
};
|
|
1398
|
+
await supabase
|
|
1399
|
+
.from("floriday_trade_items_cache")
|
|
1400
|
+
.update({
|
|
1401
|
+
data: cachePayload,
|
|
1402
|
+
fetched_at: new Date().toISOString(),
|
|
1403
|
+
})
|
|
1404
|
+
.eq("connection_id", activeId)
|
|
1405
|
+
.eq("floriday_id", pushTargetId);
|
|
1406
|
+
}
|
|
1407
|
+
catch {
|
|
1408
|
+
/* niet kritiek; sync corrigeert */
|
|
1409
|
+
}
|
|
1410
|
+
if (localId) {
|
|
1411
|
+
await markLocalSyncStatus(localId, {
|
|
1412
|
+
dirty: false,
|
|
1413
|
+
last_error: null,
|
|
1414
|
+
last_synced_at: new Date().toISOString(),
|
|
1415
|
+
}).catch(() => { });
|
|
1416
|
+
}
|
|
1417
|
+
setPendingSync(null);
|
|
1418
|
+
setSyncState("saved");
|
|
1419
|
+
return true;
|
|
1420
|
+
}
|
|
1421
|
+
catch (e) {
|
|
1422
|
+
const msg = e.message || "Onbekende fout";
|
|
1423
|
+
if (localId) {
|
|
1424
|
+
void markLocalSyncStatus(localId, { dirty: true, last_error: msg }).catch(() => { });
|
|
1425
|
+
}
|
|
1426
|
+
setPendingSync({ error: msg, at: new Date().toISOString() });
|
|
1427
|
+
setSyncError(msg);
|
|
1428
|
+
setSyncState("error");
|
|
1429
|
+
return false;
|
|
1430
|
+
}
|
|
1431
|
+
}, [
|
|
1432
|
+
activeId,
|
|
1433
|
+
draftId,
|
|
1434
|
+
draftFloridayId,
|
|
1435
|
+
isEditingPublished,
|
|
1436
|
+
tradeItemId,
|
|
1437
|
+
saveWarehousePreference,
|
|
1438
|
+
floriday,
|
|
1439
|
+
]);
|
|
1160
1440
|
useEffect(() => {
|
|
1161
1441
|
if (!activeId)
|
|
1162
1442
|
return;
|
|
@@ -1175,66 +1455,12 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1175
1455
|
}
|
|
1176
1456
|
if (lastSavedRef.current === serialized)
|
|
1177
1457
|
return;
|
|
1178
|
-
const
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
await upsertLocalTradeItem({ id: draftId, connectionId: activeId, data });
|
|
1185
|
-
}
|
|
1186
|
-
if (pushTargetId && !data.productGroup) {
|
|
1187
|
-
setSyncState(draftId ? "local" : "idle");
|
|
1188
|
-
lastSavedRef.current = serialized;
|
|
1189
|
-
return;
|
|
1190
|
-
}
|
|
1191
|
-
// Push naar Floriday als er een gekoppeld/gepubliceerd trade-item is
|
|
1192
|
-
if (pushTargetId) {
|
|
1193
|
-
// Geen warehouse gekozen? Sla stil lokaal op — geen toast, geen rode
|
|
1194
|
-
// pill. Zodra de gebruiker een warehouse kiest gaat de sync verder.
|
|
1195
|
-
const whId = (data.warehouseId ?? "").trim();
|
|
1196
|
-
const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(whId);
|
|
1197
|
-
if (!isUuid) {
|
|
1198
|
-
setSyncState(draftId ? "local" : "idle");
|
|
1199
|
-
lastSavedRef.current = serialized;
|
|
1200
|
-
return;
|
|
1201
|
-
}
|
|
1202
|
-
await saveWarehousePreference(pushTargetId, data.warehouseId);
|
|
1203
|
-
const payload = buildUpdateTradeItemPayload(data);
|
|
1204
|
-
const res = await floriday.updateTradeItem(activeId, pushTargetId, payload);
|
|
1205
|
-
if (!res?.attemptId) {
|
|
1206
|
-
throw new Error("Gateway gaf geen attemptId terug");
|
|
1207
|
-
}
|
|
1208
|
-
try {
|
|
1209
|
-
const cachePayload = {
|
|
1210
|
-
...buildAddTradeItemPayload(data),
|
|
1211
|
-
tradeItemId: pushTargetId,
|
|
1212
|
-
};
|
|
1213
|
-
await supabase
|
|
1214
|
-
.from("floriday_trade_items_cache")
|
|
1215
|
-
.update({
|
|
1216
|
-
data: cachePayload,
|
|
1217
|
-
fetched_at: new Date().toISOString(),
|
|
1218
|
-
})
|
|
1219
|
-
.eq("connection_id", activeId)
|
|
1220
|
-
.eq("floriday_id", pushTargetId);
|
|
1221
|
-
}
|
|
1222
|
-
catch {
|
|
1223
|
-
/* niet kritiek; sync corrigeert */
|
|
1224
|
-
}
|
|
1225
|
-
setSyncState("saved");
|
|
1226
|
-
}
|
|
1227
|
-
else {
|
|
1228
|
-
setSyncState("local");
|
|
1229
|
-
}
|
|
1230
|
-
lastSavedRef.current = serialized;
|
|
1231
|
-
}
|
|
1232
|
-
catch (e) {
|
|
1233
|
-
const msg = e.message || "Onbekende fout";
|
|
1234
|
-
setSyncError(msg);
|
|
1235
|
-
setSyncState("error");
|
|
1236
|
-
toast.error(`Sync naar Floriday mislukt: ${msg}`);
|
|
1237
|
-
}
|
|
1458
|
+
const snapshot = data;
|
|
1459
|
+
const t = window.setTimeout(() => {
|
|
1460
|
+
// De lokale opslag slaagt (bijna) altijd; de baseline mag dus mee, ook als
|
|
1461
|
+
// de Floriday-push faalt — de invoer staat dan veilig in de eigen database.
|
|
1462
|
+
lastSavedRef.current = serialized;
|
|
1463
|
+
void persistAndPush(snapshot);
|
|
1238
1464
|
}, 800);
|
|
1239
1465
|
return () => window.clearTimeout(t);
|
|
1240
1466
|
}, [
|
|
@@ -1246,9 +1472,22 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1246
1472
|
publishedLoading,
|
|
1247
1473
|
productLoading,
|
|
1248
1474
|
isEditingPublished,
|
|
1249
|
-
saveWarehousePreference,
|
|
1250
1475
|
tradeItemId,
|
|
1476
|
+
persistAndPush,
|
|
1251
1477
|
]);
|
|
1478
|
+
const retrySync = useCallback(async () => {
|
|
1479
|
+
setRetryingSync(true);
|
|
1480
|
+
try {
|
|
1481
|
+
const ok = await persistAndPush(data);
|
|
1482
|
+
if (ok)
|
|
1483
|
+
toast.success("Wijzigingen doorgegeven aan Floriday");
|
|
1484
|
+
else
|
|
1485
|
+
toast.error("Doorgeven aan Floriday lukt nog niet; je gegevens blijven bewaard");
|
|
1486
|
+
}
|
|
1487
|
+
finally {
|
|
1488
|
+
setRetryingSync(false);
|
|
1489
|
+
}
|
|
1490
|
+
}, [persistAndPush, data]);
|
|
1252
1491
|
if (connLoading || draftLoading || publishedLoading || productLoading) {
|
|
1253
1492
|
return (_jsx("main", { className: "p-6", children: _jsxs(Card, { className: "p-6 flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), publishedLoading
|
|
1254
1493
|
? "Artikel laden..."
|
|
@@ -1265,7 +1504,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1265
1504
|
setDeleteOpen(o);
|
|
1266
1505
|
if (!o)
|
|
1267
1506
|
setDeleteAck(false);
|
|
1268
|
-
}, 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, offerForSale: data.offerForSale })] }), data.name && data.vbnName && data.name !== data.vbnName && (_jsx("div", { className: "text-sm text-muted-foreground", children: data.vbnName }))] })), isEditing && (_jsx(Card, { className: "p-4 bg-muted/30 flex items-center gap-3 text-sm", children: isEditingPublished ? (_jsxs(_Fragment, { children: [_jsx("img", { src: floridayLogo, alt: "Floriday", className: "h-6 w-6 shrink-0" }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Gepubliceerd artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Wijzigingen worden direct naar Floriday verstuurd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: submit, disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Wijzigingen opslaan"] })] })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "font-medium", children: "Lokaal artikel bewerken" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Niet gepubliceerd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: submit, disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), publishToFloriday ? "Publiceren naar Floriday" : "Wijzigingen opslaan"] })] })) })), isEditing && (_jsxs(_Fragment, { children: [_jsx(ArtikelCompositionCard, { productId: linkedProductId, floridayId: tradeItemId ?? draftFloridayId ?? null }), _jsx(FloridayInfoCard, { floridayTradeItemId: tradeItemId ?? draftFloridayId ?? null, local: {
|
|
1507
|
+
}, 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, 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(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" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Wijzigingen worden direct naar Floriday verstuurd. Je kunt vrij tussen alle secties navigeren." })] }), _jsxs(Button, { onClick: () => 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(ArtikelCompositionCard, { productId: linkedProductId, floridayId: tradeItemId ?? draftFloridayId ?? null }), _jsx(FloridayInfoCard, { floridayTradeItemId: tradeItemId ?? draftFloridayId ?? null, local: {
|
|
1269
1508
|
name: data.name || data.vbnName,
|
|
1270
1509
|
articleNumber: data.articleCode,
|
|
1271
1510
|
gtin: data.gtin,
|
|
@@ -1293,7 +1532,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1293
1532
|
? "border-destructive bg-destructive text-destructive-foreground"
|
|
1294
1533
|
: "border-muted-foreground/30 text-muted-foreground"}`, children: status === "valid" ? (_jsx(Check, { className: "h-4 w-4" })) : status === "invalid" ? ("!") : (i + 1) }), _jsx("span", { className: `min-w-0 truncate font-medium ${status === "untouched" ? "text-muted-foreground" : ""}`, children: s.label }), !isOpen && summary && (_jsx("span", { className: `max-w-xs truncate text-xs ${status === "invalid" ? "text-destructive" : "text-muted-foreground"}`, children: status === "invalid" ? `Onvolledig — ${summary}` : summary }))] }), isOpen && (_jsxs(CardContent, { className: "space-y-4 pt-0", children: [_jsx("div", { "data-wizard-form-content": true, className: "w-full min-w-0", children: _jsx(StepBody, { stepKey: s.key, data: data, update: update, vbnProducts: vbnProducts, featureTypes: featureTypes, packagingTypes: packagingTypes, loadCarriers: loadCarriers, floridayCustomers: floridayCustomers, schemaQuery: schemaQuery, regulatoryQuery: regulatoryQuery, spec: spec, collectionsList: collectionsList, setCollectionsList: setCollectionsList, activeId: activeId, acceptedVbnCodes: acceptedVbnCodes, priceGroups: priceGroups, parentProductId: linkedProductId ?? draftId ?? null, parentFloridayId: draftFloridayId ?? tradeItemId ?? null, articleCodeLocked: Boolean(draftFloridayId || tradeItemId) }) }), isLastStep && !isEditingPublished && (_jsxs("div", { className: "rounded-lg border bg-muted/30 p-4 flex items-start gap-3", children: [_jsx("img", { src: floridayLogo, alt: "Floriday", className: "h-7 w-7 shrink-0 mt-0.5" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx(Label, { htmlFor: "publish-to-floriday", className: "font-medium cursor-pointer", children: "Publiceren naar Floriday" }), _jsx("p", { className: "text-xs text-muted-foreground mt-1", children: publishToFloriday
|
|
1295
1534
|
? "Het artikel wordt aangemaakt in Floriday en is binnen enkele minuten zichtbaar voor klanten."
|
|
1296
|
-
: "Het artikel wordt alleen lokaal opgeslagen. Je kunt het later vanuit de voorraadlijst alsnog publiceren naar Floriday." })] }), _jsx(Switch, { id: "publish-to-floriday", checked: publishToFloriday, onCheckedChange: setPublishToFloriday })] })), (isLastStep || isEditing) && invalidStepCount > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive flex items-center justify-between gap-3", children: [_jsxs("span", { children: ["Nog ", invalidStepCount, " stap", invalidStepCount === 1 ? "" : "pen", " onvolledig"] }), firstInvalidIndex >= 0 && (_jsxs("button", { type: "button", onClick: () => setStep(firstInvalidIndex), className: "underline font-medium", children: ["Ga naar ", steps[firstInvalidIndex].label] }))] })), _jsx("div", { className: "flex justify-end pt-2", children: isLastStep || isEditing ? (_jsxs(Button, { onClick: submit, disabled: submitting || invalidStepCount > 0, children: [submitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), isEditingPublished
|
|
1535
|
+
: "Het artikel wordt alleen lokaal opgeslagen. Je kunt het later vanuit de voorraadlijst alsnog publiceren naar Floriday." })] }), _jsx(Switch, { id: "publish-to-floriday", checked: publishToFloriday, onCheckedChange: setPublishToFloriday })] })), (isLastStep || isEditing) && (_jsx(PublishReadinessCard, { report: readiness, loading: connLoading || connectionReadiness.isLoading || schemaQuery.isLoading, onGoToStep: goToStepKey })), publishJob && (_jsx(PublishProgressCard, { job: publishJob, steps: publishSteps, retrying: submitting, onRetry: () => void submit({ resumeJobId: publishJob.id }), onFixStep: (key) => goToStepKey(key === "trade-item" ? "eigenschappen" : "aanbod") })), (isLastStep || isEditing) && invalidStepCount > 0 && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/5 px-3 py-2 text-xs text-destructive flex items-center justify-between gap-3", children: [_jsxs("span", { children: ["Nog ", invalidStepCount, " stap", invalidStepCount === 1 ? "" : "pen", " onvolledig"] }), firstInvalidIndex >= 0 && (_jsxs("button", { type: "button", onClick: () => setStep(firstInvalidIndex), className: "underline font-medium", children: ["Ga naar ", steps[firstInvalidIndex].label] }))] })), _jsx("div", { className: "flex justify-end pt-2", children: isLastStep || isEditing ? (_jsxs(Button, { onClick: () => 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" })), isEditingPublished
|
|
1297
1536
|
? "Wijzigingen opslaan"
|
|
1298
1537
|
: publishToFloriday
|
|
1299
1538
|
? isEditingDraft
|