@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.
- package/dist-lib/components/floriday/FlorydaySyncHealthBanner.d.ts +20 -0
- package/dist-lib/components/floriday/FlorydaySyncHealthBanner.d.ts.map +1 -0
- package/dist-lib/components/floriday/FlorydaySyncHealthBanner.js +35 -0
- package/dist-lib/components/settings/FlorydaySettingsCard.d.ts.map +1 -1
- package/dist-lib/components/settings/FlorydaySettingsCard.js +75 -53
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +6121 -2360
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +6121 -2360
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +6121 -2360
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +6259 -2382
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +10 -1
- package/dist-lib/lib/floricode-product-features.d.ts +50 -0
- package/dist-lib/lib/floricode-product-features.d.ts.map +1 -0
- package/dist-lib/lib/floricode-product-features.js +314 -0
- package/dist-lib/lib/floricode-regulatory-features.d.ts +41 -0
- package/dist-lib/lib/floricode-regulatory-features.d.ts.map +1 -0
- package/dist-lib/lib/floricode-regulatory-features.js +226 -0
- package/dist-lib/lib/floriday-auction-letters.functions.d.ts +9307 -0
- package/dist-lib/lib/floriday-auction-letters.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-auction-letters.functions.js +129 -0
- package/dist-lib/lib/floriday-gateway-hmac.d.ts +29 -0
- package/dist-lib/lib/floriday-gateway-hmac.d.ts.map +1 -0
- package/dist-lib/lib/floriday-gateway-hmac.js +119 -0
- package/dist-lib/lib/floriday-labels.functions.d.ts +9312 -0
- package/dist-lib/lib/floriday-labels.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-labels.functions.js +141 -0
- package/dist-lib/lib/floriday-public.functions.d.ts +55883 -0
- package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-public.functions.js +241 -0
- package/dist-lib/lib/floriday-scopes.d.ts +20 -0
- package/dist-lib/lib/floriday-scopes.d.ts.map +1 -0
- package/dist-lib/lib/floriday-scopes.js +89 -0
- package/dist-lib/lib/floriday-stickers.functions.d.ts +9304 -0
- package/dist-lib/lib/floriday-stickers.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-stickers.functions.js +128 -0
- package/dist-lib/lib/floriday-sync.functions.d.ts +240368 -1916
- package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-sync.functions.js +836 -35
- package/dist-lib/lib/floriday-writes.functions.d.ts +55812 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-writes.functions.js +160 -0
- package/dist-lib/lib/floriday.functions.d.ts +125816 -46888
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.js +451 -58
- package/package.json +2 -2
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
// ============================================================================
|
|
2
|
-
// Floriday resource sync —
|
|
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.
|
|
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:
|
|
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:
|
|
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}/
|
|
79
|
-
bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/
|
|
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
|
|
84
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
+
});
|