@flowselections/floriday-authenticatie-module 1.0.13 → 1.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist-lib/components/floriday/FlorydaySyncHealthBanner.d.ts +20 -0
  2. package/dist-lib/components/floriday/FlorydaySyncHealthBanner.d.ts.map +1 -0
  3. package/dist-lib/components/floriday/FlorydaySyncHealthBanner.js +35 -0
  4. package/dist-lib/components/settings/FlorydaySettingsCard.d.ts.map +1 -1
  5. package/dist-lib/components/settings/FlorydaySettingsCard.js +75 -53
  6. package/dist-lib/integrations/supabase/auth-middleware.d.ts +6121 -2360
  7. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  8. package/dist-lib/integrations/supabase/client.d.ts +6121 -2360
  9. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  10. package/dist-lib/integrations/supabase/client.server.d.ts +6121 -2360
  11. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  12. package/dist-lib/integrations/supabase/types.d.ts +6259 -2382
  13. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  14. package/dist-lib/integrations/supabase/types.js +10 -1
  15. package/dist-lib/lib/floricode-product-features.d.ts +50 -0
  16. package/dist-lib/lib/floricode-product-features.d.ts.map +1 -0
  17. package/dist-lib/lib/floricode-product-features.js +314 -0
  18. package/dist-lib/lib/floricode-regulatory-features.d.ts +41 -0
  19. package/dist-lib/lib/floricode-regulatory-features.d.ts.map +1 -0
  20. package/dist-lib/lib/floricode-regulatory-features.js +226 -0
  21. package/dist-lib/lib/floriday-auction-letters.functions.d.ts +9307 -0
  22. package/dist-lib/lib/floriday-auction-letters.functions.d.ts.map +1 -0
  23. package/dist-lib/lib/floriday-auction-letters.functions.js +129 -0
  24. package/dist-lib/lib/floriday-gateway-hmac.d.ts +29 -0
  25. package/dist-lib/lib/floriday-gateway-hmac.d.ts.map +1 -0
  26. package/dist-lib/lib/floriday-gateway-hmac.js +119 -0
  27. package/dist-lib/lib/floriday-labels.functions.d.ts +9312 -0
  28. package/dist-lib/lib/floriday-labels.functions.d.ts.map +1 -0
  29. package/dist-lib/lib/floriday-labels.functions.js +141 -0
  30. package/dist-lib/lib/floriday-public.functions.d.ts +55883 -0
  31. package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -0
  32. package/dist-lib/lib/floriday-public.functions.js +241 -0
  33. package/dist-lib/lib/floriday-scopes.d.ts +20 -0
  34. package/dist-lib/lib/floriday-scopes.d.ts.map +1 -0
  35. package/dist-lib/lib/floriday-scopes.js +89 -0
  36. package/dist-lib/lib/floriday-stickers.functions.d.ts +9304 -0
  37. package/dist-lib/lib/floriday-stickers.functions.d.ts.map +1 -0
  38. package/dist-lib/lib/floriday-stickers.functions.js +128 -0
  39. package/dist-lib/lib/floriday-sync.functions.d.ts +240368 -1916
  40. package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -1
  41. package/dist-lib/lib/floriday-sync.functions.js +836 -35
  42. package/dist-lib/lib/floriday-writes.functions.d.ts +55812 -0
  43. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -0
  44. package/dist-lib/lib/floriday-writes.functions.js +160 -0
  45. package/dist-lib/lib/floriday.functions.d.ts +125816 -46888
  46. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  47. package/dist-lib/lib/floriday.functions.js +451 -58
  48. package/package.json +2 -2
@@ -1,16 +1,18 @@
1
1
  // ============================================================================
2
- // Floriday resource sync — trade-items, sales-orders, customer-organizations.
2
+ // Floriday resource sync — alle sequence-based + full-list sync helpers.
3
3
  // ----------------------------------------------------------------------------
4
4
  // Bouwt voort op `syncFloridayEntityBySequence` uit floriday.functions.ts.
5
5
  // Iedere server fn is admin-only en kan zowel handmatig (UI) als via een
6
- // publieke cron-route worden aangeroepen. Voor cron-aanroepen wordt
7
- // `runFloridayResourceSync` direct met `supabaseAdmin` aangeroepen.
6
+ // publieke cron-route worden aangeroepen.
8
7
  // ============================================================================
9
8
  import { createServerFn } from "@tanstack/react-start";
10
9
  import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
10
+ import { FLORYDAY_CLOCK_SUPPLY_PERMISSION_MESSAGE, FLORYDAY_CONTRACT_PERMISSION_MESSAGE, } from "./floriday-scopes";
11
11
  import { SUPPLIERS_API_VERSION, getActiveFlorydayCredentials, resolveFlorydayCronBaseUrl, syncFloridayEntityBySequence, } from "./floriday.functions";
12
+ const now = () => new Date().toISOString();
12
13
  const RESOURCE_CONFIG = {
13
14
  "trade-items": {
15
+ kind: "sequence",
14
16
  entityType: "trade-items",
15
17
  maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/trade-items/current-max-sequence`,
16
18
  bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/trade-items/sync`,
@@ -26,7 +28,7 @@ const RESOURCE_CONFIG = {
26
28
  sequence_number: Number(item?.sequenceNumber ?? 0),
27
29
  is_deleted: Boolean(item?.isDeleted),
28
30
  data: item,
29
- fetched_at: new Date().toISOString(),
31
+ fetched_at: now(),
30
32
  };
31
33
  })
32
34
  .filter(Boolean);
@@ -40,6 +42,7 @@ const RESOURCE_CONFIG = {
40
42
  },
41
43
  },
42
44
  "sales-orders": {
45
+ kind: "sequence",
43
46
  entityType: "sales-orders",
44
47
  maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/sales-orders/current-max-sequence`,
45
48
  bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/sales-orders/sync`,
@@ -57,7 +60,7 @@ const RESOURCE_CONFIG = {
57
60
  is_deleted: Boolean(item?.isDeleted),
58
61
  sequence_number: Number(item?.sequenceNumber ?? 0),
59
62
  data: item,
60
- updated_at: new Date().toISOString(),
63
+ updated_at: now(),
61
64
  };
62
65
  })
63
66
  .filter(Boolean);
@@ -70,27 +73,37 @@ const RESOURCE_CONFIG = {
70
73
  throw new Error(`sales-orders upsert: ${error.message}`);
71
74
  },
72
75
  },
