@flowselections/floriday-authenticatie-module 1.0.69 → 1.0.72

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/integrations/supabase/types.d.ts +151 -0
  2. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  3. package/dist-lib/lib/floriday-order-stage.d.ts +13 -2
  4. package/dist-lib/lib/floriday-order-stage.d.ts.map +1 -1
  5. package/dist-lib/lib/floriday-order-stage.js +16 -3
  6. package/dist-lib/lib/floriday-sync.functions.d.ts +64 -0
  7. package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -1
  8. package/dist-lib/lib/floriday-sync.functions.js +19 -0
  9. package/dist-lib/lib/floriday.functions.d.ts +7 -0
  10. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  11. package/dist-lib/lib/floriday.functions.js +9 -1
  12. package/dist-lib/lib/gateway/fulfillment-order-body.d.ts +3 -0
  13. package/dist-lib/lib/gateway/fulfillment-order-body.d.ts.map +1 -1
  14. package/dist-lib/lib/gateway/fulfillment-order-body.js +32 -6
  15. package/dist-lib/lib/gateway/fulfillment-order-send.d.ts.map +1 -1
  16. package/dist-lib/lib/gateway/fulfillment-order-send.js +7 -0
  17. package/dist-lib/lib/gateway/fulfillment-order-status.d.ts +10 -4
  18. package/dist-lib/lib/gateway/fulfillment-order-status.d.ts.map +1 -1
  19. package/dist-lib/lib/gateway/fulfillment-order-status.js +195 -68
  20. package/dist-lib/lib/gateway/load-carrier-checks.d.ts +54 -0
  21. package/dist-lib/lib/gateway/load-carrier-checks.d.ts.map +1 -0
  22. package/dist-lib/lib/gateway/load-carrier-checks.js +72 -0
  23. package/dist-lib/lib/module-version.d.ts +2 -0
  24. package/dist-lib/lib/module-version.d.ts.map +1 -0
  25. package/dist-lib/lib/module-version.js +3 -0
  26. package/dist-lib/lib/status-overview.d.ts +77 -0
  27. package/dist-lib/lib/status-overview.d.ts.map +1 -0
  28. package/dist-lib/lib/status-overview.js +141 -0
  29. package/package.json +1 -1
@@ -7,6 +7,7 @@
7
7
  // De body wordt volledig opgebouwd uit de delivery-order-cache plus een join
8
8
  // op de sales-order-cache; er wordt niets verzonnen. Ontbrekende gegevens
9
9
  // leiden tot een expliciete fout, nooit tot een stille aanname.
10
+ import { defaultedFieldsWarning, packagesSumWarnings, resolveCartLoadCarrierType, } from "./load-carrier-checks";
10
11
  // ---------------------------------------------------------------------------
11
12
  // fulfillmentOrderId — deterministische UUIDv5
12
13
  // ---------------------------------------------------------------------------
@@ -260,6 +261,7 @@ export async function buildFulfillmentOrderBody(supabase, input) {
260
261
  }
261
262
  const usedRequestIds = [];
262
263
  const loadCarriers = [];
