@flowselections/floriday-authenticatie-module 1.0.54 → 1.0.56

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.
@@ -7,27 +7,8 @@
7
7
  import { createServerFn } from "@tanstack/react-start";
8
8
  import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware.js";
9
9
  import { FLORYDAY_CLOCK_SUPPLY_PERMISSION_MESSAGE, FLORYDAY_CONTRACT_PERMISSION_MESSAGE, FLORYDAY_SALES_STRATEGY_PERMISSION_MESSAGE, FLORYDAY_STANDARD_SCOPE, getFlorydayScopeForPath, isClockSupplyScope, isContractScope, isSalesStrategyScope, } from "./floriday-scopes.js";
10
- /**
11
- * Valideert een GS1 GLN: exact 13 cijfers met geldige check digit.
12
- * Retourneert null als geldig, anders een nl-foutmelding.
13
- */
14
- export function validateGln(input) {
15
- const v = input.trim();
16
- if (!v)
17
- return "GLN-code is verplicht";
18
- if (!/^\d{13}$/.test(v))
19
- return "GLN-code moet uit precies 13 cijfers bestaan";
20
- const digits = v.split("").map(Number);
21
- // GS1 mod-10: positie 1 (meest links) ×3, positie 2 ×1, … t/m positie 12.
22
- let sum = 0;
23
- for (let i = 0; i < 12; i++) {
24
- sum += digits[i] * (i % 2 === 0 ? 3 : 1);
25
- }
26
- const check = (10 - (sum % 10)) % 10;
27
- if (check !== digits[12])
28
- return "GLN-code heeft een ongeldig controlecijfer";
29
- return null;
30
- }
10
+ export { validateGln } from "./floriday-gln.js";
11
+ import { decideGln, decidePreferredWarehouse, isOwnWarehouse, warehouseLabel } from "./floriday-own-config.js";
31
12
  const FLORYDAY_ENDPOINTS = {
32
13
  staging: {
33
14
  tokenUrl: "https://idm.staging.floriday.io/oauth2/ausmw6b47z1BnlHkw0h7/v1/token",
@@ -281,20 +262,36 @@ async function fetchAndStoreFlorydayOrganizationName(supabase, env, connectionId
281
262
  }
282
263
  const orgJson = await orgRes.json().catch(() => null);
283
264
  const name = orgJson?.name ?? orgJson?.commercialName ?? null;
284
- const gln = orgJson?.companyGln ?? null;
285
- if (!name)
286
- return null;
287
- // Lees huidige rij om gln_code niet te overschrijven als die al gezet is.
265
+ const remoteGln = orgJson?.companyGln ?? null;
288
266
  const { data: existing } = await supabaseAdmin
289
267
  .from("floriday_connections")
290
268
  .select("gln_code")
291
269
  .eq("id", connectionId)
292
270
  .maybeSingle();
293
- const update = { organization_name: name };
294
- if (gln && !existing?.gln_code) {
295
- update.gln_code = gln;
271
+ const storedGln = existing?.gln_code ?? null;
272
+ const decision = decideGln({ stored: storedGln, fromFloriday: remoteGln });
273
+ if (name) {
274
+ await supabaseAdmin
275
+ .from("floriday_connections")
276
+ .update({ organization_name: name })
277
+ .eq("id", connectionId);
278
+ }
279
+ if (decision.kind === "fill") {
280
+ // Alleen een leeg veld vullen; filter op null voorkomt overschrijven.
281
+ await supabaseAdmin
282
+ .from("floriday_connections")
283
+ .update({ gln_code: decision.gln })
284
+ .eq("id", connectionId)
285
+ .is("gln_code", null);
286
+ console.log(`[floriday] gln_code automatisch ingevuld uit Floriday (connection ${connectionId})`);
287
+ }
288
+ else if (decision.kind === "mismatch") {
289
+ console.warn(`[floriday] GLN in Floriday wijkt af van opgeslagen GLN (connection ${connectionId}); niet overschreven`);
296
290
  }
297
- await supabaseAdmin.from("floriday_connections").update(update).eq("id", connectionId);
291
+ else if (decision.kind === "invalid") {
292
+ console.warn(`[floriday] companyGln uit Floriday ongeldig (${decision.error}); niet opgeslagen`);
293
+ }
294
+ await autoFillPreferredWarehouse(supabaseAdmin, connectionId).catch(() => null);
298
295
  return name;
299
296
  }
300
297
  catch (e) {
@@ -302,6 +299,113 @@ async function fetchAndStoreFlorydayOrganizationName(supabase, env, connectionId
302
299
  return null;
303
300
  }
304
301
  }
302
+ /** Haalt companyGln van de eigen organisatie op (null als niet beschikbaar). */
303
+ async function fetchOwnCompanyGln(supabase, env) {
304
+ try {
305
+ const tsRes = await florydayFetch(supabase, env, `/${SUPPLIERS_API_VERSION}/trade-settings/supplier/current-organization`);
306
+ if (!tsRes.ok)
307
+ return null;
308
+ const orgId = (await tsRes.json().catch(() => null))?.supplierOrganizationId;
309
+ if (!orgId)
310
+ return null;
311
+ const orgRes = await florydayFetch(supabase, env, `/${SUPPLIERS_API_VERSION}/organizations/${orgId}`);
312
+ if (!orgRes.ok)
313
+ return null;
314
+ return (await orgRes.json().catch(() => null))?.companyGln ?? null;
315
+ }
316
+ catch {
317
+ return null;
318
+ }
319
+ }
320
+ /** Eigen magazijnen van een koppeling uit de cache (op id). */
321
+ export async function loadOwnWarehousesFromCache(admin, connectionId) {
322
+ const { data } = await admin
323
+ .from("floriday_warehouses_cache")
324
+ .select("floriday_id, name, gln_code, data")
325
+ .eq("connection_id", connectionId)
326
+ .limit(500);
327
+ return (data ?? [])
328
+ .map((r) => ({
329
+ id: String(r.floriday_id ?? r?.data?.warehouseId ?? ""),
330
+ name: warehouseLabel(r),
331
+ gln: r.gln_code ?? r?.data?.location?.gln ?? null,
332
+ isDefault: r?.data?.isDefault === true,
333
+ isDeleted: r?.data?.isDeleted === true,
334
+ isExternal: r?.data?.isExternal === true,
335
+ hasAccess: r?.data?.hasAccess !== false,
336
+ }))
337
+ .filter((w) => w.id && isOwnWarehouse(w));
338
+ }
339
+ /**
340
+ * Vult preferred_warehouse_id alleen als die leeg is: bij precies één eigen
341
+ * magazijn, of bij meerdere met precies één Floriday-standaardmagazijn.
342
+ * Altijd op id; bestaande keuze wordt nooit overschreven.
343
+ */
344
+ export async function autoFillPreferredWarehouse(admin, connectionId) {
345
+ const { data: conn } = await admin
346
+ .from("floriday_connections")
347
+ .select("preferred_warehouse_id")
348
+ .eq("id", connectionId)
349
+ .maybeSingle();
350
+ const own = await loadOwnWarehousesFromCache(admin, connectionId);
351
+ const decision = decidePreferredWarehouse({ currentId: conn?.preferred_warehouse_id ?? null, warehouses: own });
352
+ if (decision.kind === "fill") {
353
+ const chosen = own.find((w) => w.id === decision.id);
354
+ const { data: updated } = await admin
355
+ .from("floriday_connections")
356
+ .update({ preferred_warehouse_id: decision.id, preferred_warehouse_name: chosen?.name ?? null })
357
+ .eq("id", connectionId)
358
+ .is("preferred_warehouse_id", null)
359
+ .select("id");
360
+ if (Array.isArray(updated) && updated.length > 0) {
361
+ console.log(`[floriday] voorkeursmagazijn automatisch ingevuld (${decision.reason}) voor connection ${connectionId}`);
362
+ }
363
+ }
364
+ return decision;
365
+ }
366
+ export const getFlorydayOwnConfigStatus = createServerFn({ method: "POST" })
367
+ .middleware([requireSupabaseAuth])
368
+ .inputValidator((input) => {
369
+ if (!["staging", "live"].includes(input.environment))
370
+ throw new Error("Ongeldige environment");
371
+ return input;
372
+ })
373
+ .handler(async ({ data, context }) => {
374
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
375
+ const row = await getActiveFlorydayCredentials(context.supabase, data.environment);
376
+ if (!row) {
377
+ return { connected: false, gln: null, storedGln: null, warehouse: null, preferredWarehouseId: null, preferredWarehouseName: null, ownWarehouses: [] };
378
+ }
379
+ // GLN: lezen uit Floriday, alleen een leeg veld vullen.
380
+ const remote = await fetchOwnCompanyGln(context.supabase, data.environment);
381
+ const { data: cur } = await supabaseAdmin
382
+ .from("floriday_connections")
383
+ .select("gln_code")
384
+ .eq("id", row.id)
385
+ .maybeSingle();
386
+ const gln = decideGln({ stored: cur?.gln_code ?? null, fromFloriday: remote });
387
+ if (gln.kind === "fill") {
388
+ await supabaseAdmin.from("floriday_connections").update({ gln_code: gln.gln }).eq("id", row.id).is("gln_code", null);
389
+ }
390
+ const warehouse = await autoFillPreferredWarehouse(supabaseAdmin, row.id);
391
+ const { data: after } = await supabaseAdmin
392
+ .from("floriday_connections")
393
+ .select("gln_code, preferred_warehouse_id, preferred_warehouse_name")
394
+ .eq("id", row.id)
395
+ .maybeSingle();
396
+ const own = await loadOwnWarehousesFromCache(supabaseAdmin, row.id);
397
+ return {
398
+ connected: true,
399
+ gln,
400
+ storedGln: after?.gln_code ?? null,
401
+ warehouse,
402
+ preferredWarehouseId: after?.preferred_warehouse_id ?? null,
403
+ preferredWarehouseName: after?.preferred_warehouse_name ?? null,
404
+ ownWarehouses: own
405
+ .map((w) => ({ id: w.id, name: w.name, gln: w.gln, isDefault: w.isDefault }))
406
+ .sort((a, b) => (a.isDefault !== b.isDefault ? (a.isDefault ? -1 : 1) : a.name.localeCompare(b.name, "nl"))),
407
+ };
408
+ });
305
409
  export async function getActiveFlorydayEnvironment(supabase) {
306
410
  const { data } = await supabase
307
411
  .from("floriday_settings")
@@ -1097,8 +1201,7 @@ export const listFlorydayWarehouses = createServerFn({ method: "POST" })
1097
1201
  const json = await res.json().catch(() => null);
1098
1202
  const raw = Array.isArray(json) ? json : [];
1099
1203
  const warehouses = raw
1100
- .filter((w) => w?.isDeleted !== true &&
1101
- w?.hasAccess !== false &&
1204
+ .filter((w) => isOwnWarehouse(w ?? {}) &&
1102
1205
  Boolean(String(w?.name ?? "").trim() ||
1103
1206
  String(w?.location?.address?.addressLine ?? "").trim() ||
1104
1207
  String(w?.location?.gln ?? w?.location?.glnCode ?? "").trim()))
@@ -1131,25 +1234,26 @@ export const updateFlorydayPublishingSettings = createServerFn({ method: "POST"
1131
1234
  if (!["staging", "live"].includes(input.environment)) {
1132
1235
  throw new Error("Ongeldige environment");
1133
1236
  }
1134
- const trimmed = input.gln_code?.trim();
1135
- if (trimmed) {
1136
- const err = validateGln(trimmed);
1137
- if (err)
1138
- throw new Error(err);
1139
- }
1140
- return { ...input, gln_code: trimmed || null };
1237
+ const id = String(input.preferred_warehouse_id ?? "").trim();
1238
+ if (!id)
1239
+ throw new Error("Kies een magazijn");
1240
+ // GLN wordt niet meer aangenomen: die komt altijd uit Floriday.
1241
+ return { environment: input.environment, preferred_warehouse_id: id };
1141
1242
  })
1142
1243
  .handler(async ({ data, context }) => {
1143
1244
  const { supabaseAdmin } = await import("../integrations/supabase/client.server.js");
1144
1245
  const row = await getActiveFlorydayCredentials(context.supabase, data.environment);
1145
1246
  if (!row)
1146
1247
  throw new Error(`Geen actieve Floriday-koppeling voor ${data.environment}`);
1248
+ const own = await loadOwnWarehousesFromCache(supabaseAdmin, row.id);
1249
+ const chosen = own.find((w) => w.id === data.preferred_warehouse_id);
1250
+ if (!chosen)
1251
+ throw new Error("Dit magazijn is geen eigen magazijn van deze koppeling");
1147
1252
  const { error } = await supabaseAdmin
1148
1253
  .from("floriday_connections")
1149
1254
  .update({
1150
- gln_code: data.gln_code?.trim() || null,
1151
- preferred_warehouse_id: data.preferred_warehouse_id || null,
1152
- preferred_warehouse_name: data.preferred_warehouse_name || null,
1255
+ preferred_warehouse_id: chosen.id,
1256
+ preferred_warehouse_name: chosen.name,
1153
1257
  })
1154
1258
  .eq("id", row.id);
1155
1259
  if (error)