@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.
Files changed (29) hide show
  1. package/dist-lib/assets/floriday-logo.png.asset.json +11 -0
  2. package/dist-lib/components/floriday/FlorydaySyncJobsCard.d.ts +2 -0
  3. package/dist-lib/components/floriday/FlorydaySyncJobsCard.d.ts.map +1 -0
  4. package/dist-lib/components/floriday/FlorydaySyncJobsCard.js +99 -0
  5. package/dist-lib/components/floriday/TokenHealthCard.d.ts +2 -0
  6. package/dist-lib/components/floriday/TokenHealthCard.d.ts.map +1 -0
  7. package/dist-lib/components/floriday/TokenHealthCard.js +93 -0
  8. package/dist-lib/components/settings/FlorydaySettingsCard.d.ts.map +1 -1
  9. package/dist-lib/components/settings/FlorydaySettingsCard.js +116 -3
  10. package/dist-lib/index.d.ts +1 -0
  11. package/dist-lib/index.d.ts.map +1 -1
  12. package/dist-lib/index.js +2 -1
  13. package/dist-lib/integrations/supabase/auth-middleware.d.ts +4685 -595
  14. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  15. package/dist-lib/integrations/supabase/client.d.ts +4685 -595
  16. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  17. package/dist-lib/integrations/supabase/client.server.d.ts +4685 -595
  18. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  19. package/dist-lib/integrations/supabase/client.server.js +8 -2
  20. package/dist-lib/integrations/supabase/types.d.ts +4788 -586
  21. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  22. package/dist-lib/integrations/supabase/types.js +11 -0
  23. package/dist-lib/lib/floriday-sync.functions.d.ts +49839 -0
  24. package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -0
  25. package/dist-lib/lib/floriday-sync.functions.js +306 -0
  26. package/dist-lib/lib/floriday.functions.d.ts +82115 -6526
  27. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  28. package/dist-lib/lib/floriday.functions.js +475 -37
  29. 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
+ });