@flowselections/floriday-authenticatie-module 1.0.39 → 1.0.41

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 (54) hide show
  1. package/dist-lib/assets/floriday-logo.png.asset.json +11 -0
  2. package/dist-lib/components/TemplatePage.d.ts +1 -1
  3. package/dist-lib/components/TemplatePage.d.ts.map +1 -1
  4. package/dist-lib/components/floriday/FlorydayConnectionPage.d.ts +1 -1
  5. package/dist-lib/components/floriday/FlorydayConnectionPage.d.ts.map +1 -1
  6. package/dist-lib/components/floriday/FlorydaySyncHealthBanner.d.ts +1 -1
  7. package/dist-lib/components/floriday/FlorydaySyncHealthBanner.d.ts.map +1 -1
  8. package/dist-lib/components/floriday/FlorydaySyncJobsCard.d.ts +1 -1
  9. package/dist-lib/components/floriday/FlorydaySyncJobsCard.d.ts.map +1 -1
  10. package/dist-lib/components/floriday/TokenHealthCard.d.ts +1 -1
  11. package/dist-lib/components/floriday/TokenHealthCard.d.ts.map +1 -1
  12. package/dist-lib/components/settings/FloricodeSettingsCard.d.ts +1 -1
  13. package/dist-lib/components/settings/FloricodeSettingsCard.d.ts.map +1 -1
  14. package/dist-lib/components/settings/FlorydaySettingsCard.d.ts +1 -1
  15. package/dist-lib/components/settings/FlorydaySettingsCard.d.ts.map +1 -1
  16. package/dist-lib/integrations/supabase/auth-middleware.d.ts +374 -0
  17. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  18. package/dist-lib/integrations/supabase/client.d.ts +374 -0
  19. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  20. package/dist-lib/integrations/supabase/client.server.d.ts +374 -0
  21. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  22. package/dist-lib/integrations/supabase/types.d.ts +381 -0
  23. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  24. package/dist-lib/lib/cron/process-stock-queue.js +2 -2
  25. package/dist-lib/lib/error-page.js +30 -30
  26. package/dist-lib/lib/floricode.functions.d.ts +2618 -0
  27. package/dist-lib/lib/floricode.functions.d.ts.map +1 -1
  28. package/dist-lib/lib/floriday-auction-letters.functions.d.ts +374 -0
  29. package/dist-lib/lib/floriday-auction-letters.functions.d.ts.map +1 -1
  30. package/dist-lib/lib/floriday-auto-process.server.d.ts +2 -0
  31. package/dist-lib/lib/floriday-auto-process.server.d.ts.map +1 -1
  32. package/dist-lib/lib/floriday-auto-process.server.js +64 -33
  33. package/dist-lib/lib/floriday-labels.functions.d.ts +374 -0
  34. package/dist-lib/lib/floriday-labels.functions.d.ts.map +1 -1
  35. package/dist-lib/lib/floriday-public.functions.d.ts +2244 -0
  36. package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
  37. package/dist-lib/lib/floriday-stickers.functions.d.ts +374 -0
  38. package/dist-lib/lib/floriday-stickers.functions.d.ts.map +1 -1
  39. package/dist-lib/lib/floriday-sync.functions.d.ts +11778 -184
  40. package/dist-lib/lib/floriday-sync.functions.d.ts.map +1 -1
  41. package/dist-lib/lib/floriday-writes.functions.d.ts +2250 -6
  42. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  43. package/dist-lib/lib/floriday.functions.d.ts +7201 -92
  44. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  45. package/dist-lib/lib/floriday.functions.js +46 -3
  46. package/dist-lib/lib/gateway/fulfillment-order-body.d.ts +57 -0
  47. package/dist-lib/lib/gateway/fulfillment-order-body.d.ts.map +1 -0
  48. package/dist-lib/lib/gateway/fulfillment-order-body.js +350 -0
  49. package/dist-lib/lib/gateway/fulfillment-order-send.d.ts.map +1 -1
  50. package/dist-lib/lib/gateway/fulfillment-order-send.js +94 -67
  51. package/dist-lib/lib/gateway/index.d.ts +1 -0
  52. package/dist-lib/lib/gateway/index.d.ts.map +1 -1
  53. package/dist-lib/lib/gateway/index.js +1 -0
  54. package/package.json +1 -1