73
- // De Suppliers API heeft geen /customer-organizations endpoint.
74
- // Klantorganisaties komen uit /connections (elke connection = een
75
- // klantorganisatie die aan deze supplier gekoppeld is).
76
76
  "customer-organizations": {
77
+ // Floriday-richtlijn: gebruik /organizations/sync (CUSTOMER-filter) i.p.v.
78
+ // per-id GET /organizations/{id}. Naam + GLN landen daardoor lokaal en
79
+ // de connections-sync hoeft geen aparte fetch meer te doen.
80
+ kind: "sequence",
77
81
  entityType: "customer-organizations",
78
- maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/connections/current-max-sequence`,
79
- bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/connections/sync`,
82
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/organizations/current-max-sequence`,
83
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/organizations/sync`,
80
84
  upsert: async (supabase, connectionId, items) => {
81
85
  const rows = items
82
86
  .map((item) => {
83
- const oid = item?.customerOrganizationId ??
84
- item?.organizationId ??
87
+ const orgType = item?.organizationType ?? item?.type ?? null;
88
+ if (orgType && orgType !== "CUSTOMER")
89
+ return null;
90
+ const oid = item?.organizationId ??
91
+ item?.customerOrganizationId ??
85
92
  item?.id ??
86
93
  null;
87
94
  if (!oid)
88
95
  return null;
96
+ const name = item?.name ?? item?.commercialName ?? item?.organizationName ?? null;
97
+ const gln = item?.companyGln ?? item?.gln ?? item?.glnCode ?? null;
89
98
  return {
90
99
  organization_id: String(oid),
91
100
  connection_id: connectionId,
101
+ name,
102
+ gln_code: gln,
103
+ is_deleted: Boolean(item?.isDeleted),
104
+ sequence_number: Number(item?.sequenceNumber ?? 0),
92
105
  data: item,
93
- fetched_at: new Date().toISOString(),
106
+ fetched_at: now(),
94
107
  };
95
108
  })
96
109
  .filter(Boolean);
@@ -103,9 +116,8 @@ const RESOURCE_CONFIG = {
103
116
  throw new Error(`customer-organizations upsert: ${error.message}`);
104
117
  },
105
118
  },
106
- // Vervoerders zijn organisaties met OrganizationType = CARRIER.
107
- // We hergebruiken het algemene /organizations endpoint en filteren in onBatch.
108
119
  "carriers": {
120
+ kind: "sequence",
109
121
  entityType: "carriers",
110
122
  maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/organizations/current-max-sequence`,
111
123
  bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/organizations/sync`,
@@ -118,10 +130,7 @@ const RESOURCE_CONFIG = {
118
130
  const oid = item?.organizationId ?? item?.id ?? null;
119
131
  if (!oid)
120
132
  return null;
121
- const name = item?.name ??
122
- item?.commercialName ??
123
- item?.organizationName ??
124
- null;
133
+ const name = item?.name ?? item?.commercialName ?? item?.organizationName ?? null;
125
134
  const gln = item?.companyGln ?? item?.gln ?? item?.glnCode ?? null;
126
135
  return {
127
136
  organization_id: String(oid),
@@ -129,7 +138,7 @@ const RESOURCE_CONFIG = {
129
138
  name,
130
139
  gln_code: gln,
131
140
  data: item,
132
- fetched_at: new Date().toISOString(),
141
+ fetched_at: now(),
133
142
  };
134
143
  })
135
144
  .filter(Boolean);
@@ -142,17 +151,423 @@ const RESOURCE_CONFIG = {
142
151
  throw new Error(`carriers upsert: ${error.message}`);
143
152
  },
144
153
  },
154
+ "fulfillment-orders": {
155
+ kind: "sequence",
156
+ entityType: "fulfillment-orders",
157
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/fulfillment-orders/current-max-sequence`,
158
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/fulfillment-orders/sync`,
159
+ upsert: async (supabase, connectionId, items) => {
160
+ const rows = items
161
+ .map((item) => {
162
+ const fid = item?.fulfillmentOrderId ?? item?.id ?? null;
163
+ if (!fid)
164
+ return null;
165
+ const auctionDateRaw = item?.auctionDate ?? item?.auctionDateTime ?? null;
166
+ const auctionDate = typeof auctionDateRaw === "string" ? auctionDateRaw.slice(0, 10) : null;
167
+ return {
168
+ connection_id: connectionId,
169
+ floriday_id: String(fid),
170
+ delivery_order_id: item?.deliveryOrderId ?? item?.auctionDeliveryOrderId ?? null,
171
+ despatch_warehouse_id: item?.despatchWarehouseId ?? item?.fromWarehouseId ?? null,
172
+ destination_warehouse_id: item?.destinationWarehouseId ?? item?.toWarehouseId ?? null,
173
+ auction_date: auctionDate,
174
+ latest_delivery_at: item?.latestDeliveryDateTime ?? item?.latestDeliveryAt ?? null,
175
+ load_carrier_type: item?.loadCarrierType ?? item?.loadcarrierType ?? null,
176
+ one_label_only: item?.oneLabelOnly ?? null,
177
+ status: item?.status ?? item?.fulfillmentStatus ?? null,
178
+ is_deleted: Boolean(item?.isDeleted),
179
+ sequence_number: Number(item?.sequenceNumber ?? 0),
180
+ data: item,
181
+ fetched_at: now(),
182
+ };
183
+ })
184
+ .filter(Boolean);
185
+ if (rows.length === 0)
186
+ return;
187
+ const { error } = await supabase
188
+ .from("floriday_fulfillment_orders_cache")
189
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
190
+ if (error)
191
+ throw new Error(`fulfillment-orders upsert: ${error.message}`);
192
+ },
193
+ },
194
+ "customer-stickers": {
195
+ kind: "sequence",
196
+ entityType: "customer-stickers",
197
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/customer-stickers/current-max-sequence`,
198
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/customer-stickers/sync`,
199
+ upsert: async (supabase, connectionId, items) => {
200
+ const rows = items
201
+ .map((item) => {
202
+ const fid = item?.customerStickerId ?? item?.stickerId ?? item?.id ?? null;
203
+ if (!fid)
204
+ return null;
205
+ return {
206
+ connection_id: connectionId,
207
+ floriday_id: String(fid),
208
+ sales_order_id: item?.salesOrderId ?? item?.salesOrderLineId ?? null,
209
+ trade_item_id: item?.tradeItemId ?? null,
210
+ quantity: typeof item?.quantity === "number" ? item.quantity : null,
211
+ status: item?.status ?? null,
212
+ is_deleted: Boolean(item?.isDeleted),
213
+ sequence_number: Number(item?.sequenceNumber ?? 0),
214
+ data: item,
215
+ fetched_at: now(),
216
+ };
217
+ })
218
+ .filter(Boolean);
219
+ if (rows.length === 0)
220
+ return;
221
+ const { error } = await supabase
222
+ .from("floriday_customer_stickers_cache")
223
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
224
+ if (error)
225
+ throw new Error(`customer-stickers upsert: ${error.message}`);
226
+ },
227
+ },
228
+ "packings": {
229
+ // Floriday noemt verpakkingen in de Suppliers API "custom packages".
230
+ // Gebruik het voorgeschreven sequence-patroon: current-max-sequence + sync/{sequenceNumber}.
231
+ kind: "sequence",
232
+ entityType: "packings",
233
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/custom-packages/current-max-sequence`,
234
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/custom-packages/sync`,
235
+ upsert: async (supabase, connectionId, items) => {
236
+ const rows = items
237
+ .map((item) => {
238
+ const fid = item?.customPackageId ?? item?.packingId ?? item?.id ?? null;
239
+ if (!fid)
240
+ return null;
241
+ return {
242
+ connection_id: connectionId,
243
+ floriday_id: String(fid),
244
+ sequence_number: Number(item?.sequenceNumber ?? 0),
245
+ is_deleted: Boolean(item?.isDeleted),
246
+ data: item,
247
+ fetched_at: now(),
248
+ updated_at: now(),
249
+ };
250
+ })
251
+ .filter(Boolean);
252
+ if (rows.length === 0)
253
+ return;
254
+ const { error } = await supabase
255
+ .from("floriday_packings_cache")
256
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
257
+ if (error)
258
+ throw new Error(`packings upsert: ${error.message}`);
259
+ },
260
+ },
261
+ "clock-supplies": {
262
+ // Floriday Suppliers API 2026v1 noemt klokaanvoer "clock-supply-lines".
263
+ kind: "sequence",
264
+ entityType: "clock-supplies",
265
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/clock-supply-lines/current-max-sequence`,
266
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/clock-supply-lines/sync`,
267
+ upsert: async (supabase, connectionId, items) => {
268
+ const rows = items
269
+ .map((item) => {
270
+ const fid = item?.clockSupplyLineId ??
271
+ item?.clockSupplyId ??
272
+ item?.supplyLineId ??
273
+ item?.supplyId ??
274
+ item?.id ??
275
+ null;
276
+ if (!fid)
277
+ return null;
278
+ const auctionDateRaw = item?.auctionDate ?? item?.auctionDateTime ?? null;
279
+ return {
280
+ connection_id: connectionId,
281
+ floriday_id: String(fid),
282
+ sequence_number: Number(item?.sequenceNumber ?? 0),
283
+ is_deleted: Boolean(item?.isDeleted),
284
+ trade_item_id: item?.tradeItemId ?? null,
285
+ auction_date: typeof auctionDateRaw === "string" ? auctionDateRaw.slice(0, 10) : null,
286
+ status: item?.status ?? null,
287
+ data: item,
288
+ fetched_at: now(),
289
+ };
290
+ })
291
+ .filter(Boolean);
292
+ if (rows.length === 0)
293
+ return;
294
+ const { error } = await supabase
295
+ .from("floriday_clock_supplies_cache")
296
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
297
+ if (error)
298
+ throw new Error(`clock-supplies upsert: ${error.message}`);
299
+ },
300
+ },
301
+ "delivery-orders": {
302
+ // Floriday Daytrade-stroom: DeliveryOrders volgens sequence-richtlijn.
303
+ // Eerst /current-max-sequence, daarna in batches /sync/{n}.
304
+ kind: "sequence",
305
+ entityType: "delivery-orders",
306
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/delivery-orders/current-max-sequence`,
307
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/delivery-orders/sync`,
308
+ upsert: async (supabase, connectionId, items) => {
309
+ const rows = items
310
+ .map((item) => {
311
+ const fid = item?.deliveryOrderId ?? item?.id ?? null;
312
+ if (!fid)
313
+ return null;
314
+ const auctionDateRaw = item?.auctionDate ?? null;
315
+ return {
316
+ connection_id: connectionId,
317
+ floriday_id: String(fid),
318
+ sequence_number: Number(item?.sequenceNumber ?? 0),
319
+ is_deleted: Boolean(item?.isDeleted),
320
+ trade_item_id: item?.tradeItemId ?? null,
321
+ destination_warehouse_id: item?.destinationWarehouseId ?? item?.warehouseId ?? null,
322
+ auction_date: typeof auctionDateRaw === "string" ? auctionDateRaw.slice(0, 10) : null,
323
+ status: item?.status ?? null,
324
+ data: item,
325
+ fetched_at: now(),
326
+ };
327
+ })
328
+ .filter(Boolean);
329
+ if (rows.length === 0)
330
+ return;
331
+ const { error } = await supabase
332
+ .from("floriday_delivery_orders_cache")
333
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
334
+ if (error)
335
+ throw new Error(`delivery-orders upsert: ${error.message}`);
336
+ },
337
+ },
338
+ "clock-presales": {
339
+ // Floriday Suppliers API 2026v1 noemt klokvoorverkoop "clock-presales-supply-lines".
340
+ kind: "sequence",
341
+ entityType: "clock-presales",
342
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/clock-presales-supply-lines/current-max-sequence`,
343
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/clock-presales-supply-lines/sync`,
344
+ upsert: async (supabase, connectionId, items) => {
345
+ const rows = items
346
+ .map((item) => {
347
+ const fid = item?.clockPresalesSupplyLineId ??
348
+ item?.clockPresaleSupplyLineId ??
349
+ item?.clockPresaleId ??
350
+ item?.presaleId ??
351
+ item?.id ??
352
+ null;
353
+ if (!fid)
354
+ return null;
355
+ return {
356
+ connection_id: connectionId,
357
+ floriday_id: String(fid),
358
+ sequence_number: Number(item?.sequenceNumber ?? 0),
359
+ is_deleted: Boolean(item?.isDeleted),
360
+ clock_supply_id: item?.clockSupplyLineId ?? item?.clockSupplyId ?? null,
361
+ buyer_organization_id: item?.buyerOrganizationId ?? null,
362
+ data: item,
363
+ fetched_at: now(),
364
+ };
365
+ })
366
+ .filter(Boolean);
367
+ if (rows.length === 0)
368
+ return;
369
+ const { error } = await supabase
370
+ .from("floriday_clock_presales_cache")
371
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
372
+ if (error)
373
+ throw new Error(`clock-presales upsert: ${error.message}`);
374
+ },
375
+ },
376
+ // Legacy resources (agreements, agreement-calloffs, trips, invoices) zijn
377
+ // verwijderd: de Floriday Suppliers API 2026v1 biedt voor deze entiteiten
378
+ // geen sequence-endpoints, waardoor sync-aanroepen consistent een 404
379
+ // "route-not-found" gaven. Worden niet meer beheerd door deze module.
380
+ // ── Master-data via sequence-sync (Floriday-richtlijn) ───────────────────
381
+ "warehouses": {
382
+ kind: "sequence",
383
+ entityType: "warehouses",
384
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/warehouses/current-max-sequence`,
385
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/warehouses/sync`,
386
+ upsert: async (supabase, connectionId, items) => {
387
+ const rows = items
388
+ .map((w) => {
389
+ const fid = w?.warehouseId ?? w?.id ?? null;
390
+ if (!fid)
391
+ return null;
392
+ return {
393
+ connection_id: connectionId,
394
+ floriday_id: String(fid),
395
+ name: w?.name ?? null,
396
+ gln_code: w?.location?.gln ?? w?.location?.glnCode ?? w?.gln ?? null,
397
+ is_default: Boolean(w?.isDefault),
398
+ is_deleted: Boolean(w?.isDeleted),
399
+ data: w,
400
+ fetched_at: now(),
401
+ updated_at: now(),
402
+ };
403
+ })
404
+ .filter(Boolean);
405
+ if (rows.length === 0)
406
+ return;
407
+ const { error } = await supabase
408
+ .from("floriday_warehouses_cache")
409
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
410
+ if (error)
411
+ throw new Error(`warehouses upsert: ${error.message}`);
412
+ },
413
+ },
414
+ "delivery-locations": {
415
+ kind: "sequence",
416
+ entityType: "delivery-locations",
417
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/delivery-locations/current-max-sequence`,
418
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/delivery-locations/sync`,
419
+ upsert: async (supabase, connectionId, items) => {
420
+ const rows = items
421
+ .map((d) => {
422
+ const fid = d?.deliveryLocationId ?? d?.id ?? null;
423
+ if (!fid)
424
+ return null;
425
+ return {
426
+ connection_id: connectionId,
427
+ organization_id: d?.organizationId ?? d?.ownerOrganizationId ?? d?.buyerOrganizationId ?? null,
428
+ floriday_id: String(fid),
429
+ gln_code: d?.gln ?? d?.glnCode ?? null,
430
+ name: d?.name ?? d?.locationName ?? null,
431
+ is_deleted: Boolean(d?.isDeleted),
432
+ data: d,
433
+ fetched_at: now(),
434
+ updated_at: now(),
435
+ };
436
+ })
437
+ .filter(Boolean);
438
+ if (rows.length === 0)
439
+ return;
440
+ const { error } = await supabase
441
+ .from("floriday_delivery_locations_cache")
442
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
443
+ if (error)
444
+ throw new Error(`delivery-locations upsert: ${error.message}`);
445
+ },
446
+ },
447
+ "contracts": {
448
+ // Floriday Suppliers API 2026v1 noemt overeenkomsten "contracts".
449
+ // Sequence-richtlijn: eerst current-max-sequence, daarna /sync/{n}.
450
+ kind: "sequence",
451
+ entityType: "contracts",
452
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/contracts/current-max-sequence`,
453
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/contracts/sync`,
454
+ upsert: async (supabase, connectionId, items) => {
455
+ const rows = items
456
+ .map((item) => {
457
+ const fid = item?.contractId ?? item?.id ?? null;
458
+ if (!fid)
459
+ return null;
460
+ return {
461
+ connection_id: connectionId,
462
+ floriday_id: String(fid),
463
+ sequence_number: Number(item?.sequenceNumber ?? 0),
464
+ is_deleted: Boolean(item?.isDeleted),
465
+ data: item,
466
+ fetched_at: now(),
467
+ };
468
+ })
469
+ .filter(Boolean);
470
+ if (rows.length === 0)
471
+ return;
472
+ const { error } = await supabase
473
+ .from("floriday_contracts_cache")
474
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
475
+ if (error)
476
+ throw new Error(`contracts upsert: ${error.message}`);
477
+ },
478
+ },
479
+ "blanket-orders": {
480
+ // Floriday "Blanket Orders" = contract-afroepen.
481
+ kind: "sequence",
482
+ entityType: "blanket-orders",
483
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/blanket-orders/current-max-sequence`,
484
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/blanket-orders/sync`,
485
+ upsert: async (supabase, connectionId, items) => {
486
+ const rows = items
487
+ .map((item) => {
488
+ const fid = item?.blanketOrderId ?? item?.id ?? null;
489
+ if (!fid)
490
+ return null;
491
+ return {
492
+ connection_id: connectionId,
493
+ floriday_id: String(fid),
494
+ sequence_number: Number(item?.sequenceNumber ?? 0),
495
+ is_deleted: Boolean(item?.isDeleted),
496
+ contract_id: item?.contractId ?? null,
497
+ data: item,
498
+ fetched_at: now(),
499
+ };
500
+ })
501
+ .filter(Boolean);
502
+ if (rows.length === 0)
503
+ return;
504
+ const { error } = await supabase
505
+ .from("floriday_blanket_orders_cache")
506
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
507
+ if (error)
508
+ throw new Error(`blanket-orders upsert: ${error.message}`);
509
+ },
510
+ },
511
+ // ── Stamdata voor productformulieren (centraal voor andere modules) ────
512
+ "additional-services": makeSimpleSeq("additional-services", "additional-services", "floriday_additional_services_cache", ["additionalServiceId"]),
513
+ "commercial-service-types": makeSimpleSeq("commercial-service-types", "commercial-service-types", "floriday_commercial_service_types_cache", ["commercialServiceTypeId"]),
514
+ "collections": makeSimpleSeq("collections", "collections", "floriday_collections_cache", ["collectionId"]),
515
+ "price-groups": makeSimpleSeq("price-groups", "price-groups", "floriday_price_groups_cache", ["priceGroupId"]),
516
+ "delivery-condition-sets": makeSimpleSeq("delivery-condition-sets", "delivery-condition-sets", "floriday_delivery_condition_sets_cache", ["deliveryConditionSetId"]),
517
+ // sales-strategies wijkt af van het standaardpatroon: gebruikt
518
+ // `/sync/max-sequence-number` i.p.v. `/current-max-sequence`.
519
+ "sales-strategies": makeSimpleSeq("sales-strategies", "sales-strategies", "floriday_sales_strategies_cache", ["salesStrategyId"], { maxSequencePath: "/sales-strategies/sync/max-sequence-number" }),
520
+ "base-items": makeSimpleSeq("base-items", "base-items", "floriday_base_items_cache", ["baseItemId"]),
521
+ "contract-trade-item-groups": makeSimpleSeq("contract-trade-item-groups", "contract-trade-item-groups", "floriday_contract_trade_item_groups_cache", ["contractTradeItemGroupId"]),
522
+ // NB: `trade-item-classifications` is GEEN Floriday Suppliers-resource (2026v1
523
+ // levert geen `/trade-item-classifications/*` endpoints — bevestigd met 404 op
524
+ // /current-max-sequence). VBN-productcodes en -karakteristieken komen uit
525
+ // Floricode en zijn beschikbaar via de Swagger-enums (gateway/schema +
526
+ // gateway/code-list). Daarom hier bewust geen sequence-sync.
145
527
  };