264
+ const loadWarnings = [];
263
265
  const buildItem = (req, override, sortIndex) => {
264
266
  const id = String(req?.fulfillmentRequestId ?? req?.id);
265
267
  const count = override?.numberOfPackages && Number(override.numberOfPackages) > 0
@@ -298,11 +300,13 @@ export async function buildFulfillmentOrderBody(supabase, input) {
298
300
  if (cartCarriers) {
299
301
  // ── Pad 1: onze eigen karrenindeling is leidend ────────────────────────
300
302
  let carrierIndex = 0;
303
+ const packageLines = [];
301
304
  for (const cart of cartCarriers) {
302
305
  carrierIndex += 1;
303
306
  const items = [];
304
307
  let itemIndex = 0;
305
- let typeFromRequests = "";
308
+ const cartRequests = [];
309
+ const defaulted = [];
306
310
  for (const line of cart.items ?? []) {
307
311
  const id = String(line?.fulfillmentRequestId ?? "").trim();
308
312
  if (!id) {
@@ -329,8 +333,11 @@ export async function buildFulfillmentOrderBody(supabase, input) {
329
333
  return { kind: "error", error: built.error, permanent: built.permanent };
330
334
  }
331
335
  items.push(built.item);
332
- if (!typeFromRequests)
333
- typeFromRequests = String(req?.loadCarrier ?? "").trim();
336
+ packageLines.push({ fulfillmentRequestId: id, numberOfPackages: Number(built.item.numberOfPackages) });
337
+ if (!(Number(line?.numberOfPackages) > 0)) {
338
+ defaulted.push({ field: `numberOfPackages[${id}]`, value: built.item.numberOfPackages, source: "fulfillment_request" });
339
+ }
340
+ cartRequests.push({ fulfillmentRequestId: id, loadCarrier: req?.loadCarrier });
334
341
  }
335
342
  if (items.length === 0) {
336
343
  return {
@@ -339,14 +346,29 @@ export async function buildFulfillmentOrderBody(supabase, input) {
339
346
  permanent: true,
340
347
  };
341
348
  }
342
- const loadCarrierType = String(cart.loadCarrierType ?? typeFromRequests).trim();
343
- if (!loadCarrierType || loadCarrierType === "NONE") {
349
+ // Type uit de requests is leidend; Afhandeling alleen als terugval.
350
+ const typed = resolveCartLoadCarrierType(carrierIndex - 1, cart.loadCarrierType, cartRequests);
351
+ const loadCarrierType = typed.loadCarrierType;
352
+ if (!loadCarrierType) {
344
353
  return {
345
354
  kind: "error",
346
355
  error: `Kar ${cart.loadCarrierReference ?? carrierIndex} heeft geen bruikbaar loadCarrierType`,
347
356
  permanent: true,
348
357
  };
349
358
  }
359
+ loadWarnings.push(...typed.warnings);
360
+ if (typed.source === "fulfillment_request" && !String(cart.loadCarrierType ?? "").trim()) {
361
+ defaulted.push({ field: "loadCarrierType", value: loadCarrierType, source: "fulfillment_request" });
362
+ }
363
+ const layersGiven = cart.numberOfAdditionalLayers !== undefined && cart.numberOfAdditionalLayers !== null;
364
+ if (!layersGiven)
365
+ defaulted.push({ field: "numberOfAdditionalLayers", value: 0, source: "default" });
366
+ if (cart.sortIndex === undefined || cart.sortIndex === null) {
367
+ defaulted.push({ field: "sortIndex", value: carrierIndex, source: "default" });
368
+ }
369
+ const dw = defaultedFieldsWarning(carrierIndex - 1, defaulted);
370
+ if (dw)
371
+ loadWarnings.push(dw);
350
372
  const carrier = {
351
373
  sortIndex: cart.sortIndex ?? carrierIndex,
352
374
  loadCarrierType,
@@ -359,6 +381,10 @@ export async function buildFulfillmentOrderBody(supabase, input) {
359
381
  carrier.loadCarrierBarcode = cart.loadCarrierBarcode;
360
382
  loadCarriers.push(carrier);
361
383
  }
384
+ const expected = new Map();
385
+ for (const l of packageLines)
386
+ expected.set(l.fulfillmentRequestId, resolvePackageCount(byRequestId.get(l.fulfillmentRequestId)));
387
+ loadWarnings.push(...packagesSumWarnings(packageLines, expected));
362
388
  }
363
389
  else {
364
390
  // ── Pad 2 (fallback): groeperen per loadCarrier-waarde van de requests ──
@@ -411,5 +437,5 @@ export async function buildFulfillmentOrderBody(supabase, input) {
411
437
  const gln = input.deliveryLocationGln ?? doData?.destination?.location?.gln ?? null;
412
438
  if (gln)
413
439
  body.deliveryLocationGln = gln;
414
- return { kind: "body", body, fulfillmentOrderId, usedRequestIds, skipped };
440
+ return { kind: "body", body, fulfillmentOrderId, usedRequestIds, skipped, loadWarnings };
415
441
  }
@@ -1 +1 @@
1
- {"version":3,"file":"fulfillment-order-send.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/fulfillment-order-send.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAyB5D,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,0BAA0B,CAAC,CAmOrC;AAeD,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CAmCnB"}
1
+ {"version":3,"file":"fulfillment-order-send.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/fulfillment-order-send.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAyB5D,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,GAAG,EACT,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,0BAA0B,CAAC,CA0OrC;AAeD,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CAmCnB"}
@@ -156,6 +156,13 @@ export async function runFulfillmentOrderSend(supabase, body, caller) {
156
156
  }
157
157
  writeBody = built.body;
158
158
  skipped = built.skipped;
159
+ if (hasCartLoad) {
160
+ warnings.push(...built.loadWarnings);
161
+ if (typeof body?.oneLabelOnly !== "boolean") {
162
+ warnings.push({ code: "defaulted_fields", severity: "warning", fields: [{ field: "oneLabelOnly", value: false, source: "default" }],
163
+ message: "oneLabelOnly niet meegegeven; door de module ingevuld als false (default)." });
164
+ }
165
+ }
159
166
  }
160
167
  // Laatste poort: zijn alle karren van de order afgemeld aan de controlebalie?
161
168
  const approval = await checkCartApproval(supabase, {
@@ -1,17 +1,23 @@
1
1
  import type { SupabaseClient } from "@supabase/supabase-js";
2
+ import { type RefreshDeps, type RefreshMode } from "../status-overview.js";
2
3
  export interface FulfillmentOrderStatusResult {
3
4
  status: number;
4
5
  payload: unknown;
5
6
  }
6
7
  /**
7
- * Statusoverzicht per verkooporder, alleen uit de caches (geen Floriday-
8
- * aanroep): orderstatus, uitleveringen, fulfillmentRequestIds, floridayStage
9
- * en de ordervelden. Een order die niet in de cache staat krijgt stage
10
- * "unknown" — geen fout. Wijzigt nooit een orderstatus.
8
+ * Statusoverzicht per verkooporder. Leest de lokaal gesynchroniseerde
9
+ * orders, afleveropdrachten en uitleveringen per tabel in één keer. Alleen
10
+ * als een bron verouderd is of een order ontbreekt, volgt één begrensde
11
+ * inhaalronde op de bestaande sequence-sync (nooit een aanroep per order).
12
+ * Onbekende orders geven found:false; nooit een fout voor de hele lijst.
13
+ * Wijzigt nooit een orderstatus.
11
14
  */
12
15
  export declare function runSalesOrderStatusOverview(supabase: SupabaseClient, input: {
13
16
  salesOrderIds: string[];
17
+ fulfillmentOrderIds?: string[] | null;
18
+ refresh?: RefreshMode;
14
19
  environment?: "staging" | "live";
20
+ deps?: Partial<RefreshDeps>;
15
21
  }): Promise<FulfillmentOrderStatusResult>;
16
22
  export declare function runFulfillmentOrderStatus(supabase: SupabaseClient, input: {
17
23
  fulfillmentOrderId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"fulfillment-order-status.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/fulfillment-order-status.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAK5D,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAID;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE;IAAE,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;CAAE,GACnE,OAAO,CAAC,4BAA4B,CAAC,CAuFvC;AAED,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE;IAAE,kBAAkB,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;CAAE,GACtE,OAAO,CAAC,4BAA4B,CAAC,CA6CvC;AAcD,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CA4CnB"}
1
+ {"version":3,"file":"fulfillment-order-status.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/fulfillment-order-status.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,OAAO,EAKL,KAAK,WAAW,EAChB,KAAK,WAAW,EAGjB,MAAM,uBAAuB,CAAC;AAI/B,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AA2DD;;;;;;;GAOG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE;IACL,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IACtC,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,WAAW,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;CAC7B,GACA,OAAO,CAAC,4BAA4B,CAAC,CA4IvC;AAED,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE;IAAE,kBAAkB,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,GAAG,MAAM,CAAA;CAAE,GACtE,OAAO,CAAC,4BAA4B,CAAC,CA8CvC;AAmBD,wBAAsB,4BAA4B,CAChD,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CA8CnB"}
@@ -7,57 +7,157 @@
7
7
  import { resolveGatewayEnvironment } from "./shared.js";
8
8
  import { SUPPLIERS_API_VERSION, florydayFetch, getActiveFlorydayCredentials } from "../floriday.functions.js";
9
9
  import { deriveFloridayStage, isDeliveryNoteReady, readIncoterm } from "../floriday-order-stage.js";
10
- const MAX_SALES_ORDER_IDS = 100;
10
+ import { MAX_SALES_ORDER_IDS, REFRESH_LIMITS, parseStatusRequest, refreshSource, } from "../status-overview.js";
11
+ import { tryAcquireJobLock, releaseJobLock } from "../cron/job-lock.js";
12
+ import { MODULE_VERSION } from "../module-version.js";
13
+ /** Brokken van max 20 id's voor JSON-contains-filters; max 6 tegelijk (Worker-limiet). */
14
+ async function inChunks(items, size, fn) {
15
+ const chunks = [];
16
+ for (let i = 0; i < items.length; i += size)
17
+ chunks.push(items.slice(i, i + size));
18
+ const out = [];
19
+ for (let i = 0; i < chunks.length; i += 6) {
20
+ const res = await Promise.all(chunks.slice(i, i + 6).map(fn));
21
+ for (const r of res)
22
+ out.push(...r);
23
+ }
24
+ return out;
25
+ }
26
+ async function readLocal(db, connId, ids) {
27
+ const soRows = await inChunks(ids, 100, async (c) => {
28
+ const { data, error } = await db.from("floriday_salesorders_cache")
29
+ .select("floriday_id, status, buyer_organization_id, data, updated_at")
30
+ .eq("connection_id", connId).in("floriday_id", c);
31
+ if (error)
32
+ throw new Error(error.message);
33
+ return data ?? [];
34
+ });
35
+ const doRows = await inChunks(ids, 20, async (c) => {
36
+ const or = c.map((sid) => `data->fulfillmentRequests.cs.${JSON.stringify([{ salesOrderId: sid }])}`).join(",");
37
+ const { data, error } = await db.from("floriday_delivery_orders_cache")
38
+ .select("floriday_id, data, updated_at").eq("connection_id", connId).or(or);
39
+ if (error)
40
+ throw new Error(error.message);
41
+ return data ?? [];
42
+ });
43
+ const requests = new Map();
44
+ const wanted = new Set(ids);
45
+ const seenReq = new Set();
46
+ for (const d of doRows) {
47
+ for (const r of (d.data?.fulfillmentRequests ?? [])) {
48
+ const sid = String(r?.salesOrderId ?? "");
49
+ if (!wanted.has(sid) || !r?.fulfillmentRequestId)
50
+ continue;
51
+ const key = `${sid}|${r.fulfillmentRequestId}`;
52
+ if (seenReq.has(key))
53
+ continue;
54
+ seenReq.add(key);
55
+ const list = requests.get(sid) ?? [];
56
+ list.push({
57
+ fulfillmentRequestId: String(r.fulfillmentRequestId),
58
+ deliveryOrderId: String(d.floriday_id),
59
+ fulfillmentOrderIds: Array.from(new Set((r.fulfillmentOrderClaims ?? [])
60
+ .map((c) => c?.fulfillmentOrderId).filter(Boolean).map(String))),
61
+ });
62
+ requests.set(sid, list);
63
+ }
64
+ }
65
+ const foIds = Array.from(new Set(Array.from(requests.values()).flat().flatMap((r) => r.fulfillmentOrderIds)));
66
+ const foRows = foIds.length ? await inChunks(foIds, 100, async (c) => {
67
+ const { data, error } = await db.from("floriday_fulfillment_orders_cache")
68
+ .select("floriday_id, status, data, updated_at").eq("connection_id", connId).in("floriday_id", c);
69
+ if (error)
70
+ throw new Error(error.message);
71
+ return data ?? [];
72
+ }) : [];
73
+ return { soRows, requests, foRows };
74
+ }
11
75
  /**
12
- * Statusoverzicht per verkooporder, alleen uit de caches (geen Floriday-
13
- * aanroep): orderstatus, uitleveringen, fulfillmentRequestIds, floridayStage
14
- * en de ordervelden. Een order die niet in de cache staat krijgt stage
15
- * "unknown" — geen fout. Wijzigt nooit een orderstatus.
76
+ * Statusoverzicht per verkooporder. Leest de lokaal gesynchroniseerde
77
+ * orders, afleveropdrachten en uitleveringen per tabel in één keer. Alleen
78
+ * als een bron verouderd is of een order ontbreekt, volgt één begrensde
79
+ * inhaalronde op de bestaande sequence-sync (nooit een aanroep per order).
80
+ * Onbekende orders geven found:false; nooit een fout voor de hele lijst.
81
+ * Wijzigt nooit een orderstatus.
16
82
  */
17
83
  export async function runSalesOrderStatusOverview(supabase, input) {
18
84
  const ids = Array.from(new Set(input.salesOrderIds.map((s) => String(s).trim()).filter(Boolean)));
19
85
  if (ids.length === 0 || ids.length > MAX_SALES_ORDER_IDS) {
20
- return { status: 400, payload: { ok: false, error: `salesOrderIds: 1 tot ${MAX_SALES_ORDER_IDS} id's` } };
86
+ return { status: 400, payload: { ok: false, code: "invalid_request", errors: [`salesOrderIds: 1 tot ${MAX_SALES_ORDER_IDS} id's`] } };
87
+ }
88
+ let env;
89
+ let conn;
90
+ try {
91
+ env = await resolveGatewayEnvironment(supabase, input?.environment);
92
+ conn = await getActiveFlorydayCredentials(supabase, env);
93
+ }
94
+ catch {
95
+ conn = null;
21
96
  }
22
- const env = await resolveGatewayEnvironment(supabase, input?.environment);
23
- const conn = await getActiveFlorydayCredentials(supabase, env);
24
97
  if (!conn)
25
- return { status: 409, payload: { ok: false, error: `Geen actieve Floriday-koppeling voor ${env}` } };
98
+ return { status: 409, payload: { ok: false, code: "no_active_connection", error: "Geen actieve Floriday-koppeling" } };
26
99
  const db = supabase;
27
- const { data: soRows } = await db.from("floriday_salesorders_cache")
28
- .select("floriday_id, status, buyer_organization_id, data, updated_at")
29
- .eq("connection_id", conn.id).in("floriday_id", ids);
30
- const soById = new Map((soRows ?? []).map((r) => [String(r.floriday_id), r]));
31
- // fulfillmentRequests per verkooporder uit de afleveropdrachten.
32
- const requests = new Map();
33
- await Promise.all(ids.map(async (sid) => {
34
- const { data } = await db.from("floriday_delivery_orders_cache")
35
- .select("floriday_id, data").eq("connection_id", conn.id)
36
- .filter("data->fulfillmentRequests", "cs", JSON.stringify([{ salesOrderId: sid }])).limit(20);
37
- const list = [];
38
- for (const d of data ?? []) {
39
- for (const r of (d.data?.fulfillmentRequests ?? [])) {
40
- if (String(r?.salesOrderId) !== sid || !r?.fulfillmentRequestId)
41
- continue;
42
- list.push({
43
- fulfillmentRequestId: String(r.fulfillmentRequestId),
44
- deliveryOrderId: String(d.floriday_id),
45
- fulfillmentOrderIds: Array.from(new Set((r.fulfillmentOrderClaims ?? [])
46
- .map((c) => c?.fulfillmentOrderId).filter(Boolean).map(String))),
47
- });
100
+ let local;
101
+ try {
102
+ local = await readLocal(db, conn.id, ids);
103
+ }
104
+ catch (e) {
105
+ return { status: 502, payload: { ok: false, code: "floriday_unavailable", error: `Lokale gegevens niet leesbaar: ${String(e?.message ?? e)}` } };
106
+ }
107
+ // Inhaalronde (alleen bij mode auto).
108
+ const mode = input.refresh ?? "auto";
109
+ const reports = [];
110
+ const beforeIds = new Set(local.soRows.map((r) => String(r.floriday_id)));
111
+ if (mode === "never") {
112
+ for (const source of ["sales-orders", "delivery-orders", "fulfillment-orders"]) {
113
+ reports.push({ source, attempted: false, skippedReason: "disabled", timedOut: false, durationMs: 0, synced: 0, error: null });
114
+ }
115
+ }
116
+ else {
117
+ const deps = {
118
+ now: () => Date.now(),
119
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
120
+ readState: async (source) => {
121
+ const { data } = await db.from("floriday_sync_state")
122
+ .select("last_synced_at, last_success_at").eq("connection_id", conn.id).eq("entity_type", source).maybeSingle();
123
+ const t = (v) => (v ? Date.parse(v) : null);
124
+ return { lastSyncedAt: t(data?.last_synced_at), lastSuccessAt: t(data?.last_success_at) };
125
+ },
126
+ // Zelfde lock-sleutel als de cron: cron en statusoverzicht draaien nooit tegelijk.
127
+ tryLock: async (source) => (await tryAcquireJobLock(supabase, `floriday-sync:${source}`, REFRESH_LIMITS.lockTtlSeconds)).acquired,
128
+ unlock: async (source) => releaseJobLock(supabase, `floriday-sync:${source}`),
129
+ runSync: async (source, deadlineMs) => {
130
+ const { runBoundedSequenceSync } = await import("../floriday-sync.functions.js");
131
+ return runBoundedSequenceSync(supabase, conn.id, env, source, { maxBatches: REFRESH_LIMITS.maxBatches, deadlineMs });
132
+ },
133
+ ...(input.deps ?? {}),
134
+ };
135
+ const deadline = deps.now() + REFRESH_LIMITS.timeoutMs;
136
+ const missingSo = ids.some((sid) => !beforeIds.has(sid));
137
+ const missingReq = ids.some((sid) => beforeIds.has(sid) && !(local.requests.get(sid)?.length));
138
+ const claimedFo = new Set(Array.from(local.requests.values()).flat().flatMap((r) => r.fulfillmentOrderIds));
139
+ const missingFo = Array.from(claimedFo).some((id) => !local.foRows.some((f) => String(f.floriday_id) === id));
140
+ const needed = {
141
+ "sales-orders": missingSo,
142
+ "delivery-orders": missingReq,
143
+ "fulfillment-orders": missingFo,
144
+ };
145
+ // Bewust na elkaar: elke bron gebruikt de tokenemmer en telt mee in de 6 gelijktijdige aanroepen.
146
+ for (const source of ["sales-orders", "delivery-orders", "fulfillment-orders"]) {
147
+ reports.push(await refreshSource(deps, source, needed[source], deadline));
148
+ }
149
+ if (reports.some((r) => r.attempted || (r.skippedReason === "in_progress" && !r.timedOut))) {
150
+ try {
151
+ local = await readLocal(db, conn.id, ids);
48
152
  }
153
+ catch { /* lokale stand van vóór de ronde blijft */ }
49
154
  }
50
- requests.set(sid, list);
51
- }));
52
- const foIds = Array.from(new Set(Array.from(requests.values()).flat().flatMap((r) => r.fulfillmentOrderIds)));
53
- const foStatus = new Map();
54
- if (foIds.length) {
55
- const { data } = await db.from("floriday_fulfillment_orders_cache")
56
- .select("floriday_id, status, data").eq("connection_id", conn.id).in("floriday_id", foIds);
57
- for (const f of data ?? [])
58
- foStatus.set(String(f.floriday_id), f.status ?? f.data?.status ?? null);
59
- }
60
- const buyerIds = Array.from(new Set((soRows ?? [])
155
+ }
156
+ const { soRows, requests, foRows } = local;
157
+ const soById = new Map(soRows.map((r) => [String(r.floriday_id), r]));
158
+ const foById = new Map(foRows.map((f) => [String(f.floriday_id), f]));
159
+ const foFilter = input.fulfillmentOrderIds?.length ? new Set(input.fulfillmentOrderIds) : null;
160
+ const buyerIds = Array.from(new Set(soRows
61
161
  .map((r) => r.data?.customerOrganizationId ?? r.buyer_organization_id).filter(Boolean).map(String)));
62
162
  const glnById = new Map();
63
163
  if (buyerIds.length) {
@@ -68,25 +168,35 @@ export async function runSalesOrderStatusOverview(supabase, input) {
68
168
  }
69
169
  const orders = ids.map((sid) => {
70
170
  const so = soById.get(sid);
71
- const reqs = requests.get(sid) ?? [];
171
+ let reqs = requests.get(sid) ?? [];
172
+ if (foFilter) {
173
+ reqs = reqs.map((r) => ({ ...r, fulfillmentOrderIds: r.fulfillmentOrderIds.filter((id) => foFilter.has(id)) }));
174
+ }
175
+ const statusOf = (id) => {
176
+ const f = foById.get(id);
177
+ return f ? (f.status ?? f.data?.status ?? null) : null;
178
+ };
72
179
  const fulfillmentOrders = Array.from(new Set(reqs.flatMap((r) => r.fulfillmentOrderIds)))
73
180
  .map((id) => {
74
- const status = foStatus.get(id) ?? null;
181
+ const status = statusOf(id);
75
182
  return { fulfillmentOrderId: id, status, deliveryNoteReady: isDeliveryNoteReady(status) };
76
183
  });
77
184
  if (!so) {
78
- return { salesOrderId: sid, found: false, floridayStage: "unknown",
79
- fulfillmentRequests: reqs, fulfillmentOrders };
185
+ return { salesOrderId: sid, found: false, salesOrderStatus: null, floridayStage: "unknown",
186
+ fulfillmentRequests: reqs, fulfillmentOrders, source: null, syncedAt: null };
80
187
  }
81
188
  const d = so.data ?? {};
82
189
  const buyer = d.customerOrganizationId ?? so.buyer_organization_id ?? null;
190
+ const status = so.status ?? d.status ?? null;
83
191
  return {
84
192
  salesOrderId: sid,
85
193
  found: true,
86
- salesOrderStatus: so.status ?? d.status ?? null,
87
- floridayStage: deriveFloridayStage(so.status ?? d.status, fulfillmentOrders.map((f) => f.status)),
194
+ salesOrderStatus: status,
195
+ floridayStage: deriveFloridayStage(status, reqs.map((r) => r.fulfillmentOrderIds.map(statusOf))),
88
196
  fulfillmentRequests: reqs,
89
197
  fulfillmentOrders,
198
+ source: beforeIds.has(sid) ? "local" : "live",
199
+ syncedAt: so.updated_at ?? null,
90
200
  salesChannel: d.salesChannel ?? null,
91
201
  tradeInstrument: d.tradeInstrument ?? null,
92
202
  incoterm: readIncoterm(d),
@@ -98,14 +208,27 @@ export async function runSalesOrderStatusOverview(supabase, input) {
98
208
  cachedAt: so.updated_at ?? null,
99
209
  };
100
210
  });
101
- return { status: 200, payload: { ok: true, environment: env, orders } };
211
+ return {
212
+ status: 200,
213
+ payload: {
214
+ ok: true,
215
+ environment: env,
216
+ refresh: {
217
+ mode,
218
+ timedOut: reports.some((r) => r.timedOut),
219
+ error: reports.find((r) => r.error)?.error ?? null,
220
+ sources: reports,
221
+ },
222
+ orders,
223
+ },
224
+ };
102
225
  }
103
226
  export async function runFulfillmentOrderStatus(supabase, input) {
104
227
  const fulfillmentOrderId = String(input?.fulfillmentOrderId ?? "").trim();
105
228
  if (!fulfillmentOrderId) {
106
229
  return {
107
230
  status: 400,
108
- payload: { ok: false, error: "fulfillmentOrderId is verplicht" },
231
+ payload: { ok: false, code: "invalid_request", error: "fulfillmentOrderId is verplicht", errors: ["fulfillmentOrderId is verplicht"] },
109
232
  };
110
233
  }
111
234
  const env = await resolveGatewayEnvironment(supabase, input?.environment);
@@ -124,6 +247,7 @@ export async function runFulfillmentOrderStatus(supabase, input) {
124
247
  status: res.status === 404 ? 404 : 502,
125
248
  payload: {
126
249
  ok: false,
250
+ code: res.status === 404 ? "not_found" : "floriday_unavailable",
127
251
  error: `Floriday fulfillment-order status ${res.status}`,
128
252
  detail: data,
129
253
  },
@@ -145,46 +269,49 @@ export async function runFulfillmentOrderStatus(supabase, input) {
145
269
  // HTTP-handler
146
270
  // ---------------------------------------------------------------------------
147
271
  import { verifyGatewayCaller, corsPreflight, withCors, } from "../floriday-gateway-auth.js";
148
- const _fosStatusJson = (data, init) => withCors(Response.json(data, init));
272
+ const _fosStatusJson = (data, init) => withCors(Response.json(data && typeof data === "object" && !Array.isArray(data)
273
+ ? { ...data, moduleVersion: MODULE_VERSION }
274
+ : { data, moduleVersion: MODULE_VERSION }, init));
149
275
  export async function handleFulfillmentOrderStatus(request, supabase) {
150
276
  if (request.method === "OPTIONS")
151
277
  return corsPreflight();
152
278
  const auth = await verifyGatewayCaller(request, supabase);
153
279
  if (!auth.ok) {
154
- return _fosStatusJson({ ok: false, error: auth.error }, { status: auth.status });
280
+ return _fosStatusJson({ ok: false, code: auth.status === 401 ? "invalid_signature" : "gateway_error", error: auth.error }, { status: auth.status });
155
281
  }
156
282
  let body;
157
283
  try {
158
284
  body = JSON.parse(auth.rawBody || "{}");
159
285
  }
160
286
  catch {
161
- return _fosStatusJson({ ok: false, error: "Ongeldige JSON-body" }, { status: 400 });
287
+ return _fosStatusJson({ ok: false, code: "invalid_request", error: "Ongeldige JSON-body", errors: ["Ongeldige JSON-body"] }, { status: 400 });
162
288
  }
163
- if (body?.environment !== undefined &&
164
- body.environment !== "staging" &&
165
- body.environment !== "live") {
166
- return _fosStatusJson({ ok: false, error: "Ongeldige environment" }, { status: 400 });
289
+ const parsed = parseStatusRequest(body);
290
+ if (!parsed.ok) {
291
+ return _fosStatusJson({ ok: false, code: "invalid_request", error: parsed.errors.join("; "), errors: parsed.errors }, { status: 400 });
167
292
  }
168
- if (Array.isArray(body?.salesOrderIds)) {
169
- try {
293
+ const req = parsed.value;
294
+ try {
295
+ if (req.kind === "overview") {
170
296
  const { status, payload } = await runSalesOrderStatusOverview(supabase, {
171
- salesOrderIds: body.salesOrderIds,
172
- environment: body?.environment,
297
+ salesOrderIds: req.salesOrderIds,
298
+ fulfillmentOrderIds: req.fulfillmentOrderIds,
299
+ refresh: req.refresh,
300
+ environment: req.environment,
173
301
  });
174
302
  return _fosStatusJson(payload, { status });
175
303
  }
176
- catch (e) {
177
- return _fosStatusJson({ ok: false, error: String(e?.message ?? e) }, { status: 500 });
178
- }
179
- }
180
- try {
181
304
  const { status, payload } = await runFulfillmentOrderStatus(supabase, {
182
- fulfillmentOrderId: String(body?.fulfillmentOrderId ?? ""),
183
- environment: body?.environment,
305
+ fulfillmentOrderId: req.fulfillmentOrderId,
306
+ environment: req.environment,
184
307
  });
185
308
  return _fosStatusJson(payload, { status });
186
309
  }
187
310
  catch (e) {
188
- return _fosStatusJson({ ok: false, error: String(e?.message ?? e) }, { status: 500 });
311
+ const msg = String(e?.message ?? e);
312
+ if (/actieve Floriday-koppeling/i.test(msg)) {
313
+ return _fosStatusJson({ ok: false, code: "no_active_connection", error: msg }, { status: 409 });
314
+ }
315
+ return _fosStatusJson({ ok: false, code: "floriday_unavailable", error: msg }, { status: 502 });
189
316
  }
190
317
  }
@@ -0,0 +1,54 @@
1
+ export type LoadFieldWarning = {
2
+ code: "defaulted_fields";
3
+ severity: "warning";
4
+ cartIndex: number;
5
+ fields: Array<{
6
+ field: string;
7
+ value: unknown;
8
+ source: "fulfillment_request" | "default";
9
+ }>;
10
+ message: string;
11
+ } | {
12
+ code: "load_carrier_type_mismatch";
13
+ severity: "warning";
14
+ cartIndex: number;
15
+ sent: string;
16
+ fromAfhandeling: string;
17
+ typesByRequest: Array<{
18
+ fulfillmentRequestId: string;
19
+ loadCarrierType: string;
20
+ }>;
21
+ message: string;
22
+ } | {
23
+ code: "packages_sum_mismatch";
24
+ severity: "warning";
25
+ fulfillmentRequestId: string;
26
+ sum: number;
27
+ expected: number;
28
+ message: string;
29
+ };
30
+ /**
31
+ * Bepaalt het loadCarrierType van één kar. Het type uit de requests is leidend
32
+ * (eerste request met een bruikbaar type); alleen als geen enkel request er een
33
+ * heeft, valt het terug op de waarde van Afhandeling.
34
+ */
35
+ export declare function resolveCartLoadCarrierType(cartIndex: number, afhandelingType: unknown, requests: Array<{
36
+ fulfillmentRequestId: string;
37
+ loadCarrier: unknown;
38
+ }>): {
39
+ loadCarrierType: string;
40
+ source: "fulfillment_request" | "afhandeling" | "none";
41
+ warnings: LoadFieldWarning[];
42
+ };
43
+ /** Velden die de module zelf invulde voor één kar. */
44
+ export declare function defaultedFieldsWarning(cartIndex: number, fields: Array<{
45
+ field: string;
46
+ value: unknown;
47
+ source: "fulfillment_request" | "default";
48
+ }>): LoadFieldWarning | null;
49
+ /** Som van numberOfPackages per request over alle karren vergeleken met het request. */
50
+ export declare function packagesSumWarnings(lines: Array<{
51
+ fulfillmentRequestId: string;
52
+ numberOfPackages: number;
53
+ }>, expectedById: Map<string, number | null>): LoadFieldWarning[];
54
+ //# sourceMappingURL=load-carrier-checks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-carrier-checks.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/load-carrier-checks.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,gBAAgB,GACxB;IACE,IAAI,EAAE,kBAAkB,CAAC;IACzB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,qBAAqB,GAAG,SAAS,CAAA;KAAE,CAAC,CAAC;IAC5F,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,4BAA4B,CAAC;IACnC,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,KAAK,CAAC;QAAE,oBAAoB,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjF,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,IAAI,EAAE,uBAAuB,CAAC;IAC9B,QAAQ,EAAE,SAAS,CAAC;IACpB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAON;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,MAAM,EACjB,eAAe,EAAE,OAAO,EACxB,QAAQ,EAAE,KAAK,CAAC;IAAE,oBAAoB,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,GACtE;IAAE,eAAe,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,qBAAqB,GAAG,aAAa,GAAG,MAAM,CAAC;IAAC,QAAQ,EAAE,gBAAgB,EAAE,CAAA;CAAE,CA2BnH;AAED,sDAAsD;AACtD,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,qBAAqB,GAAG,SAAS,CAAA;CAAE,CAAC,GAC1F,gBAAgB,GAAG,IAAI,CASzB;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,CAAC;IAAE,oBAAoB,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,CAAC,EACxE,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GACvC,gBAAgB,EAAE,CAiBpB"}
@@ -0,0 +1,72 @@
1
+ // Pure afleidregels en waarschuwingen voor de kar-velden van
2
+ // FulfillmentOrderCreate (Suppliers API 2026v1). Geen I/O; getest in
3
+ // tests/load-carrier-checks.test.ts. Waarschuwingen weigeren nooit.
4
+ const usable = (t) => {
5
+ const s = String(t ?? "").trim();
6
+ return s && s !== "NONE" ? s : "";
7
+ };
8
+ /**
9
+ * Bepaalt het loadCarrierType van één kar. Het type uit de requests is leidend
10
+ * (eerste request met een bruikbaar type); alleen als geen enkel request er een
11
+ * heeft, valt het terug op de waarde van Afhandeling.
12
+ */
13
+ export function resolveCartLoadCarrierType(cartIndex, afhandelingType, requests) {
14
+ const typesByRequest = requests
15
+ .map((r) => ({ fulfillmentRequestId: r.fulfillmentRequestId, loadCarrierType: usable(r.loadCarrier) }))
16
+ .filter((r) => r.loadCarrierType);
17
+ const fromAfh = usable(afhandelingType);
18
+ const warnings = [];
19
+ const fromReq = typesByRequest[0]?.loadCarrierType ?? "";
20
+ const loadCarrierType = fromReq || fromAfh;
21
+ const source = fromReq ? "fulfillment_request" : fromAfh ? "afhandeling" : "none";
22
+ const distinct = new Set(typesByRequest.map((t) => t.loadCarrierType));
23
+ const afhDiffers = !!fromAfh && !!fromReq && [...distinct].some((t) => t !== fromAfh);
24
+ if (fromReq && (afhDiffers || distinct.size > 1)) {
25
+ warnings.push({
26
+ code: "load_carrier_type_mismatch",
27
+ severity: "warning",
28
+ cartIndex,
29
+ sent: loadCarrierType,
30
+ fromAfhandeling: fromAfh,
31
+ typesByRequest,
32
+ message: `Kar ${cartIndex + 1}: verzonden loadCarrierType ${loadCarrierType} (uit request ${typesByRequest[0].fulfillmentRequestId})` +
33
+ (fromAfh ? `, Afhandeling stuurde ${fromAfh}` : "") +
34
+ (distinct.size > 1 ? `; requests op deze kar hebben verschillende types: ${typesByRequest.map((t) => `${t.fulfillmentRequestId}=${t.loadCarrierType}`).join(", ")}` : "") +
35
+ ".",
36
+ });
37
+ }
38
+ return { loadCarrierType, source, warnings };
39
+ }
40
+ /** Velden die de module zelf invulde voor één kar. */
41
+ export function defaultedFieldsWarning(cartIndex, fields) {
42
+ if (fields.length === 0)
43
+ return null;
44
+ return {
45
+ code: "defaulted_fields",
46
+ severity: "warning",
47
+ cartIndex,
48
+ fields,
49
+ message: `Kar ${cartIndex + 1}: door de module ingevuld: ${fields.map((f) => `${f.field}=${String(f.value)} (${f.source})`).join(", ")}.`,
50
+ };
51
+ }
52
+ /** Som van numberOfPackages per request over alle karren vergeleken met het request. */
53
+ export function packagesSumWarnings(lines, expectedById) {
54
+ const sums = new Map();
55
+ for (const l of lines)
56
+ sums.set(l.fulfillmentRequestId, (sums.get(l.fulfillmentRequestId) ?? 0) + l.numberOfPackages);
57
+ const out = [];
58
+ for (const [id, sum] of sums) {
59
+ const expected = expectedById.get(id);
60
+ if (expected == null || expected === sum)
61
+ continue;
62
+ out.push({
63
+ code: "packages_sum_mismatch",
64
+ severity: "warning",
65
+ fulfillmentRequestId: id,
66
+ sum,
67
+ expected,
68
+ message: `fulfillmentRequest ${id}: ${sum} pakketten over alle karren, request heeft ${expected}.`,
69
+ });
70
+ }
71
+ return out;
72
+ }
@@ -0,0 +1,2 @@
1
+ export declare const MODULE_VERSION = "1.0.71";
2
+ //# sourceMappingURL=module-version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module-version.d.ts","sourceRoot":"","sources":["../../src/lib/module-version.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,WAAW,CAAC"}
@@ -0,0 +1,3 @@
1
+ // Versie van dit pakket, meegegeven in gateway-antwoorden (moduleVersion).
2
+ // Moet gelijk zijn aan "version" in package.json; tests/module-version.test.ts bewaakt dat.
3
+ export const MODULE_VERSION = "1.0.71";