@@ -796,6 +796,11 @@ export async function syncFloridayEntityBySequence(supabase, env, connectionId,
796
796
  .eq("connection_id", connectionId)
797
797
  .eq("entity_type", opts.entityType)
798
798
  .maybeSingle();
799
+ // Een ONTBREKENDE state-rij mag NOOIT stil op 0 starten: Floriday-sequences
800
+ // zijn globaal, dus cursor 0 haalt willekeurige historie (2019) binnen en
801
+ // springt daarna door de kleine-batch-regel meteen naar maxSeq. We starten
802
+ // in dat geval expliciet op maxSeq en markeren de rij als "backfill nodig".
803
+ const hasStateRow = stateRow != null;
799
804
  const startCursor = stateRow?.last_sequence_number ?? 0;
800
805
  try {
801
806
  // Debug-logging: helpt 401's onderscheid maken tussen scope-issue,
@@ -812,6 +817,32 @@ export async function syncFloridayEntityBySequence(supabase, env, connectionId,
812
817
  /* logging mag nooit de sync breken */
813
818
  }
814
819
  const maxSeq = await fetchMaxSequence(supabase, env, opts.maxSequenceEndpoint);
820
+ if (!hasStateRow) {
821
+ // Geen cursor bekend: initialiseer op maxSeq (geen impliciete reset naar 0)
822
+ // en markeer expliciet dat historische data ontbreekt. Een backfill moet
823
+ // bewust worden gestart; nooit ongemerkt via deze sync.
824
+ const marker = `BACKFILL NODIG: cursor geïnitialiseerd op maxSeq=${maxSeq} zonder historie (entity=${opts.entityType}, connection=${connectionId}). Start een gerichte backfill/by-id refetch om oudere records te herstellen.`;
825
+ console.warn("[floriday-sync] %s", marker);
826
+ const initIso = new Date().toISOString();
827
+ await supabase
828
+ .from("floriday_sync_state")
829
+ .upsert({
830
+ connection_id: connectionId,
831
+ entity_type: opts.entityType,
832
+ last_sequence_number: maxSeq,
833
+ last_synced_at: initIso,
834
+ last_success_at: initIso,
835
+ last_items_processed: 0,
836
+ last_error: marker.slice(0, 500),
837
+ });
838
+ return {
839
+ synced: 0,
840
+ fromSequence: maxSeq,
841
+ toSequence: maxSeq,
842
+ durationMs: Date.now() - t0,
843
+ backfillNeeded: true,
844
+ };
845
+ }
815
846
  let cursor = startCursor;
816
847
  let totalSynced = 0;
817
848
  let safety = 0;
@@ -1129,6 +1160,17 @@ async function hashIdempotencyKey(input) {
1129
1160
  * zonder Floriday opnieuw aan te roepen.
1130
1161
  */
1131
1162
  export async function florydayWrite(supabase, env, opts) {
1163
+ // Eén plek waar de API-versieprefix aan schrijfpaden wordt toegevoegd.
1164
+ // Idempotent: een pad dat de prefix al meestuurt krijgt er geen tweede.
1165
+ const rawPath = opts.path.startsWith("/") ? opts.path : `/${opts.path}`;
1166
+ let requestPath;
1167
+ if (rawPath.startsWith(`/${SUPPLIERS_API_VERSION}/`) || rawPath === `/${SUPPLIERS_API_VERSION}`) {
1168
+ requestPath = rawPath;
1169
+ console.warn(`[florydayWrite] caller stuurde de API-versieprefix zelf mee (${opts.module}/${opts.action}): ${rawPath} — prefix hoort in florydayWrite`);
1170
+ }
1171
+ else {
1172
+ requestPath = `/${SUPPLIERS_API_VERSION}${rawPath}`;
1173
+ }
1132
1174
  const conn = await getActiveFlorydayCredentials(supabase, env);
1133
1175
  if (!conn) {
1134
1176
  return {
@@ -1200,9 +1242,10 @@ export async function florydayWrite(supabase, env, opts) {
1200
1242
  "Idempotency-Key": idemKey,
1201
1243
  };
1202
1244
  const requestLog = {
1203
- endpoint: `/${SUPPLIERS_API_VERSION}${opts.path.startsWith("/") ? "" : "/"}${opts.path}`,
1245
+ // endpoint == het werkelijk verstuurde pad (geen samengestelde string).
1246
+ endpoint: requestPath,
1204
1247
  method: opts.method,
1205
- path: opts.path,
1248
+ path: requestPath,
1206
1249
  environment: env,
1207
1250
  connectionId: conn.id,
1208
1251
  tenant: {
@@ -1225,7 +1268,7 @@ export async function florydayWrite(supabase, env, opts) {
1225
1268
  let ok = false;
1226
1269
  let errMsg;
1227
1270
  try {
1228
- const res = await florydayFetch(supabase, env, opts.path, {
1271
+ const res = await florydayFetch(supabase, env, requestPath, {
1229
1272
  method: opts.method,
1230
1273
  headers,
1231
1274
  body: opts.body == null ? undefined : JSON.stringify(opts.body),
@@ -0,0 +1,57 @@
1
+ import type { SupabaseClient } from "@supabase/supabase-js";
2
+ export declare function deriveFulfillmentOrderId(deliveryOrderId: string): string;
3
+ export declare function resolvePackageCount(req: any): number | null;
4
+ export interface SkippedFulfillmentRequest {
5
+ fulfillmentRequestId: string;
6
+ reason: "load_carrier_none" | "already_claimed";
7
+ }
8
+ export type BuildFulfillmentOrderBodyResult = {
9
+ kind: "body";
10
+ body: Record<string, unknown>;
11
+ fulfillmentOrderId: string;
12
+ usedRequestIds: string[];
13
+ skipped: SkippedFulfillmentRequest[];
14
+ } | {
15
+ kind: "already_fulfilled";
16
+ code: "already_fulfilled";
17
+ source: FulfillmentDetectionSource;
18
+ message: string;
19
+ fulfillmentOrderId?: string;
20
+ skipped: SkippedFulfillmentRequest[];
21
+ } | {
22
+ kind: "error";
23
+ error: string;
24
+ /** false = tijdelijk (opnieuw proberen kan slagen na sync). */
25
+ permanent: boolean;
26
+ };
27
+ export interface BuildFulfillmentOrderBodyInput {
28
+ connectionId: string;
29
+ deliveryOrderId: string;
30
+ /** De `data`-kolom van floriday_delivery_orders_cache. */
31
+ deliveryOrderData: any;
32
+ /** Optionele filter op fulfillmentRequestId (gateway-pad). */
33
+ fulfillmentRequestIds?: string[] | null;
34
+ }
35
+ export type FulfillmentDetectionSource = "delivery_order_claims" | "fulfillment_orders_cache";
36
+ export type FulfillmentDetection = {
37
+ code: "already_fulfilled";
38
+ source: FulfillmentDetectionSource;
39
+ message: string;
40
+ fulfillmentOrderId?: string;
41
+ skipped: SkippedFulfillmentRequest[];
42
+ } | {
43
+ code: "delivery_order_not_cached";
44
+ message: string;
45
+ } | {
46
+ code: "not_fulfilled";
47
+ };
48
+ export interface DetectFulfillmentStateInput {
49
+ connectionId: string;
50
+ deliveryOrderId: string;
51
+ /** Optioneel: de al gelezen `data`-kolom. Ontbreekt hij, dan lezen we zelf. */
52
+ deliveryOrderData?: any;
53
+ fulfillmentRequestIds?: string[] | null;
54
+ }
55
+ export declare function detectFulfillmentState(supabase: SupabaseClient, input: DetectFulfillmentStateInput): Promise<FulfillmentDetection>;
56
+ export declare function buildFulfillmentOrderBody(supabase: SupabaseClient, input: BuildFulfillmentOrderBodyInput): Promise<BuildFulfillmentOrderBodyResult>;
57
+ //# sourceMappingURL=fulfillment-order-body.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fulfillment-order-body.d.ts","sourceRoot":"","sources":["../../../src/lib/gateway/fulfillment-order-body.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAsC5D,wBAAgB,wBAAwB,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAExE;AAUD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,GAAG,GAAG,MAAM,GAAG,IAAI,CAO3D;AAMD,MAAM,WAAW,yBAAyB;IACxC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,MAAM,EAAE,mBAAmB,GAAG,iBAAiB,CAAC;CACjD;AAED,MAAM,MAAM,+BAA+B,GACvC;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,OAAO,EAAE,yBAAyB,EAAE,CAAC;CACtC,GACD;IACE,IAAI,EAAE,mBAAmB,CAAC;IAC1B,IAAI,EAAE,mBAAmB,CAAC;IAC1B,MAAM,EAAE,0BAA0B,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,yBAAyB,EAAE,CAAC;CACtC,GACD;IACE,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAEN,MAAM,WAAW,8BAA8B;IAC7C,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,iBAAiB,EAAE,GAAG,CAAC;IACvB,8DAA8D;IAC9D,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACzC;AAqBD,MAAM,MAAM,0BAA0B,GAClC,uBAAuB,GACvB,0BAA0B,CAAC;AAE/B,MAAM,MAAM,oBAAoB,GAC5B;IACE,IAAI,EAAE,mBAAmB,CAAC;IAC1B,MAAM,EAAE,0BAA0B,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,yBAAyB,EAAE,CAAC;CACtC,GACD;IACE,IAAI,EAAE,2BAA2B,CAAC;IAClC,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC;AAyC9B,MAAM,WAAW,2BAA2B;IAC1C,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,+EAA+E;IAC/E,iBAAiB,CAAC,EAAE,GAAG,CAAC;IACxB,qBAAqB,CAAC,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;CACzC;AAED,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,2BAA2B,GACjC,OAAO,CAAC,oBAAoB,CAAC,CA0D/B;AAOD,wBAAsB,yBAAyB,CAC7C,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,8BAA8B,GACpC,OAAO,CAAC,+BAA+B,CAAC,CA2N1C"}
@@ -0,0 +1,350 @@
1
+ // Gedeelde bodybouwer voor de Floriday fulfillment-order push (2026v1).
2
+ //
3
+ // Eén implementatie voor beide paden:
4
+ // - src/lib/gateway/fulfillment-order-send.ts (gateway-endpoint)
5
+ // - src/lib/floriday-auto-process.server.ts (auto-process / cron-scan)
6
+ //
7
+ // De body wordt volledig opgebouwd uit de delivery-order-cache plus een join
8
+ // op de sales-order-cache; er wordt niets verzonnen. Ontbrekende gegevens
9
+ // leiden tot een expliciete fout, nooit tot een stille aanname.
10
+ import { createHash } from "node:crypto";
11
+ // ---------------------------------------------------------------------------
12
+ // fulfillmentOrderId — deterministische UUIDv5
13
+ // ---------------------------------------------------------------------------
14
+ //
15
+ // WAAROM DETERMINISTISCH:
16
+ // Floriday vereist een client-side `fulfillmentOrderId` met een non-default
17
+ // waarde. Een random UUID zou bij elke retry een nieuwe fulfillment-order
18
+ // aanmaken: een netwerkfout of timeout na een geslaagde POST zou dan tot een
19
+ // dubbele fulfilment leiden. Door de GUID deterministisch af te leiden uit de
20
+ // deliveryOrderId (UUIDv5, vaste namespace hieronder) levert elke retry exact
21
+ // dezelfde waarde op en dedupliceert Floriday zelf. Er is daarom géén extra
22
+ // databasekolom en géén opslag nodig.
23
+ //
24
+ // BEPERKING:
25
+ // Deze afleiding geeft precies één fulfillment-order per delivery-order.
26
+ // Wil je ooit een delivery-order in meerdere fulfillment-orders splitsen
27
+ // (bijvoorbeeld per ladingdrager in aparte pushes), dan moet de afleiding
28
+ // worden uitgebreid met een discriminator én moet de gekozen waarde worden
29
+ // vastgelegd, anders overschrijven de pushes elkaars identiteit.
30
+ const FULFILLMENT_ORDER_NAMESPACE = "6f9a1e2c-7d34-4c1b-9a5e-3b8f0d2c4a71";
31
+ function uuidV5(name, namespace) {
32
+ const hex = namespace.replace(/-/g, "");
33
+ if (hex.length !== 32)
34
+ throw new Error("Ongeldige namespace-GUID");
35
+ const nsBytes = Buffer.from(hex, "hex");
36
+ const hash = createHash("sha1")
37
+ .update(Buffer.concat([nsBytes, Buffer.from(name, "utf8")]))
38
+ .digest();
39
+ const bytes = Buffer.from(hash.subarray(0, 16));
40
+ bytes[6] = (bytes[6] & 0x0f) | 0x50; // versie 5
41
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // RFC 4122 variant
42
+ const h = bytes.toString("hex");
43
+ return `${h.slice(0, 8)}-${h.slice(8, 12)}-${h.slice(12, 16)}-${h.slice(16, 20)}-${h.slice(20)}`;
44
+ }
45
+ export function deriveFulfillmentOrderId(deliveryOrderId) {
46
+ return uuidV5(deliveryOrderId, FULFILLMENT_ORDER_NAMESPACE);
47
+ }
48
+ // ---------------------------------------------------------------------------
49
+ // numberOfPackages
50
+ // ---------------------------------------------------------------------------
51
+ //
52
+ // In de delivery-order-cache is `numberOfPackages` in alle rijen 0; het
53
+ // werkelijke aantal staat in `initialNumberOfPackages` (en wordt bevestigd
54
+ // door fulfillmentOrderClaims[].claimedNumberOfPackages). Deze functie valt
55
+ // NOOIT stil terug op 1: een ontbrekend aantal is een fout.
56
+ export function resolvePackageCount(req) {
57
+ const candidates = [req?.initialNumberOfPackages, req?.numberOfPackages];
58
+ for (const c of candidates) {
59
+ const n = Number(c);
60
+ if (Number.isFinite(n) && n > 0)
61
+ return n;
62
+ }
63
+ return null;
64
+ }
65
+ /** Pure scan van de claims op de (gefilterde) fulfillmentRequests. */
66
+ function scanClaims(doData, filterIds) {
67
+ const requests = Array.isArray(doData?.fulfillmentRequests)
68
+ ? doData.fulfillmentRequests
69
+ : [];
70
+ const claimed = [];
71
+ let unclaimedCount = 0;
72
+ let consideredCount = 0;
73
+ let fulfillmentOrderId;
74
+ for (const r of requests) {
75
+ const id = r?.fulfillmentRequestId ?? r?.id;
76
+ if (!id)
77
+ continue;
78
+ if (filterIds && !filterIds.includes(String(id)))
79
+ continue;
80
+ consideredCount += 1;
81
+ const claims = Array.isArray(r?.fulfillmentOrderClaims)
82
+ ? r.fulfillmentOrderClaims
83
+ : [];
84
+ if (claims.length > 0) {
85
+ claimed.push({ fulfillmentRequestId: String(id), reason: "already_claimed" });
86
+ if (!fulfillmentOrderId) {
87
+ const fromClaim = claims.find((c) => c?.fulfillmentOrderId)?.fulfillmentOrderId;
88
+ if (fromClaim)
89
+ fulfillmentOrderId = String(fromClaim);
90
+ }
91
+ }
92
+ else {
93
+ unclaimedCount += 1;
94
+ }
95
+ }
96
+ return { claimed, unclaimedCount, consideredCount, fulfillmentOrderId };
97
+ }
98
+ export async function detectFulfillmentState(supabase, input) {
99
+ const { connectionId, deliveryOrderId } = input;
100
+ const filterIds = Array.isArray(input.fulfillmentRequestIds)
101
+ ? input.fulfillmentRequestIds.map((s) => String(s))
102
+ : null;
103
+ let doData = input.deliveryOrderData ?? null;
104
+ if (!doData && deliveryOrderId) {
105
+ const { data: doRow } = await supabase
106
+ .from("floriday_delivery_orders_cache")
107
+ .select("floriday_id, data")
108
+ .eq("connection_id", connectionId)
109
+ .eq("floriday_id", deliveryOrderId)
110
+ .maybeSingle();
111
+ doData = doRow?.data ?? null;
112
+ }
113
+ // ─ Bron (a): claims op de delivery-order-cache ───────────────────────────
114
+ if (doData) {
115
+ const scan = scanClaims(doData, filterIds);
116
+ if (scan.consideredCount > 0 && scan.unclaimedCount === 0) {
117
+ return {
118
+ code: "already_fulfilled",
119
+ source: "delivery_order_claims",
120
+ message: "Al afgehandeld bij Floriday",
121
+ fulfillmentOrderId: scan.fulfillmentOrderId,
122
+ skipped: scan.claimed,
123
+ };
124
+ }
125
+ }
126
+ // ─ Bron (b): fulfillment-order-cache (zie het commentaarblok hierboven) ──
127
+ if (deliveryOrderId) {
128
+ const { data: existing } = await supabase
129
+ .from("floriday_fulfillment_orders_cache")
130
+ .select("floriday_id")
131
+ .eq("connection_id", connectionId)
132
+ .eq("delivery_order_id", deliveryOrderId)
133
+ .maybeSingle();
134
+ if (existing?.floriday_id) {
135
+ return {
136
+ code: "already_fulfilled",
137
+ source: "fulfillment_orders_cache",
138
+ message: "Al afgehandeld bij Floriday",
139
+ fulfillmentOrderId: String(existing.floriday_id),
140
+ skipped: [],
141
+ };
142
+ }
143
+ }
144
+ if (!doData) {
145
+ return {
146
+ code: "delivery_order_not_cached",
147
+ message: "Delivery-order niet in cache — sync eerst",
148
+ };
149
+ }
150
+ return { code: "not_fulfilled" };
151
+ }
152
+ // ---------------------------------------------------------------------------
153
+ // De bodybouwer
154
+ // ---------------------------------------------------------------------------
155
+ export async function buildFulfillmentOrderBody(supabase, input) {
156
+ const { connectionId, deliveryOrderId, deliveryOrderData } = input;
157
+ const doData = deliveryOrderData ?? {};
158
+ const allRequests = Array.isArray(doData.fulfillmentRequests)
159
+ ? doData.fulfillmentRequests
160
+ : [];
161
+ if (allRequests.length === 0) {
162
+ return {
163
+ kind: "error",
164
+ error: "Delivery-order bevat geen fulfillmentRequests",
165
+ permanent: true,
166
+ };
167
+ }
168
+ const filterIds = Array.isArray(input.fulfillmentRequestIds)
169
+ ? input.fulfillmentRequestIds.map((s) => String(s))
170
+ : null;
171
+ const skipped = [];
172
+ const candidates = [];
173
+ for (const r of allRequests) {
174
+ const id = r?.fulfillmentRequestId ?? r?.id;
175
+ if (!id) {
176
+ return {
177
+ kind: "error",
178
+ error: "fulfillmentRequest zonder fulfillmentRequestId in delivery-order",
179
+ permanent: true,
180
+ };
181
+ }
182
+ if (filterIds && !filterIds.includes(String(id)))
183
+ continue;
184
+ // Veiligheidscheck: bestaat er al een fulfillment-order bij Floriday?
185
+ const claims = Array.isArray(r?.fulfillmentOrderClaims)
186
+ ? r.fulfillmentOrderClaims
187
+ : [];
188
+ if (claims.length > 0) {
189
+ skipped.push({ fulfillmentRequestId: String(id), reason: "already_claimed" });
190
+ continue;
191
+ }
192
+ // Ladingdrager komt per request uit de delivery-order; "NONE" slaan we over.
193
+ const loadCarrier = String(r?.loadCarrier ?? "").trim();
194
+ if (!loadCarrier || loadCarrier === "NONE") {
195
+ skipped.push({ fulfillmentRequestId: String(id), reason: "load_carrier_none" });
196
+ continue;
197
+ }
198
+ candidates.push(r);
199
+ }
200
+ if (candidates.length === 0) {
201
+ const allClaimed = skipped.length > 0 && skipped.every((s) => s.reason === "already_claimed");
202
+ if (allClaimed) {
203
+ return {
204
+ kind: "already_fulfilled",
205
+ code: "already_fulfilled",
206
+ source: "delivery_order_claims",
207
+ message: "Al afgehandeld bij Floriday",
208
+ fulfillmentOrderId: scanClaims(doData, filterIds).fulfillmentOrderId,
209
+ skipped,
210
+ };
211
+ }
212
+ return {
213
+ kind: "error",
214
+ error: "Geen verstuurbare fulfillmentRequests: alle requests zijn overgeslagen " +
215
+ `(${skipped.map((s) => `${s.fulfillmentRequestId}=${s.reason}`).join(", ") || "geen"})`,
216
+ permanent: true,
217
+ };
218
+ }
219
+ // ─ Join: tradeItemId + organisatie-id's uit de sales-order-cache ─────────
220
+ const salesOrderIds = Array.from(new Set(candidates
221
+ .map((r) => (r?.salesOrderId ? String(r.salesOrderId) : ""))
222
+ .filter(Boolean)));
223
+ if (salesOrderIds.length === 0) {
224
+ return {
225
+ kind: "error",
226
+ error: "fulfillmentRequests zonder salesOrderId — tradeItemId niet te bepalen",
227
+ permanent: true,
228
+ };
229
+ }
230
+ const { data: soRows, error: soError } = await supabase
231
+ .from("floriday_salesorders_cache")
232
+ .select("floriday_id, data")
233
+ .eq("connection_id", connectionId)
234
+ .in("floriday_id", salesOrderIds);
235
+ if (soError) {
236
+ return {
237
+ kind: "error",
238
+ error: `Sales-orders lezen mislukt: ${soError.message}`,
239
+ permanent: false,
240
+ };
241
+ }
242
+ const soById = new Map();
243
+ for (const row of (soRows ?? [])) {
244
+ soById.set(String(row.floriday_id), row.data ?? {});
245
+ }
246
+ const missing = salesOrderIds.filter((id) => !soById.has(id));
247
+ if (missing.length > 0) {
248
+ return {
249
+ kind: "error",
250
+ error: `Sales-order(s) niet in cache — sync eerst: ${missing.join(", ")}`,
251
+ permanent: false,
252
+ };
253
+ }
254
+ const firstSo = soById.get(String(candidates[0].salesOrderId));
255
+ const supplierOrganizationId = firstSo?.supplierOrganizationId ?? null;
256
+ if (!supplierOrganizationId) {
257
+ return {
258
+ kind: "error",
259
+ error: "supplierOrganizationId ontbreekt op de gekoppelde sales-order",
260
+ permanent: false,
261
+ };
262
+ }
263
+ // ─ Groeperen per loadCarrier-waarde ──────────────────────────────────────
264
+ const groups = new Map();
265
+ for (const r of candidates) {
266
+ const key = String(r.loadCarrier).trim();
267
+ const list = groups.get(key);
268
+ if (list)
269
+ list.push(r);
270
+ else
271
+ groups.set(key, [r]);
272
+ }
273
+ const usedRequestIds = [];
274
+ const loadCarriers = [];
275
+ let carrierIndex = 0;
276
+ for (const [loadCarrierType, reqs] of groups) {
277
+ carrierIndex += 1;
278
+ const items = [];
279
+ let itemIndex = 0;
280
+ for (const r of reqs) {
281
+ const id = String(r?.fulfillmentRequestId ?? r?.id);
282
+ const count = resolvePackageCount(r);
283
+ if (count === null) {
284
+ return {
285
+ kind: "error",
286
+ error: `Aantal pakketten ontbreekt op fulfillmentRequest ${id}`,
287
+ permanent: true,
288
+ };
289
+ }
290
+ const so = soById.get(String(r.salesOrderId));
291
+ const tradeItemId = so?.tradeItemId ?? null;
292
+ if (!tradeItemId) {
293
+ return {
294
+ kind: "error",
295
+ error: `tradeItemId ontbreekt op sales-order ${r.salesOrderId}`,
296
+ permanent: false,
297
+ };
298
+ }
299
+ itemIndex += 1;
300
+ items.push({
301
+ sortIndex: itemIndex,
302
+ fulfillmentRequestId: id,
303
+ salesOrderId: String(r.salesOrderId),
304
+ tradeItemId,
305
+ numberOfPackages: count,
306
+ });
307
+ usedRequestIds.push(id);
308
+ }
309
+ loadCarriers.push({
310
+ sortIndex: carrierIndex,
311
+ loadCarrierType,
312
+ numberOfAdditionalLayers: 0,
313
+ loadCarrierItems: items,
314
+ });
315
+ }
316
+ const fulfillmentOrderId = deriveFulfillmentOrderId(deliveryOrderId);
317
+ const body = {
318
+ type: "DIRECT_SALES",
319
+ fulfillmentOrderId,
320
+ supplierOrganizationId,
321
+ fulfillingOrganizationId: supplierOrganizationId,
322
+ despatchWarehouseId: doData.despatchWarehouseId ?? null,
323
+ latestDeliveryDateTime: doData.deliveryDateTime ?? null,
324
+ oneLabelOnly: false,
325
+ destination: doData.destination ?? null,
326
+ loadCarriers,
327
+ };
328
+ if (!body.despatchWarehouseId) {
329
+ return {
330
+ kind: "error",
331
+ error: "despatchWarehouseId ontbreekt in de delivery-order-cache",
332
+ permanent: false,
333
+ };
334
+ }
335
+ if (!body.latestDeliveryDateTime) {
336
+ return {
337
+ kind: "error",
338
+ error: "deliveryDateTime ontbreekt in de delivery-order-cache",
339
+ permanent: false,
340
+ };
341
+ }
342
+ if (!body.destination) {
343
+ return {
344
+ kind: "error",
345
+ error: "destination ontbreekt in de delivery-order-cache",
346
+ permanent: false,
347
+ };
348
+ }
349
+ return { kind: "body", body, fulfillmentOrderId, usedRequestIds, skipped };
350
+ }
@@ -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;AAkB5D,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,CA8GrC;AAcD,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;AAQ5D,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,CA0JrC;AAeD,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CAmCnB"}