146
- /**
147
- * Runt een sequence-sync voor één resource binnen één Floriday-koppeling.
148
- * Kan zowel met de user-scoped (RLS) als met de admin-client worden
149
- * aangeroepen — de helper laat dat aan de caller over.
150
- */
528
+ function makeSimpleSeq(entityType, apiPath, table, idFields, opts) {
529
+ const maxPath = opts?.maxSequencePath ?? `/${apiPath}/current-max-sequence`;
530
+ const seqBase = opts?.bySequencePathBase ?? `/${apiPath}/sync`;
531
+ return {
532
+ kind: "sequence",
533
+ entityType,
534
+ maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}${maxPath}`,
535
+ bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}${seqBase}`,
536
+ upsert: async (supabase, connectionId, items) => {
537
+ const rows = items
538
+ .map((item) => {
539
+ const fid = [...idFields, "id"].map((f) => item?.[f]).find((v) => v != null);
540
+ if (!fid)
541
+ return null;
542
+ return {
543
+ connection_id: connectionId,
544
+ floriday_id: String(fid),
545
+ sequence_number: Number(item?.sequenceNumber ?? 0),
546
+ is_deleted: Boolean(item?.isDeleted),
547
+ data: item,
548
+ fetched_at: now(),
549
+ updated_at: now(),
550
+ };
551
+ })
552
+ .filter(Boolean);
553
+ if (rows.length === 0)
554
+ return;
555
+ const { error } = await supabase
556
+ .from(table)
557
+ .upsert(rows, { onConflict: "connection_id,floriday_id" });
558
+ if (error)
559
+ throw new Error(`${entityType} upsert: ${error.message}`);
560
+ },
561
+ };
562
+ }
151
563
  export async function runFloridayResourceSync(supabase, env, resource) {
152
564
  const row = await getActiveFlorydayCredentials(supabase, env);
153
565
  if (!row)
154
566
  throw new Error(`Geen actieve Floriday-koppeling voor ${env}`);
155
567
  const cfg = RESOURCE_CONFIG[resource];
568
+ if (cfg.kind === "full") {
569
+ return cfg.run(supabase, env, row.id);
570
+ }
156
571
  return syncFloridayEntityBySequence(supabase, env, row.id, {
157
572
  entityType: cfg.entityType,
158
573
  maxSequenceEndpoint: cfg.maxSequenceEndpoint,
@@ -172,9 +587,6 @@ async function requireAdmin(supabase, userId) {
172
587
  if (!data)
173
588
  throw new Error("Alleen admins mogen deze actie uitvoeren");
174
589
  }
175
- // NB: server fns MOETEN top-level createServerFn(...).handler(...) chains zijn,
176
- // anders strippt de TanStack server-fn Vite-plugin de handler-body niet en krijg
177
- // je op de client "Cannot read properties of undefined (reading 'rpc')".
178
590
  const envValidator = (input) => {
179
591
  if (!["staging", "live"].includes(input.environment)) {
180
592
  throw new Error("Ongeldige environment");
@@ -184,17 +596,38 @@ const envValidator = (input) => {
184
596
  async function runSyncHandler(context, environment, resource) {
185
597
  await requireAdmin(context.supabase, context.userId);
186
598
  try {
187
- const result = await runFloridayResourceSync(context.supabase, environment, resource);
599
+ // Schrijfacties gaan via de admin-client zodat RLS op de cache-tabellen niet
600
+ // afgehandeld hoeft te worden voor authenticated users — identiek aan de
601
+ // cron-route die elke 5-15 minuten draait.
602
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server");
603
+ const result = await runFloridayResourceSync(supabaseAdmin, environment, resource);
188
604
  return { ok: true, resource, ...result };
189
605
  }
190
606
  catch (e) {
607
+ const rawMessage = e?.message ?? "Onbekende fout";
608
+ const lower = String(rawMessage).toLowerCase();
609
+ const policyDenied = lower.includes("policy evaluation failed") ||
610
+ lower.includes("access_denied") ||
611
+ lower.includes("403") ||
612
+ lower.includes("forbidden");
613
+ const isContractResource = resource === "contracts" ||
614
+ resource === "blanket-orders" ||
615
+ resource === "contract-trade-item-groups";
616
+ let message = rawMessage;
617
+ if (resource === "sales-strategies" && policyDenied) {
618
+ message = FLORYDAY_CLOCK_SUPPLY_PERMISSION_MESSAGE;
619
+ }
620
+ else if (isContractResource && policyDenied) {
621
+ message = FLORYDAY_CONTRACT_PERMISSION_MESSAGE;
622
+ }
191
623
  return {
192
624
  ok: false,
193
625
  resource,
194
- error: e?.message ?? "Onbekende fout",
626
+ error: message,
195
627
  };
196
628
  }
197
629
  }
630
+ // ─── Server fns — één per resource ──────────────────────────────────────────
198
631
  export const syncFlorydayTradeItems = createServerFn({ method: "POST" })
199
632
  .middleware([requireSupabaseAuth])
200
633
  .inputValidator(envValidator)
@@ -211,6 +644,46 @@ export const syncFlorydayCarriers = createServerFn({ method: "POST" })
211
644
  .middleware([requireSupabaseAuth])
212
645
  .inputValidator(envValidator)
213
646
  .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "carriers"));
647
+ export const syncFlorydayFulfillmentOrders = createServerFn({ method: "POST" })
648
+ .middleware([requireSupabaseAuth])
649
+ .inputValidator(envValidator)
650
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "fulfillment-orders"));
651
+ export const syncFlorydayCustomerStickers = createServerFn({ method: "POST" })
652
+ .middleware([requireSupabaseAuth])
653
+ .inputValidator(envValidator)
654
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "customer-stickers"));
655
+ export const syncFlorydayWarehouses = createServerFn({ method: "POST" })
656
+ .middleware([requireSupabaseAuth])
657
+ .inputValidator(envValidator)
658
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "warehouses"));
659
+ export const syncFlorydayPackings = createServerFn({ method: "POST" })
660
+ .middleware([requireSupabaseAuth])
661
+ .inputValidator(envValidator)
662
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "packings"));
663
+ export const syncFlorydayDeliveryLocations = createServerFn({ method: "POST" })
664
+ .middleware([requireSupabaseAuth])
665
+ .inputValidator(envValidator)
666
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "delivery-locations"));
667
+ export const syncFlorydayClockSupplies = createServerFn({ method: "POST" })
668
+ .middleware([requireSupabaseAuth])
669
+ .inputValidator(envValidator)
670
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "clock-supplies"));
671
+ export const syncFlorydayDeliveryOrders = createServerFn({ method: "POST" })
672
+ .middleware([requireSupabaseAuth])
673
+ .inputValidator(envValidator)
674
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "delivery-orders"));
675
+ export const syncFlorydayClockPresales = createServerFn({ method: "POST" })
676
+ .middleware([requireSupabaseAuth])
677
+ .inputValidator(envValidator)
678
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "clock-presales"));
679
+ export const syncFlorydayContracts = createServerFn({ method: "POST" })
680
+ .middleware([requireSupabaseAuth])
681
+ .inputValidator(envValidator)
682
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "contracts"));
683
+ export const syncFlorydayBlanketOrders = createServerFn({ method: "POST" })
684
+ .middleware([requireSupabaseAuth])
685
+ .inputValidator(envValidator)
686
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "blanket-orders"));
214
687
  const SCHEDULER_SPECS = {
215
688
  "trade-items": {
216
689
  rpc: "schedule_floriday_trade_items_sync",
@@ -232,6 +705,96 @@ const SCHEDULER_SPECS = {
232
705
  path: "/api/public/cron/sync-floriday-carriers",
233
706
  jobname: "floriday-carriers-sync-twice-daily",
234
707
  },
708
+ "fulfillment-orders": {
709
+ rpc: "schedule_floriday_fulfillment_orders_sync",
710
+ path: "/api/public/cron/sync-floriday-fulfillment-orders",
711
+ jobname: "floriday-fulfillment-orders-sync-5min",
712
+ },
713
+ "customer-stickers": {
714
+ rpc: "schedule_floriday_customer_stickers_sync",
715
+ path: "/api/public/cron/sync-floriday-customer-stickers",
716
+ jobname: "floriday-customer-stickers-sync-5min",
717
+ },
718
+ "warehouses": {
719
+ rpc: "schedule_floriday_warehouses_sync",
720
+ path: "/api/public/cron/sync-floriday-warehouses",
721
+ jobname: "floriday-warehouses-sync-twice-daily",
722
+ },
723
+ "packings": {
724
+ rpc: "schedule_floriday_packings_sync",
725
+ path: "/api/public/cron/sync-floriday-packings",
726
+ jobname: "floriday-packings-sync-daily",
727
+ },
728
+ "delivery-locations": {
729
+ rpc: "schedule_floriday_delivery_locations_sync",
730
+ path: "/api/public/cron/sync-floriday-delivery-locations",
731
+ jobname: "floriday-delivery-locations-sync-twice-daily",
732
+ },
733
+ "clock-supplies": {
734
+ rpc: "schedule_floriday_clock_supplies_sync",
735
+ path: "/api/public/cron/sync-floriday-clock-supplies",
736
+ jobname: "floriday-clock-supplies-sync-5min",
737
+ },
738
+ "delivery-orders": {
739
+ rpc: "schedule_floriday_delivery_orders_sync",
740
+ path: "/api/public/cron/sync-floriday-delivery-orders",
741
+ jobname: "floriday-delivery-orders-sync-5min",
742
+ },
743
+ "clock-presales": {
744
+ rpc: "schedule_floriday_clock_presales_sync",
745
+ path: "/api/public/cron/sync-floriday-clock-presales",
746
+ jobname: "floriday-clock-presales-sync-5min",
747
+ },
748
+ "contracts": {
749
+ rpc: "schedule_floriday_contracts_sync",
750
+ path: "/api/public/cron/sync-floriday-contracts",
751
+ jobname: "floriday-contracts-sync-hourly",
752
+ },
753
+ "blanket-orders": {
754
+ rpc: "schedule_floriday_blanket_orders_sync",
755
+ path: "/api/public/cron/sync-floriday-blanket-orders",
756
+ jobname: "floriday-blanket-orders-sync-15min",
757
+ },
758
+ "additional-services": {
759
+ rpc: "schedule_floriday_additional_services_sync",
760
+ path: "/api/public/cron/sync-floriday-additional-services",
761
+ jobname: "floriday-additional-services-sync-hourly",
762
+ },
763
+ "commercial-service-types": {
764
+ rpc: "schedule_floriday_commercial_service_types_sync",
765
+ path: "/api/public/cron/sync-floriday-commercial-service-types",
766
+ jobname: "floriday-commercial-service-types-sync-hourly",
767
+ },
768
+ "collections": {
769
+ rpc: "schedule_floriday_collections_sync",
770
+ path: "/api/public/cron/sync-floriday-collections",
771
+ jobname: "floriday-collections-sync-hourly",
772
+ },
773
+ "price-groups": {
774
+ rpc: "schedule_floriday_price_groups_sync",
775
+ path: "/api/public/cron/sync-floriday-price-groups",
776
+ jobname: "floriday-price-groups-sync-hourly",
777
+ },
778
+ "delivery-condition-sets": {
779
+ rpc: "schedule_floriday_delivery_condition_sets_sync",
780
+ path: "/api/public/cron/sync-floriday-delivery-condition-sets",
781
+ jobname: "floriday-delivery-condition-sets-sync-hourly",
782
+ },
783
+ "sales-strategies": {
784
+ rpc: "schedule_floriday_sales_strategies_sync",
785
+ path: "/api/public/cron/sync-floriday-sales-strategies",
786
+ jobname: "floriday-sales-strategies-sync-hourly",
787
+ },
788
+ "base-items": {
789
+ rpc: "schedule_floriday_base_items_sync",
790
+ path: "/api/public/cron/sync-floriday-base-items",
791
+ jobname: "floriday-base-items-sync-hourly",
792
+ },
793
+ "contract-trade-item-groups": {
794
+ rpc: "schedule_floriday_contract_trade_item_groups_sync",
795
+ path: "/api/public/cron/sync-floriday-contract-trade-item-groups",
796
+ jobname: "floriday-contract-trade-item-groups-sync-hourly",
797
+ },
235
798
  };
236
799
  export async function scheduleFlorydayResourceSyncInternal(supabase, resource) {
237
800
  const apikey = process.env.SUPABASE_PUBLISHABLE_KEY ??
@@ -270,11 +833,12 @@ export const scheduleFlorydayCustomerOrganizationsSync = createServerFn({ method
270
833
  export const scheduleFlorydayCarriersSync = createServerFn({ method: "POST" })
271
834
  .middleware([requireSupabaseAuth])
272
835
  .handler(async ({ context }) => runScheduleHandler(context, "carriers"));
273
- /**
274
- * Geeft de actieve Floriday-koppeling van de huidige user terug, zonder
275
- * credentials. Bedoeld voor andere modules (bv. de Voorraad Module) die
276
- * de gedeelde tokenflow gebruiken en alleen meta-informatie nodig hebben.
277
- */
836
+ export const scheduleFlorydayFulfillmentOrdersSync = createServerFn({ method: "POST" })
837
+ .middleware([requireSupabaseAuth])
838
+ .handler(async ({ context }) => runScheduleHandler(context, "fulfillment-orders"));
839
+ export const scheduleFlorydayCustomerStickersSync = createServerFn({ method: "POST" })
840
+ .middleware([requireSupabaseAuth])
841
+ .handler(async ({ context }) => runScheduleHandler(context, "customer-stickers"));
278
842
  export const getActiveFlorydayPublic = createServerFn({ method: "GET" })
279
843
  .middleware([requireSupabaseAuth])
280
844
  .handler(async ({ context }) => {
@@ -304,3 +868,240 @@ export const getActiveFlorydayPublic = createServerFn({ method: "GET" })
304
868
  preferred_warehouse_name: row.preferred_warehouse_name ?? null,
305
869
  };
306
870
  });
871
+ // ─── Server fns voor de nieuwe stamdata-resources ───────────────────────────
872
+ export const syncFlorydayAdditionalServices = createServerFn({ method: "POST" })
873
+ .middleware([requireSupabaseAuth])
874
+ .inputValidator(envValidator)
875
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "additional-services"));
876
+ export const syncFlorydayCommercialServiceTypes = createServerFn({ method: "POST" })
877
+ .middleware([requireSupabaseAuth])
878
+ .inputValidator(envValidator)
879
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "commercial-service-types"));
880
+ export const syncFlorydayCollections = createServerFn({ method: "POST" })
881
+ .middleware([requireSupabaseAuth])
882
+ .inputValidator(envValidator)
883
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "collections"));
884
+ export const syncFlorydayPriceGroups = createServerFn({ method: "POST" })
885
+ .middleware([requireSupabaseAuth])
886
+ .inputValidator(envValidator)
887
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "price-groups"));
888
+ export const syncFlorydayDeliveryConditionSets = createServerFn({ method: "POST" })
889
+ .middleware([requireSupabaseAuth])
890
+ .inputValidator(envValidator)
891
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "delivery-condition-sets"));
892
+ export const syncFlorydaySalesStrategies = createServerFn({ method: "POST" })
893
+ .middleware([requireSupabaseAuth])
894
+ .inputValidator(envValidator)
895
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "sales-strategies"));
896
+ export const syncFlorydayBaseItems = createServerFn({ method: "POST" })
897
+ .middleware([requireSupabaseAuth])
898
+ .inputValidator(envValidator)
899
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "base-items"));
900
+ export const syncFlorydayContractTradeItemGroups = createServerFn({ method: "POST" })
901
+ .middleware([requireSupabaseAuth])
902
+ .inputValidator(envValidator)
903
+ .handler(async ({ data, context }) => runSyncHandler(context, data.environment, "contract-trade-item-groups"));
904
+ // Opmerking: `trade-item-classifications` is verwijderd als sync-resource. De
905
+ // Floriday Suppliers API (2026v1) kent dit endpoint niet (404 op
906
+ // /current-max-sequence). Classificatie-data leeft in de Swagger-enums en
907
+ // wordt door consumers via `/api/public/gateway/schema` en
908
+ // `/api/public/gateway/code-list` opgehaald.
909
+ // ─── Referentiedata: JSON-schema (uit Swagger) + code-lijsten ───────────────
910
+ //
911
+ // De Floriday Suppliers API publiceert haar Swagger publiek; daarmee halen we
912
+ // alle invoervelden + enums op voor het trade-item formulier. Andere modules
913
+ // renderen formulieren op basis van dit schema en valideren tegen dezelfde
914
+ // bron — geen handmatige duplicaten meer.
915
+ const SWAGGER_URL_PER_ENV = {
916
+ staging: `https://api.staging.floriday.io/${SUPPLIERS_API_VERSION}/swagger/UUID/swagger.json`,
917
+ live: `https://api.floriday.io/${SUPPLIERS_API_VERSION}/swagger/GUID/swagger.json`,
918
+ };
919
+ // Schema-roots die we uit de Suppliers-Swagger oogsten. Door breed in te
920
+ // gaan op alle write-body's worden álle relevante enums (Currency,
921
+ // AuctionLocation, SalesUnit, Incoterm, …) automatisch in
922
+ // `floriday_code_lists_cache` geland, zodat andere modules invoervelden
923
+ // kunnen valideren zónder zelf Floriday aan te roepen.
924
+ const SCHEMA_ROOTS = {
925
+ // Catalogus
926
+ "trade-item": "TradeItemBase",
927
+ "trade-item-variant": "TradeItemVariant",
928
+ // Klok-aanbod (publish/edit)
929
+ "add-clock-supply-line": "AddClockSupplyLineV2",
930
+ "edit-clock-supply-line": "EditClockSupplyLine",
931
+ "add-clock-presales-supply-line": "AddClockPresalesSupplyLine",
932
+ // Daghandel / delivery-orders (lees-schema bevat de enums)
933
+ "delivery-order": "DeliveryOrder",
934
+ // Fulfillment
935
+ "fulfillment-order-create": "FulfillmentOrderCreate",
936
+ "fulfillment-order-correction": "AddFulfillmentOrderCorrection",
937
+ "fulfillment-order-claim": "FulfillmentOrderClaim",
938
+ // Sales-order schrijfacties
939
+ "sales-order-correction": "AddSalesOrderCorrectionRequest",
940
+ "sales-order-correction-additional-service": "AddSalesOrderCorrectionRequestForAdditionalService",
941
+ "sales-order-delivery": "AddSalesOrderDelivery",
942
+ // Contracten / blanket-orders
943
+ "add-blanket-order": "AddBlanketOrder",
944
+ "edit-blanket-order": "EditBlanketOrder",
945
+ // Verpakkingen
946
+ "add-batch-packing-configuration": "AddBatchPackingConfiguration",
947
+ "add-collection-packing-configuration": "AddCollectionPackingConfiguration",
948
+ // Master-data + locaties (enums voor types/landen)
949
+ "organization": "Organization",
950
+ "custom-package": "CustomPackage",
951
+ "warehouse": "Warehouse",
952
+ "add-delivery-location": "AddDeliveryLocation",
953
+ "delivery-location": "DeliveryLocation",
954
+ };
955
+ function extractSchemaWithRefs(defs, rootName) {
956
+ const collected = {};
957
+ const enums = {};
958
+ const seen = new Set();
959
+ const walk = (name) => {
960
+ if (seen.has(name) || !defs[name])
961
+ return;
962
+ seen.add(name);
963
+ const def = defs[name];
964
+ collected[name] = def;
965
+ if (Array.isArray(def.enum)) {
966
+ enums[name] = def.enum.map((v) => String(v));
967
+ }
968
+ const props = def.properties || {};
969
+ for (const prop of Object.values(props)) {
970
+ const ref = prop?.$ref ?? prop?.items?.$ref ?? prop?.additionalProperties?.$ref;
971
+ if (typeof ref === "string") {
972
+ walk(ref.split("/").pop());
973
+ }
974
+ }
975
+ };
976
+ walk(rootName);
977
+ return {
978
+ schema: { rootName, definitions: collected },
979
+ enums,
980
+ };
981
+ }
982
+ async function fetchAndStoreFlorydayTradeItemSchema(supabase, env) {
983
+ const results = [];
984
+ let swaggerJson = null;
985
+ try {
986
+ const res = await fetch(SWAGGER_URL_PER_ENV[env], { cache: "no-store" });
987
+ if (!res.ok)
988
+ throw new Error(`Swagger HTTP ${res.status}`);
989
+ swaggerJson = await res.json();
990
+ }
991
+ catch (e) {
992
+ for (const resource of Object.keys(SCHEMA_ROOTS)) {
993
+ results.push({ resource, ok: false, error: `swagger fetch: ${e?.message ?? e}` });
994
+ }
995
+ return results;
996
+ }
997
+ const defs = swaggerJson.definitions ?? {};
998
+ for (const [resource, root] of Object.entries(SCHEMA_ROOTS)) {
999
+ try {
1000
+ const { schema, enums } = extractSchemaWithRefs(defs, root);
1001
+ const { error } = await supabase
1002
+ .from("floriday_resource_schemas")
1003
+ .upsert({
1004
+ resource,
1005
+ version: SUPPLIERS_API_VERSION,
1006
+ schema,
1007
+ enums,
1008
+ source_url: SWAGGER_URL_PER_ENV[env],
1009
+ fetched_at: new Date().toISOString(),
1010
+ }, { onConflict: "resource,version" });
1011
+ if (error)
1012
+ throw new Error(error.message);
1013
+ results.push({ resource, ok: true });
1014
+ }
1015
+ catch (e) {
1016
+ results.push({ resource, ok: false, error: e?.message ?? String(e) });
1017
+ }
1018
+ }
1019
+ return results;
1020
+ }
1021
+ // Code-lijsten worden per environment opgehaald uit de Floriday-bronnen.
1022
+ // 2026v1 levert codelijsten als enums via Swagger en daarnaast via separate
1023
+ // endpoints. De enums die in de Swagger staan worden hier ook als code-list
1024
+ // platgeslagen zodat andere modules niet hoeven te weten waar ze vandaan komen.
1025
+ async function refreshFlorydayCodeListsFromSchemas(supabase, env) {
1026
+ const results = [];
1027
+ const { data: schemaRows, error } = await supabase
1028
+ .from("floriday_resource_schemas")
1029
+ .select("enums")
1030
+ .eq("version", SUPPLIERS_API_VERSION);
1031
+ if (error) {
1032
+ return [{ list_name: "*", count: 0, ok: false, error: error.message }];
1033
+ }
1034
+ const merged = {};
1035
+ for (const row of (schemaRows ?? [])) {
1036
+ const enums = (row?.enums ?? {});
1037
+ for (const [name, values] of Object.entries(enums)) {
1038
+ if (!merged[name])
1039
+ merged[name] = [];
1040
+ for (const v of values) {
1041
+ if (!merged[name].includes(v))
1042
+ merged[name].push(v);
1043
+ }
1044
+ }
1045
+ }
1046
+ for (const [list_name, codes] of Object.entries(merged)) {
1047
+ try {
1048
+ const rows = codes.map((code) => ({
1049
+ environment: env,
1050
+ list_name,
1051
+ code,
1052
+ label: code,
1053
+ payload: null,
1054
+ updated_at: new Date().toISOString(),
1055
+ }));
1056
+ const { error: upErr } = await supabase
1057
+ .from("floriday_code_lists_cache")
1058
+ .upsert(rows, { onConflict: "environment,list_name,code" });
1059
+ if (upErr)
1060
+ throw new Error(upErr.message);
1061
+ results.push({ list_name, count: codes.length, ok: true });
1062
+ }
1063
+ catch (e) {
1064
+ results.push({
1065
+ list_name,
1066
+ count: 0,
1067
+ ok: false,
1068
+ error: e?.message ?? String(e),
1069
+ });
1070
+ }
1071
+ }
1072
+ return results;
1073
+ }
1074
+ export async function runFlorydayReferenceDataRefresh(supabase, env) {
1075
+ const schemas = await fetchAndStoreFlorydayTradeItemSchema(supabase, env);
1076
+ const codeLists = await refreshFlorydayCodeListsFromSchemas(supabase, env);
1077
+ return { ok: true, schemas, codeLists };
1078
+ }
1079
+ export const refreshFlorydayReferenceData = createServerFn({ method: "POST" })
1080
+ .middleware([requireSupabaseAuth])
1081
+ .inputValidator(envValidator)
1082
+ .handler(async ({ data, context }) => {
1083
+ await requireAdmin(context.supabase, context.userId);
1084
+ try {
1085
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server");
1086
+ return await runFlorydayReferenceDataRefresh(supabaseAdmin, data.environment);
1087
+ }
1088
+ catch (e) {
1089
+ return { ok: false, error: e?.message ?? "Onbekende fout" };
1090
+ }
1091
+ });
1092
+ export async function scheduleFlorydayReferenceDataRefreshInternal(supabase) {
1093
+ const apikey = process.env.SUPABASE_PUBLISHABLE_KEY ?? process.env.VITE_SUPABASE_PUBLISHABLE_KEY;
1094
+ if (!apikey)
1095
+ throw new Error("Supabase anon key ontbreekt in server-env");
1096
+ const url = `${resolveFlorydayCronBaseUrl()}/api/public/cron/refresh-floriday-reference-data`;
1097
+ const { data, error } = await supabase.rpc("schedule_floriday_reference_data_refresh", { p_url: url, p_apikey: apikey });
1098
+ if (error)
1099
+ throw new Error(error.message);
1100
+ return { ok: true, jobid: data, url };
1101
+ }
1102
+ export const scheduleFlorydayReferenceDataRefresh = createServerFn({ method: "POST" })
1103
+ .middleware([requireSupabaseAuth])
1104
+ .handler(async ({ context }) => {
1105
+ await requireAdmin(context.supabase, context.userId);
1106
+ return scheduleFlorydayReferenceDataRefreshInternal(context.supabase);
1107
+ });