@flowselections/floriday-voorraad 1.0.27 → 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 +576 -324
- 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 +5 -4
- 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/ProductCustomFieldsCard.d.ts +5 -0
- package/dist-lib/components/voorraad/ProductCustomFieldsCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ProductCustomFieldsCard.js +65 -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/StockBulkButtons.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockBulkButtons.js +65 -2
- 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/ValidationOverviewTable.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ValidationOverviewTable.js +8 -2
- package/dist-lib/components/voorraad/VoorraadDashboard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadDashboard.js +4 -1
- 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 +10 -2
- package/dist-lib/components/voorraad/VoorraadVeldenCard.d.ts +2 -0
- package/dist-lib/components/voorraad/VoorraadVeldenCard.d.ts.map +1 -0
- package/dist-lib/components/voorraad/VoorraadVeldenCard.js +97 -0
- 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 +17 -7
- package/dist-lib/hooks/useLocalTradeItems.d.ts.map +1 -1
- package/dist-lib/hooks/useLocalTradeItems.js +139 -37
- 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/hooks/useVoorraadCustomFields.d.ts +9 -0
- package/dist-lib/hooks/useVoorraadCustomFields.d.ts.map +1 -0
- package/dist-lib/hooks/useVoorraadCustomFields.js +26 -0
- package/dist-lib/index.d.ts +2 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +9 -2
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +8467 -4487
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +8467 -4487
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +8467 -4487
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +8714 -4631
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +49 -0
- package/dist-lib/lib/floricode-required-features.functions.d.ts +8467 -4487
- 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 +8467 -4487
- 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 +77902 -40145
- 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 +23115 -15155
- package/dist-lib/lib/floriday-vbn-required.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-warehouse.functions.d.ts +31362 -19422
- 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 +23191 -15231
- 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/dist-lib/lib/voorraad-custom-fields.d.ts +22 -0
- package/dist-lib/lib/voorraad-custom-fields.d.ts.map +1 -0
- package/dist-lib/lib/voorraad-custom-fields.js +71 -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";
|
|
@@ -78,6 +85,11 @@ function warehouseSubLabel(raw) {
|
|
|
78
85
|
const city = addr.city ?? "";
|
|
79
86
|
return [line, city].filter(Boolean).join(", ") || undefined;
|
|
80
87
|
}
|
|
88
|
+
function asPlainRecord(value) {
|
|
89
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
90
|
+
? value
|
|
91
|
+
: {};
|
|
92
|
+
}
|
|
81
93
|
async function lookupProductGroupForVbn(vbnCode) {
|
|
82
94
|
const code = String(vbnCode ?? "").trim();
|
|
83
95
|
if (!code)
|
|
@@ -90,14 +102,23 @@ async function lookupProductGroupForVbn(vbnCode) {
|
|
|
90
102
|
.maybeSingle();
|
|
91
103
|
return data ? groupOfRecord(data) : null;
|
|
92
104
|
}
|
|
93
|
-
export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId, } = {}) {
|
|
105
|
+
export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId, resumeJobId, } = {}) {
|
|
94
106
|
const navigate = useNavigate();
|
|
95
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
|
+
});
|
|
96
115
|
const { customers: floridayCustomers } = useFlorydayCustomers(activeId);
|
|
97
116
|
const { priceGroups } = useFloridayPriceGroups(activeId);
|
|
98
117
|
const [step, setStep] = useState(0);
|
|
99
118
|
const [data, setData] = useState(empty);
|
|
100
119
|
const [submitting, setSubmitting] = useState(false);
|
|
120
|
+
const [publishJobId, setPublishJobId] = useState(resumeJobId ?? null);
|
|
121
|
+
const { job: publishJob, steps: publishSteps } = usePublishJob(publishJobId);
|
|
101
122
|
const [publishToFloriday, setPublishToFloriday] = useState(true);
|
|
102
123
|
const [draftId, setDraftId] = useState(initialDraftId);
|
|
103
124
|
const [draftFloridayId, setDraftFloridayId] = useState(null);
|
|
@@ -106,6 +127,9 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
106
127
|
const [productLoading, setProductLoading] = useState(Boolean(productId));
|
|
107
128
|
const [syncState, setSyncState] = useState("idle");
|
|
108
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);
|
|
109
133
|
const [productMeta, setProductMeta] = useState({ floridayActive: false, floridayTradeItemId: null, physicalStock: 0 });
|
|
110
134
|
const [deleteAck, setDeleteAck] = useState(false);
|
|
111
135
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
@@ -257,7 +281,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
257
281
|
try {
|
|
258
282
|
const { data: product, error } = await supabase
|
|
259
283
|
.from("products")
|
|
260
|
-
.select("id,product,barcode,article_number,quantity,
|
|
284
|
+
.select("id,product,barcode,article_number,quantity,image_url,plants_per_crate,crates_per_layer,layers_per_cart,trade_item_key,floriday_trade_item_id,custom_fields")
|
|
261
285
|
.eq("id", productId)
|
|
262
286
|
.maybeSingle();
|
|
263
287
|
if (cancelled)
|
|
@@ -274,9 +298,15 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
274
298
|
const ppc = Number(product.plants_per_crate) || 0;
|
|
275
299
|
const cpl = Number(product.crates_per_layer) || 0;
|
|
276
300
|
const lpc = Number(product.layers_per_cart) || 0;
|
|
277
|
-
const
|
|
278
|
-
const
|
|
279
|
-
const
|
|
301
|
+
const customFields = asPlainRecord(product.custom_fields);
|
|
302
|
+
const payload = asPlainRecord(customFields.floriday_payload);
|
|
303
|
+
const productTradeItemKey = String(product.trade_item_key ?? "");
|
|
304
|
+
const fid = ((product.floriday_trade_item_id ??
|
|
305
|
+
customFields.floriday_id ??
|
|
306
|
+
(productTradeItemKey && !productTradeItemKey.startsWith("product:")
|
|
307
|
+
? productTradeItemKey
|
|
308
|
+
: null)) || null);
|
|
309
|
+
const fActive = Boolean(fid);
|
|
280
310
|
// Floriday-cache + warehouse-voorkeur parallel ophalen wanneer we een
|
|
281
311
|
// gekoppeld trade-item hebben. Missing cache blokkeert de wizard niet;
|
|
282
312
|
// dan valt hij terug op wat er lokaal is opgeslagen.
|
|
@@ -313,6 +343,20 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
313
343
|
console.warn("Floriday-cache laden mislukt", e);
|
|
314
344
|
}
|
|
315
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
|
+
}
|
|
316
360
|
const cachePackings = cacheData?.packingConfigurations ?? [];
|
|
317
361
|
const payloadHasPacking = Array.isArray(payload.packingConfigurations) && payload.packingConfigurations.length > 0;
|
|
318
362
|
const seededPacking = cachePackings.length > 0
|
|
@@ -402,17 +446,19 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
402
446
|
: validGtin
|
|
403
447
|
? barcode
|
|
404
448
|
: (payload.gtin ?? ""),
|
|
405
|
-
salePricePerPiece: Number(
|
|
449
|
+
salePricePerPiece: Number(customFields.sale_price ?? customFields.salePrice ?? payload.salePricePerPiece) || 0,
|
|
406
450
|
// Voorkeur: werkelijke voorraad uit trade_item_stock.
|
|
407
451
|
// Fallback: opgeslagen payload-waarde. `products.quantity` wordt
|
|
408
452
|
// bewust NIET meer gelezen — die staat structureel op 0.
|
|
409
453
|
saleStockPieces: physical > 0
|
|
410
454
|
? physical
|
|
411
455
|
: Number(payload.saleStockPieces ?? 0) || 0,
|
|
412
|
-
vatRate:
|
|
413
|
-
? Number(
|
|
456
|
+
vatRate: customFields.vat_rate !== null && customFields.vat_rate !== undefined
|
|
457
|
+
? Number(customFields.vat_rate)
|
|
414
458
|
: (payload.vatRate ?? null),
|
|
415
|
-
vatCode:
|
|
459
|
+
vatCode: (customFields.vat_code ?? customFields.vatCode) ??
|
|
460
|
+
payload.vatCode ??
|
|
461
|
+
null,
|
|
416
462
|
warehouseId: warehousePref ||
|
|
417
463
|
cacheWarehouseId ||
|
|
418
464
|
payloadWarehouseId ||
|
|
@@ -473,14 +519,45 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
473
519
|
toast.error("Artikel niet gevonden in de cache");
|
|
474
520
|
return;
|
|
475
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
|
+
}
|
|
476
550
|
const wizardData = floridayCacheToWizardData(row.data);
|
|
477
|
-
const
|
|
478
|
-
|
|
551
|
+
const baseData = localSync?.dirty && localPayload
|
|
552
|
+
? { ...wizardData, ...localPayload }
|
|
553
|
+
: wizardData;
|
|
554
|
+
const productGroup = baseData.productGroup ??
|
|
555
|
+
(await lookupProductGroupForVbn(baseData.vbnCode));
|
|
479
556
|
const prefWarehouseId = pref?.warehouse_id ?? "";
|
|
480
|
-
const cachedWarehouseId =
|
|
557
|
+
const cachedWarehouseId = baseData.warehouseId ?? "";
|
|
481
558
|
const preferredWarehouseFallback = prefWarehouseId || cachedWarehouseId ? null : await resolvePreferredWarehouseId();
|
|
482
559
|
setData({
|
|
483
|
-
...
|
|
560
|
+
...baseData,
|
|
484
561
|
productGroup,
|
|
485
562
|
warehouseId: prefWarehouseId || cachedWarehouseId || preferredWarehouseFallback || "",
|
|
486
563
|
});
|
|
@@ -795,20 +872,94 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
795
872
|
}, 50);
|
|
796
873
|
return () => window.clearTimeout(id);
|
|
797
874
|
}, [step]);
|
|
798
|
-
|
|
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) => {
|
|
799
950
|
if (!activeId) {
|
|
800
|
-
toast.error("Geen actieve
|
|
951
|
+
toast.error("Geen actieve verbinding met de handelsomgeving", {
|
|
952
|
+
description: "Stel de verbinding in bij Instellingen.",
|
|
953
|
+
});
|
|
801
954
|
return;
|
|
802
955
|
}
|
|
803
|
-
// Publish-gate: blokkeer zolang het trade-item schema niet succesvol
|
|
804
|
-
// geladen is — anders kunnen verplichte karakteristieken ontbreken.
|
|
805
|
-
const wantsPublish = (isEditingPublished && Boolean(tradeItemId)) ||
|
|
806
|
-
(!isEditingPublished && publishToFloriday);
|
|
807
956
|
if (wantsPublish && !schemaQuery.isSuccess) {
|
|
808
|
-
toast.error("
|
|
957
|
+
toast.error("De productregels worden nog opgehaald", {
|
|
958
|
+
description: "Wacht heel even en probeer het opnieuw.",
|
|
959
|
+
});
|
|
809
960
|
return;
|
|
810
961
|
}
|
|
811
|
-
//
|
|
962
|
+
// Strikte controle van alle stappen voor verzending.
|
|
812
963
|
for (const s of steps) {
|
|
813
964
|
const v = validateStepLocal(s.key, data);
|
|
814
965
|
if (!v.ok) {
|
|
@@ -819,68 +970,226 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
819
970
|
return;
|
|
820
971
|
}
|
|
821
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
|
+
}
|
|
822
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;
|
|
823
1071
|
try {
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
try {
|
|
835
|
-
const cachePayload = {
|
|
836
|
-
...buildAddTradeItemPayload(data),
|
|
837
|
-
tradeItemId,
|
|
838
|
-
};
|
|
839
|
-
await supabase
|
|
840
|
-
.from("floriday_trade_items_cache")
|
|
841
|
-
.update({
|
|
842
|
-
data: cachePayload,
|
|
843
|
-
fetched_at: new Date().toISOString(),
|
|
844
|
-
})
|
|
845
|
-
.eq("connection_id", activeId)
|
|
846
|
-
.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 };
|
|
847
1082
|
}
|
|
848
|
-
|
|
849
|
-
|
|
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");
|
|
850
1087
|
}
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
if (
|
|
1088
|
+
const createdId = res.tradeItemId ?? payload.tradeItemId;
|
|
1089
|
+
await saveWarehousePreference(createdId, data.warehouseId);
|
|
1090
|
+
if (draftId) {
|
|
854
1091
|
try {
|
|
855
|
-
|
|
856
|
-
data.packingConfigurations[0];
|
|
857
|
-
if (primary?.id) {
|
|
858
|
-
await upsertSupplyLineForTradeItem({
|
|
859
|
-
connectionId: activeId,
|
|
860
|
-
tradeItemId,
|
|
861
|
-
pricePerPiece: data.salePricePerPiece,
|
|
862
|
-
packingConfigurationId: primary.id,
|
|
863
|
-
availableQuantity: data.saleStockPieces,
|
|
864
|
-
customerOrganizationIds: data.customerOrganizationIds,
|
|
865
|
-
periodStart: data.offerStartDate
|
|
866
|
-
? new Date(`${data.offerStartDate}T00:00:00Z`).toISOString()
|
|
867
|
-
: undefined,
|
|
868
|
-
});
|
|
869
|
-
toast.success("Prijs bijgewerkt in Floriday-catalogus");
|
|
870
|
-
}
|
|
1092
|
+
await deleteLocalTradeItem(draftId);
|
|
871
1093
|
}
|
|
872
|
-
catch
|
|
873
|
-
|
|
1094
|
+
catch {
|
|
1095
|
+
/* niet kritiek */
|
|
874
1096
|
}
|
|
875
1097
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
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 () => {
|
|
881
1190
|
const priceSync = await syncTradeItemPrices({
|
|
882
|
-
tradeItemId,
|
|
883
|
-
tradeItemKey:
|
|
1191
|
+
tradeItemId: resolvedTradeItemId,
|
|
1192
|
+
tradeItemKey: resolvedTradeItemId,
|
|
884
1193
|
availableQuantity: data.saleStockPieces,
|
|
885
1194
|
currency: "EUR",
|
|
886
1195
|
periodStart: data.offerStartDate
|
|
@@ -893,199 +1202,82 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
893
1202
|
});
|
|
894
1203
|
const failed = priceSync.groupResults.filter((r) => !r.ok);
|
|
895
1204
|
if (failed.length) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
toast.success(`${priceSync.groupResults.length} prijsgroep-prijs(zen) bijgewerkt`);
|
|
1205
|
+
throw new Error(`Deze prijsafspraken zijn niet doorgekomen: ${failed
|
|
1206
|
+
.map((r) => r.name)
|
|
1207
|
+
.join(", ")}`);
|
|
900
1208
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
toast.error(`Prijzen synchroniseren mislukt: ${e.message}`);
|
|
904
|
-
}
|
|
1209
|
+
return { groups: priceSync.groupResults.length };
|
|
1210
|
+
}, stepOpts);
|
|
905
1211
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
const
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
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");
|
|
918
1223
|
}
|
|
919
|
-
|
|
920
|
-
// Niet kritiek; rij verdwijnt na volgende reload
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
// ---- Direct-sale flow -----------------------------------------
|
|
924
|
-
// Alleen doorzetten als de gebruiker de "Aanbieden"-stap geactiveerd
|
|
925
|
-
// heeft. Falen mag de TradeItem-succes NIET terugdraaien — daarom
|
|
926
|
-
// in een aparte try/catch en apart toast bericht.
|
|
927
|
-
if (data.offerForSale) {
|
|
928
|
-
const tradeItemId = createdTradeItemId;
|
|
929
|
-
// 1) Seed lokale voorraad (physical_stock) — de continuous-stock
|
|
930
|
-
// trigger pusht dit naar Floriday zodra de trade item in de
|
|
931
|
-
// cache verschijnt.
|
|
932
|
-
// GUARD: nooit een BESTAANDE voorraad-rij overschrijven. Als
|
|
933
|
-
// er al een rij is (bv. bij herpubliceren of bij een lokaal
|
|
934
|
-
// product met historische voorraad), laten we die met rust
|
|
935
|
-
// om ongewild op 0 zetten te voorkomen. Voorraad wordt
|
|
936
|
-
// beheerd via de Voorraadkaart, niet via de wizard.
|
|
937
|
-
try {
|
|
938
|
-
const key = stockKeyFor({ tradeItemId });
|
|
939
|
-
if (key && data.saleStockPieces > 0) {
|
|
940
|
-
const { data: existing } = await supabase
|
|
941
|
-
.from("trade_item_stock")
|
|
942
|
-
.select("trade_item_key")
|
|
943
|
-
.eq("connection_id", activeId)
|
|
944
|
-
.eq("trade_item_key", key)
|
|
945
|
-
.maybeSingle();
|
|
946
|
-
if (!existing) {
|
|
947
|
-
const userRes = await supabase.auth.getUser();
|
|
948
|
-
const userId = userRes.data.user?.id;
|
|
949
|
-
if (userId) {
|
|
950
|
-
await supabase
|
|
951
|
-
.from("trade_item_stock")
|
|
952
|
-
.insert({
|
|
953
|
-
user_id: userId,
|
|
954
|
-
connection_id: activeId,
|
|
955
|
-
trade_item_key: key,
|
|
956
|
-
physical_stock: data.saleStockPieces,
|
|
957
|
-
minimum_stock: 0,
|
|
958
|
-
reserved_stock: 0,
|
|
959
|
-
incoming_stock: 0,
|
|
960
|
-
});
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
catch (e) {
|
|
966
|
-
toast.error(`Voorraad opslaan mislukt: ${e.message}`);
|
|
967
|
-
}
|
|
968
|
-
// 2) CustomerOffer publiceren (POST /customer-offers)
|
|
969
|
-
try {
|
|
970
|
-
const packingBase = buildPrimaryPackingBase(data);
|
|
971
|
-
await publishCustomerOfferFn({
|
|
972
|
-
data: {
|
|
973
|
-
customerOfferId: crypto.randomUUID(),
|
|
974
|
-
tradeItemId,
|
|
975
|
-
title: data.name || data.vbnName || "Aanbod",
|
|
976
|
-
description: "",
|
|
977
|
-
pricePerPiece: data.salePricePerPiece,
|
|
978
|
-
currency: "EUR",
|
|
979
|
-
despatchWarehouseId: data.warehouseId,
|
|
980
|
-
numberOfPieces: data.saleStockPieces,
|
|
981
|
-
allowedCustomerOrganizationIds: data.customerOrganizationIds,
|
|
982
|
-
packingConfiguration: packingBase,
|
|
983
|
-
orderStart: new Date(`${data.offerStartDate}T00:00:00Z`).toISOString(),
|
|
984
|
-
},
|
|
985
|
-
});
|
|
986
|
-
toast.success("Artikel aangemaakt en aangeboden in Floriday");
|
|
987
|
-
}
|
|
988
|
-
catch (e) {
|
|
989
|
-
toast.error(`Aanbod publiceren mislukt: ${e.message}`);
|
|
990
|
-
}
|
|
991
|
-
// 3) Supply-line pushen naar de Authenticatie-module gateway
|
|
992
|
-
// zodat het product met prijs online komt in de Floriday-catalogus.
|
|
993
|
-
// Idempotent op supplyLineId — herprijzen hergebruikt dezelfde regel.
|
|
1224
|
+
// Lokale kopie pas bijwerken nadat de handelsomgeving bevestigd heeft.
|
|
994
1225
|
try {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
availableQuantity: data.saleStockPieces,
|
|
1004
|
-
customerOrganizationIds: data.customerOrganizationIds,
|
|
1005
|
-
periodStart: data.offerStartDate
|
|
1006
|
-
? new Date(`${data.offerStartDate}T00:00:00Z`).toISOString()
|
|
1007
|
-
: undefined,
|
|
1008
|
-
});
|
|
1009
|
-
if (sl.supplyLineId) {
|
|
1010
|
-
toast.success("Prijs online gezet in Floriday-catalogus");
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
catch (e) {
|
|
1015
|
-
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);
|
|
1016
1234
|
}
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
const primary = data.packingConfigurations.find((p) => p.isPrimary) ??
|
|
1020
|
-
data.packingConfigurations[0];
|
|
1021
|
-
const priceSync = await syncTradeItemPrices({
|
|
1022
|
-
tradeItemId,
|
|
1023
|
-
tradeItemKey: tradeItemId,
|
|
1024
|
-
availableQuantity: data.saleStockPieces,
|
|
1025
|
-
currency: "EUR",
|
|
1026
|
-
periodStart: data.offerStartDate
|
|
1027
|
-
? new Date(`${data.offerStartDate}T00:00:00Z`).toISOString()
|
|
1028
|
-
: undefined,
|
|
1029
|
-
basePricePerPiece: data.salePricePerPiece,
|
|
1030
|
-
pricesByGroupId: data.priceGroupPrices ?? {},
|
|
1031
|
-
priceGroups,
|
|
1032
|
-
packingConfigurationId: primary?.id,
|
|
1033
|
-
});
|
|
1034
|
-
const failed = priceSync.groupResults.filter((r) => !r.ok);
|
|
1035
|
-
if (failed.length) {
|
|
1036
|
-
toast.error(`Prijsgroep(en) niet gepusht: ${failed.map((r) => r.name).join(", ")}`);
|
|
1037
|
-
}
|
|
1235
|
+
catch {
|
|
1236
|
+
/* wordt automatisch bijgewerkt bij de volgende synchronisatie */
|
|
1038
1237
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
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");
|
|
1041
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
|
+
});
|
|
1042
1249
|
}
|
|
1043
|
-
else {
|
|
1044
|
-
toast.success("Artikel aangemaakt in Floriday");
|
|
1045
|
-
}
|
|
1046
|
-
}
|
|
1047
|
-
else {
|
|
1048
|
-
const newId = await upsertLocalTradeItem({
|
|
1049
|
-
id: draftId,
|
|
1050
|
-
connectionId: activeId,
|
|
1051
|
-
data,
|
|
1052
|
-
});
|
|
1053
|
-
setDraftId(newId);
|
|
1054
|
-
toast.success(draftId
|
|
1055
|
-
? "Wijzigingen opgeslagen (nog niet gepubliceerd)"
|
|
1056
|
-
: "Artikel opgeslagen (nog niet gepubliceerd)");
|
|
1057
1250
|
}
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
if (data.vbnCode) {
|
|
1069
|
-
try {
|
|
1070
|
-
const res = await saveFloridayVbnRequired({
|
|
1071
|
-
data: { vbnCode: data.vbnCode, codes: missingCodes },
|
|
1072
|
-
});
|
|
1073
|
-
queryClient.setQueryData(["floriday-vbn-required", data.vbnCode], { vbnCode: res.vbnCode, requiredCodes: res.requiredCodes });
|
|
1074
|
-
}
|
|
1075
|
-
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(() => { });
|
|
1076
1261
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
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" });
|
|
1081
1265
|
}
|
|
1082
|
-
else if (
|
|
1083
|
-
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
|
+
});
|
|
1084
1270
|
}
|
|
1085
1271
|
else {
|
|
1086
|
-
toast.error(
|
|
1272
|
+
toast.error("Publiceren is niet gelukt");
|
|
1087
1273
|
}
|
|
1088
1274
|
}
|
|
1275
|
+
catch (e) {
|
|
1276
|
+
const friendly = translatePublishError(e, { characteristicLabels });
|
|
1277
|
+
await handleStepFailure(friendly);
|
|
1278
|
+
if (jobId)
|
|
1279
|
+
await finishPublishJob(jobId);
|
|
1280
|
+
}
|
|
1089
1281
|
finally {
|
|
1090
1282
|
setSubmitting(false);
|
|
1091
1283
|
}
|
|
@@ -1138,12 +1330,113 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1138
1330
|
setSubmitting(false);
|
|
1139
1331
|
}
|
|
1140
1332
|
};
|
|
1141
|
-
// Auto-save:
|
|
1142
|
-
//
|
|
1143
|
-
//
|
|
1144
|
-
//
|
|
1145
|
-
|
|
1146
|
-
|
|
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
|
+
]);
|
|
1147
1440
|
useEffect(() => {
|
|
1148
1441
|
if (!activeId)
|
|
1149
1442
|
return;
|
|
@@ -1162,66 +1455,12 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1162
1455
|
}
|
|
1163
1456
|
if (lastSavedRef.current === serialized)
|
|
1164
1457
|
return;
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
await upsertLocalTradeItem({ id: draftId, connectionId: activeId, data });
|
|
1172
|
-
}
|
|
1173
|
-
if (pushTargetId && !data.productGroup) {
|
|
1174
|
-
setSyncState(draftId ? "local" : "idle");
|
|
1175
|
-
lastSavedRef.current = serialized;
|
|
1176
|
-
return;
|
|
1177
|
-
}
|
|
1178
|
-
// Push naar Floriday als er een gekoppeld/gepubliceerd trade-item is
|
|
1179
|
-
if (pushTargetId) {
|
|
1180
|
-
// Geen warehouse gekozen? Sla stil lokaal op — geen toast, geen rode
|
|
1181
|
-
// pill. Zodra de gebruiker een warehouse kiest gaat de sync verder.
|
|
1182
|
-
const whId = (data.warehouseId ?? "").trim();
|
|
1183
|
-
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);
|
|
1184
|
-
if (!isUuid) {
|
|
1185
|
-
setSyncState(draftId ? "local" : "idle");
|
|
1186
|
-
lastSavedRef.current = serialized;
|
|
1187
|
-
return;
|
|
1188
|
-
}
|
|
1189
|
-
await saveWarehousePreference(pushTargetId, data.warehouseId);
|
|
1190
|
-
const payload = buildUpdateTradeItemPayload(data);
|
|
1191
|
-
const res = await floriday.updateTradeItem(activeId, pushTargetId, payload);
|
|
1192
|
-
if (!res?.attemptId) {
|
|
1193
|
-
throw new Error("Gateway gaf geen attemptId terug");
|
|
1194
|
-
}
|
|
1195
|
-
try {
|
|
1196
|
-
const cachePayload = {
|
|
1197
|
-
...buildAddTradeItemPayload(data),
|
|
1198
|
-
tradeItemId: pushTargetId,
|
|
1199
|
-
};
|
|
1200
|
-
await supabase
|
|
1201
|
-
.from("floriday_trade_items_cache")
|
|
1202
|
-
.update({
|
|
1203
|
-
data: cachePayload,
|
|
1204
|
-
fetched_at: new Date().toISOString(),
|
|
1205
|
-
})
|
|
1206
|
-
.eq("connection_id", activeId)
|
|
1207
|
-
.eq("floriday_id", pushTargetId);
|
|
1208
|
-
}
|
|
1209
|
-
catch {
|
|
1210
|
-
/* niet kritiek; sync corrigeert */
|
|
1211
|
-
}
|
|
1212
|
-
setSyncState("saved");
|
|
1213
|
-
}
|
|
1214
|
-
else {
|
|
1215
|
-
setSyncState("local");
|
|
1216
|
-
}
|
|
1217
|
-
lastSavedRef.current = serialized;
|
|
1218
|
-
}
|
|
1219
|
-
catch (e) {
|
|
1220
|
-
const msg = e.message || "Onbekende fout";
|
|
1221
|
-
setSyncError(msg);
|
|
1222
|
-
setSyncState("error");
|
|
1223
|
-
toast.error(`Sync naar Floriday mislukt: ${msg}`);
|
|
1224
|
-
}
|
|
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);
|
|
1225
1464
|
}, 800);
|
|
1226
1465
|
return () => window.clearTimeout(t);
|
|
1227
1466
|
}, [
|
|
@@ -1233,9 +1472,22 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1233
1472
|
publishedLoading,
|
|
1234
1473
|
productLoading,
|
|
1235
1474
|
isEditingPublished,
|
|
1236
|
-
saveWarehousePreference,
|
|
1237
1475
|
tradeItemId,
|
|
1476
|
+
persistAndPush,
|
|
1238
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]);
|
|
1239
1491
|
if (connLoading || draftLoading || publishedLoading || productLoading) {
|
|
1240
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
|
|
1241
1493
|
? "Artikel laden..."
|
|
@@ -1252,7 +1504,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1252
1504
|
setDeleteOpen(o);
|
|
1253
1505
|
if (!o)
|
|
1254
1506
|
setDeleteAck(false);
|
|
1255
|
-
}, 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: {
|
|
1256
1508
|
name: data.name || data.vbnName,
|
|
1257
1509
|
articleNumber: data.articleCode,
|
|
1258
1510
|
gtin: data.gtin,
|
|
@@ -1280,7 +1532,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1280
1532
|
? "border-destructive bg-destructive text-destructive-foreground"
|
|
1281
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
|
|
1282
1534
|
? "Het artikel wordt aangemaakt in Floriday en is binnen enkele minuten zichtbaar voor klanten."
|
|
1283
|
-
: "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
|
|
1284
1536
|
? "Wijzigingen opslaan"
|
|
1285
1537
|
: publishToFloriday
|
|
1286
1538
|
? isEditingDraft
|