@flowselections/floriday-authenticatie-module 1.0.12 → 1.0.13
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/assets/floriday-logo.png.asset.json +11 -0
- package/dist-lib/components/floriday/FlorydaySyncJobsCard.d.ts +2 -0
- package/dist-lib/components/floriday/FlorydaySyncJobsCard.d.ts.map +1 -0
- package/dist-lib/components/floriday/FlorydaySyncJobsCard.js +99 -0
- package/dist-lib/components/floriday/TokenHealthCard.d.ts +2 -0
- package/dist-lib/components/floriday/TokenHealthCard.d.ts.map +1 -0
- package/dist-lib/components/floriday/TokenHealthCard.js +93 -0
- package/dist-lib/components/settings/FlorydaySettingsCard.d.ts.map +1 -1
- package/dist-lib/components/settings/FlorydaySettingsCard.js +116 -3
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +2 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +4685 -595
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +4685 -595
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +4685 -595
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.js +8 -2
- package/dist-lib/integrations/supabase/types.d.ts +4788 -586
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +11 -0
- package/dist-lib/lib/floriday-sync.functions.d.ts +49839 -0
- package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-sync.functions.js +306 -0
- package/dist-lib/lib/floriday.functions.d.ts +82115 -6526
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.js +475 -37
- package/package.json +5 -4
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Floriday resource sync — trade-items, sales-orders, customer-organizations.
|
|
3
|
+
// ----------------------------------------------------------------------------
|
|
4
|
+
// Bouwt voort op `syncFloridayEntityBySequence` uit floriday.functions.ts.
|
|
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.
|
|
8
|
+
// ============================================================================
|
|
9
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
10
|
+
import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
|
|
11
|
+
import { SUPPLIERS_API_VERSION, getActiveFlorydayCredentials, resolveFlorydayCronBaseUrl, syncFloridayEntityBySequence, } from "./floriday.functions";
|
|
12
|
+
const RESOURCE_CONFIG = {
|
|
13
|
+
"trade-items": {
|
|
14
|
+
entityType: "trade-items",
|
|
15
|
+
maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/trade-items/current-max-sequence`,
|
|
16
|
+
bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/trade-items/sync`,
|
|
17
|
+
upsert: async (supabase, connectionId, items) => {
|
|
18
|
+
const rows = items
|
|
19
|
+
.map((item) => {
|
|
20
|
+
const fid = item?.tradeItemId ?? item?.id ?? null;
|
|
21
|
+
if (!fid)
|
|
22
|
+
return null;
|
|
23
|
+
return {
|
|
24
|
+
connection_id: connectionId,
|
|
25
|
+
floriday_id: String(fid),
|
|
26
|
+
sequence_number: Number(item?.sequenceNumber ?? 0),
|
|
27
|
+
is_deleted: Boolean(item?.isDeleted),
|
|
28
|
+
data: item,
|
|
29
|
+
fetched_at: new Date().toISOString(),
|
|
30
|
+
};
|
|
31
|
+
})
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
if (rows.length === 0)
|
|
34
|
+
return;
|
|
35
|
+
const { error } = await supabase
|
|
36
|
+
.from("floriday_trade_items_cache")
|
|
37
|
+
.upsert(rows, { onConflict: "connection_id,floriday_id" });
|
|
38
|
+
if (error)
|
|
39
|
+
throw new Error(`trade-items upsert: ${error.message}`);
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
"sales-orders": {
|
|
43
|
+
entityType: "sales-orders",
|
|
44
|
+
maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/sales-orders/current-max-sequence`,
|
|
45
|
+
bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/sales-orders/sync`,
|
|
46
|
+
upsert: async (supabase, connectionId, items) => {
|
|
47
|
+
const rows = items
|
|
48
|
+
.map((item) => {
|
|
49
|
+
const fid = item?.salesOrderId ?? item?.id ?? null;
|
|
50
|
+
if (!fid)
|
|
51
|
+
return null;
|
|
52
|
+
return {
|
|
53
|
+
connection_id: connectionId,
|
|
54
|
+
floriday_id: String(fid),
|
|
55
|
+
buyer_organization_id: item?.buyerOrganizationId ?? null,
|
|
56
|
+
status: item?.status ?? null,
|
|
57
|
+
is_deleted: Boolean(item?.isDeleted),
|
|
58
|
+
sequence_number: Number(item?.sequenceNumber ?? 0),
|
|
59
|
+
data: item,
|
|
60
|
+
updated_at: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
})
|
|
63
|
+
.filter(Boolean);
|
|
64
|
+
if (rows.length === 0)
|
|
65
|
+
return;
|
|
66
|
+
const { error } = await supabase
|
|
67
|
+
.from("floriday_salesorders_cache")
|
|
68
|
+
.upsert(rows, { onConflict: "connection_id,floriday_id" });
|
|
69
|
+
if (error)
|
|
70
|
+
throw new Error(`sales-orders upsert: ${error.message}`);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
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
|
+
"customer-organizations": {
|
|
77
|
+
entityType: "customer-organizations",
|
|
78
|
+
maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/connections/current-max-sequence`,
|
|
79
|
+
bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/connections/sync`,
|
|
80
|
+
upsert: async (supabase, connectionId, items) => {
|
|
81
|
+
const rows = items
|
|
82
|
+
.map((item) => {
|
|
83
|
+
const oid = item?.customerOrganizationId ??
|
|
84
|
+
item?.organizationId ??
|
|
85
|
+
item?.id ??
|
|
86
|
+
null;
|
|
87
|
+
if (!oid)
|
|
88
|
+
return null;
|
|
89
|
+
return {
|
|
90
|
+
organization_id: String(oid),
|
|
91
|
+
connection_id: connectionId,
|
|
92
|
+
data: item,
|
|
93
|
+
fetched_at: new Date().toISOString(),
|
|
94
|
+
};
|
|
95
|
+
})
|
|
96
|
+
.filter(Boolean);
|
|
97
|
+
if (rows.length === 0)
|
|
98
|
+
return;
|
|
99
|
+
const { error } = await supabase
|
|
100
|
+
.from("floriday_customers_cache")
|
|
101
|
+
.upsert(rows, { onConflict: "organization_id" });
|
|
102
|
+
if (error)
|
|
103
|
+
throw new Error(`customer-organizations upsert: ${error.message}`);
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
// Vervoerders zijn organisaties met OrganizationType = CARRIER.
|
|
107
|
+
// We hergebruiken het algemene /organizations endpoint en filteren in onBatch.
|
|
108
|
+
"carriers": {
|
|
109
|
+
entityType: "carriers",
|
|
110
|
+
maxSequenceEndpoint: `/${SUPPLIERS_API_VERSION}/organizations/current-max-sequence`,
|
|
111
|
+
bySequenceEndpointBase: `/${SUPPLIERS_API_VERSION}/organizations/sync`,
|
|
112
|
+
upsert: async (supabase, connectionId, items) => {
|
|
113
|
+
const rows = items
|
|
114
|
+
.map((item) => {
|
|
115
|
+
const orgType = item?.organizationType ?? item?.type ?? null;
|
|
116
|
+
if (orgType !== "CARRIER")
|
|
117
|
+
return null;
|
|
118
|
+
const oid = item?.organizationId ?? item?.id ?? null;
|
|
119
|
+
if (!oid)
|
|
120
|
+
return null;
|
|
121
|
+
const name = item?.name ??
|
|
122
|
+
item?.commercialName ??
|
|
123
|
+
item?.organizationName ??
|
|
124
|
+
null;
|
|
125
|
+
const gln = item?.companyGln ?? item?.gln ?? item?.glnCode ?? null;
|
|
126
|
+
return {
|
|
127
|
+
organization_id: String(oid),
|
|
128
|
+
connection_id: connectionId,
|
|
129
|
+
name,
|
|
130
|
+
gln_code: gln,
|
|
131
|
+
data: item,
|
|
132
|
+
fetched_at: new Date().toISOString(),
|
|
133
|
+
};
|
|
134
|
+
})
|
|
135
|
+
.filter(Boolean);
|
|
136
|
+
if (rows.length === 0)
|
|
137
|
+
return;
|
|
138
|
+
const { error } = await supabase
|
|
139
|
+
.from("floriday_carriers_cache")
|
|
140
|
+
.upsert(rows, { onConflict: "organization_id" });
|
|
141
|
+
if (error)
|
|
142
|
+
throw new Error(`carriers upsert: ${error.message}`);
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
};
|
|
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
|
+
*/
|
|
151
|
+
export async function runFloridayResourceSync(supabase, env, resource) {
|
|
152
|
+
const row = await getActiveFlorydayCredentials(supabase, env);
|
|
153
|
+
if (!row)
|
|
154
|
+
throw new Error(`Geen actieve Floriday-koppeling voor ${env}`);
|
|
155
|
+
const cfg = RESOURCE_CONFIG[resource];
|
|
156
|
+
return syncFloridayEntityBySequence(supabase, env, row.id, {
|
|
157
|
+
entityType: cfg.entityType,
|
|
158
|
+
maxSequenceEndpoint: cfg.maxSequenceEndpoint,
|
|
159
|
+
bySequenceEndpointBase: cfg.bySequenceEndpointBase,
|
|
160
|
+
onBatch: async (items) => {
|
|
161
|
+
await cfg.upsert(supabase, row.id, items);
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async function requireAdmin(supabase, userId) {
|
|
166
|
+
const { data, error } = await supabase.rpc("has_role", {
|
|
167
|
+
_user_id: userId,
|
|
168
|
+
_role: "admin",
|
|
169
|
+
});
|
|
170
|
+
if (error)
|
|
171
|
+
throw new Error(error.message);
|
|
172
|
+
if (!data)
|
|
173
|
+
throw new Error("Alleen admins mogen deze actie uitvoeren");
|
|
174
|
+
}
|
|
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
|
+
const envValidator = (input) => {
|
|
179
|
+
if (!["staging", "live"].includes(input.environment)) {
|
|
180
|
+
throw new Error("Ongeldige environment");
|
|
181
|
+
}
|
|
182
|
+
return input;
|
|
183
|
+
};
|
|
184
|
+
async function runSyncHandler(context, environment, resource) {
|
|
185
|
+
await requireAdmin(context.supabase, context.userId);
|
|
186
|
+
try {
|
|
187
|
+
const result = await runFloridayResourceSync(context.supabase, environment, resource);
|
|
188
|
+
return { ok: true, resource, ...result };
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
resource,
|
|
194
|
+
error: e?.message ?? "Onbekende fout",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
export const syncFlorydayTradeItems = createServerFn({ method: "POST" })
|
|
199
|
+
.middleware([requireSupabaseAuth])
|
|
200
|
+
.inputValidator(envValidator)
|
|
201
|
+
.handler(async ({ data, context }) => runSyncHandler(context, data.environment, "trade-items"));
|
|
202
|
+
export const syncFlorydaySalesOrders = createServerFn({ method: "POST" })
|
|
203
|
+
.middleware([requireSupabaseAuth])
|
|
204
|
+
.inputValidator(envValidator)
|
|
205
|
+
.handler(async ({ data, context }) => runSyncHandler(context, data.environment, "sales-orders"));
|
|
206
|
+
export const syncFlorydayCustomerOrganizations = createServerFn({ method: "POST" })
|
|
207
|
+
.middleware([requireSupabaseAuth])
|
|
208
|
+
.inputValidator(envValidator)
|
|
209
|
+
.handler(async ({ data, context }) => runSyncHandler(context, data.environment, "customer-organizations"));
|
|
210
|
+
export const syncFlorydayCarriers = createServerFn({ method: "POST" })
|
|
211
|
+
.middleware([requireSupabaseAuth])
|
|
212
|
+
.inputValidator(envValidator)
|
|
213
|
+
.handler(async ({ data, context }) => runSyncHandler(context, data.environment, "carriers"));
|
|
214
|
+
const SCHEDULER_SPECS = {
|
|
215
|
+
"trade-items": {
|
|
216
|
+
rpc: "schedule_floriday_trade_items_sync",
|
|
217
|
+
path: "/api/public/cron/sync-floriday-trade-items",
|
|
218
|
+
jobname: "floriday-trade-items-sync-5min",
|
|
219
|
+
},
|
|
220
|
+
"sales-orders": {
|
|
221
|
+
rpc: "schedule_floriday_salesorders_sync",
|
|
222
|
+
path: "/api/public/cron/sync-floriday-salesorders",
|
|
223
|
+
jobname: "floriday-salesorders-sync-5min",
|
|
224
|
+
},
|
|
225
|
+
"customer-organizations": {
|
|
226
|
+
rpc: "schedule_floriday_customer_organizations_sync",
|
|
227
|
+
path: "/api/public/cron/sync-floriday-customer-organizations",
|
|
228
|
+
jobname: "floriday-customer-organizations-sync-twice-daily",
|
|
229
|
+
},
|
|
230
|
+
"carriers": {
|
|
231
|
+
rpc: "schedule_floriday_carriers_sync",
|
|
232
|
+
path: "/api/public/cron/sync-floriday-carriers",
|
|
233
|
+
jobname: "floriday-carriers-sync-twice-daily",
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
export async function scheduleFlorydayResourceSyncInternal(supabase, resource) {
|
|
237
|
+
const apikey = process.env.SUPABASE_PUBLISHABLE_KEY ??
|
|
238
|
+
process.env.VITE_SUPABASE_PUBLISHABLE_KEY;
|
|
239
|
+
if (!apikey)
|
|
240
|
+
throw new Error("Supabase anon key ontbreekt in server-env");
|
|
241
|
+
const spec = SCHEDULER_SPECS[resource];
|
|
242
|
+
const url = `${resolveFlorydayCronBaseUrl()}${spec.path}`;
|
|
243
|
+
const { data, error } = await supabase.rpc(spec.rpc, {
|
|
244
|
+
p_url: url,
|
|
245
|
+
p_apikey: apikey,
|
|
246
|
+
});
|
|
247
|
+
if (error)
|
|
248
|
+
throw new Error(error.message);
|
|
249
|
+
return {
|
|
250
|
+
ok: true,
|
|
251
|
+
resource,
|
|
252
|
+
jobid: data,
|
|
253
|
+
jobname: spec.jobname,
|
|
254
|
+
url,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
async function runScheduleHandler(context, resource) {
|
|
258
|
+
await requireAdmin(context.supabase, context.userId);
|
|
259
|
+
return scheduleFlorydayResourceSyncInternal(context.supabase, resource);
|
|
260
|
+
}
|
|
261
|
+
export const scheduleFlorydayTradeItemsSync = createServerFn({ method: "POST" })
|
|
262
|
+
.middleware([requireSupabaseAuth])
|
|
263
|
+
.handler(async ({ context }) => runScheduleHandler(context, "trade-items"));
|
|
264
|
+
export const scheduleFlorydaySalesOrdersSync = createServerFn({ method: "POST" })
|
|
265
|
+
.middleware([requireSupabaseAuth])
|
|
266
|
+
.handler(async ({ context }) => runScheduleHandler(context, "sales-orders"));
|
|
267
|
+
export const scheduleFlorydayCustomerOrganizationsSync = createServerFn({ method: "POST" })
|
|
268
|
+
.middleware([requireSupabaseAuth])
|
|
269
|
+
.handler(async ({ context }) => runScheduleHandler(context, "customer-organizations"));
|
|
270
|
+
export const scheduleFlorydayCarriersSync = createServerFn({ method: "POST" })
|
|
271
|
+
.middleware([requireSupabaseAuth])
|
|
272
|
+
.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
|
+
*/
|
|
278
|
+
export const getActiveFlorydayPublic = createServerFn({ method: "GET" })
|
|
279
|
+
.middleware([requireSupabaseAuth])
|
|
280
|
+
.handler(async ({ context }) => {
|
|
281
|
+
const { data: settings } = await context.supabase
|
|
282
|
+
.from("floriday_settings")
|
|
283
|
+
.select("active_environment")
|
|
284
|
+
.eq("id", 1)
|
|
285
|
+
.maybeSingle();
|
|
286
|
+
const env = settings?.active_environment ===
|
|
287
|
+
"live"
|
|
288
|
+
? "live"
|
|
289
|
+
: "staging";
|
|
290
|
+
const { data: row } = await context.supabase
|
|
291
|
+
.from("floriday_connections")
|
|
292
|
+
.select("id, environment, organization_name, gln_code, preferred_warehouse_id, preferred_warehouse_name")
|
|
293
|
+
.eq("environment", env)
|
|
294
|
+
.eq("is_active", true)
|
|
295
|
+
.maybeSingle();
|
|
296
|
+
if (!row)
|
|
297
|
+
return null;
|
|
298
|
+
return {
|
|
299
|
+
environment: row.environment,
|
|
300
|
+
connection_id: row.id,
|
|
301
|
+
organization_name: row.organization_name ?? null,
|
|
302
|
+
gln_code: row.gln_code ?? null,
|
|
303
|
+
preferred_warehouse_id: row.preferred_warehouse_id ?? null,
|
|
304
|
+
preferred_warehouse_name: row.preferred_warehouse_name ?? null,
|
|
305
|
+
};
|
|
306
|
+
});
|