@flowselections/floriday-verkoop-module 1.0.12 → 1.0.14

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 (65) hide show
  1. package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.d.ts +4 -0
  2. package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.d.ts.map +1 -0
  3. package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.js +95 -0
  4. package/dist-lib/components/verkoop/CartAssignmentWizard.d.ts +6 -0
  5. package/dist-lib/components/verkoop/CartAssignmentWizard.d.ts.map +1 -0
  6. package/dist-lib/components/verkoop/CartAssignmentWizard.js +194 -0
  7. package/dist-lib/components/verkoop/CreateOrderPage.d.ts.map +1 -1
  8. package/dist-lib/components/verkoop/CreateOrderPage.js +14 -4
  9. package/dist-lib/components/verkoop/OrderAfleverbrievenCard.d.ts +4 -0
  10. package/dist-lib/components/verkoop/OrderAfleverbrievenCard.d.ts.map +1 -0
  11. package/dist-lib/components/verkoop/OrderAfleverbrievenCard.js +44 -0
  12. package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts +5 -0
  13. package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts.map +1 -0
  14. package/dist-lib/components/verkoop/OrderKarindelingCard.js +160 -0
  15. package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts.map +1 -1
  16. package/dist-lib/components/verkoop/SalesOrderDetailView.js +6 -3
  17. package/dist-lib/components/verkoop/VerkoopOrdersPage.d.ts.map +1 -1
  18. package/dist-lib/components/verkoop/VerkoopOrdersPage.js +39 -3
  19. package/dist-lib/integrations/supabase/auth-middleware.d.ts +605 -4
  20. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  21. package/dist-lib/integrations/supabase/client.d.ts +605 -4
  22. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  23. package/dist-lib/integrations/supabase/client.server.d.ts +605 -4
  24. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  25. package/dist-lib/integrations/supabase/client.server.js +4 -4
  26. package/dist-lib/integrations/supabase/types.d.ts +613 -4
  27. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  28. package/dist-lib/lib/cart-lock.functions.d.ts +11356 -0
  29. package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -0
  30. package/dist-lib/lib/cart-lock.functions.js +47 -0
  31. package/dist-lib/lib/cart-planner.d.ts +38 -0
  32. package/dist-lib/lib/cart-planner.d.ts.map +1 -0
  33. package/dist-lib/lib/cart-planner.js +217 -0
  34. package/dist-lib/lib/cart-replan.functions.d.ts +22729 -0
  35. package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -0
  36. package/dist-lib/lib/cart-replan.functions.js +203 -0
  37. package/dist-lib/lib/create-order.functions.d.ts +26999 -76
  38. package/dist-lib/lib/create-order.functions.d.ts.map +1 -1
  39. package/dist-lib/lib/create-order.functions.js +158 -3
  40. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +34033 -0
  41. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -0
  42. package/dist-lib/lib/floriday-auto-fulfillment.functions.js +65 -0
  43. package/dist-lib/lib/floriday-auto-fulfillment.server.d.ts +21 -0
  44. package/dist-lib/lib/floriday-auto-fulfillment.server.d.ts.map +1 -0
  45. package/dist-lib/lib/floriday-auto-fulfillment.server.js +352 -0
  46. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +11351 -0
  47. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -0
  48. package/dist-lib/lib/floriday-logistic-labels.functions.js +171 -0
  49. package/dist-lib/lib/floriday-public.functions.d.ts +4237 -44
  50. package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
  51. package/dist-lib/lib/floriday-public.functions.js +45 -47
  52. package/dist-lib/lib/floriday-writes.functions.d.ts +3025 -20
  53. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  54. package/dist-lib/lib/salesorders-mapping.d.ts +2 -0
  55. package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -1
  56. package/dist-lib/lib/salesorders-mapping.js +46 -20
  57. package/dist-lib/lib/salesorders.functions.d.ts +1831 -28
  58. package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
  59. package/dist-lib/lib/salesorders.functions.js +8 -0
  60. package/dist-lib/lib/tradeitems.functions.d.ts +605 -4
  61. package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
  62. package/dist-lib/lib/useSalesOrdersQuery.d.ts +1 -0
  63. package/dist-lib/lib/useSalesOrdersQuery.d.ts.map +1 -1
  64. package/dist-lib/lib/useSalesOrdersQuery.js +121 -39
  65. package/package.json +1 -1
@@ -0,0 +1,352 @@
1
+ import { florydayWrite } from "./floriday-gateway.server";
2
+ async function loadConnectionMeta(supabase, connectionId) {
3
+ const { data } = await supabase
4
+ .from("floriday_connections")
5
+ .select("id, environment, access_token, api_key, is_active, gln_code, preferred_warehouse_id")
6
+ .eq("id", connectionId)
7
+ .maybeSingle();
8
+ return data ?? null;
9
+ }
10
+ async function loadProductPacking(supabase, tradeItemId) {
11
+ if (!tradeItemId)
12
+ return null;
13
+ const { data } = await supabase
14
+ .from("products")
15
+ .select("plants_per_crate, crates_per_layer, layers_per_cart")
16
+ .eq("trade_item_key", tradeItemId)
17
+ .limit(1)
18
+ .maybeSingle();
19
+ if (!data)
20
+ return null;
21
+ return {
22
+ plants_per_crate: Number(data.plants_per_crate) || 0,
23
+ crates_per_layer: Number(data.crates_per_layer) || 0,
24
+ layers_per_cart: Number(data.layers_per_cart) || 0,
25
+ };
26
+ }
27
+ function upsertLog(supabase, connectionId, salesOrderId, patch) {
28
+ return supabase.from("floriday_auto_fulfillment_log").upsert({
29
+ connection_id: connectionId,
30
+ sales_order_id: salesOrderId,
31
+ last_attempt_at: new Date().toISOString(),
32
+ ...patch,
33
+ }, { onConflict: "connection_id,sales_order_id" });
34
+ }
35
+ async function existingFulfillmentForOrder(supabase, connectionId, salesOrderId) {
36
+ // Scan the most recent fulfillment orders for this connection and look for
37
+ // a nested loadCarrierItems entry referencing this sales order.
38
+ const { data } = await supabase
39
+ .from("floriday_fulfillment_orders_cache")
40
+ .select("data")
41
+ .eq("connection_id", connectionId)
42
+ .order("fetched_at", { ascending: false })
43
+ .limit(500);
44
+ const rows = data ?? [];
45
+ for (const row of rows) {
46
+ const carriers = row?.data?.loadCarriers;
47
+ if (!Array.isArray(carriers))
48
+ continue;
49
+ for (const c of carriers) {
50
+ const items = c?.loadCarrierItems;
51
+ if (!Array.isArray(items))
52
+ continue;
53
+ for (const it of items) {
54
+ if (String(it?.salesOrderId ?? "") === salesOrderId)
55
+ return true;
56
+ }
57
+ }
58
+ }
59
+ return false;
60
+ }
61
+ async function alreadyProcessed(supabase, connectionId, salesOrderId) {
62
+ const { data } = await supabase
63
+ .from("floriday_auto_fulfillment_log")
64
+ .select("status")
65
+ .eq("connection_id", connectionId)
66
+ .eq("sales_order_id", salesOrderId)
67
+ .maybeSingle();
68
+ return data?.status === "created";
69
+ }
70
+ async function loadDeliveryContext(supabase, connectionId, salesOrderId) {
71
+ const { data } = await supabase
72
+ .from("floriday_delivery_orders_cache")
73
+ .select("data")
74
+ .eq("connection_id", connectionId)
75
+ .eq("is_deleted", false)
76
+ .order("fetched_at", { ascending: false })
77
+ .limit(500);
78
+ const rows = data ?? [];
79
+ for (const row of rows) {
80
+ const d = row?.data;
81
+ const reqs = Array.isArray(d?.fulfillmentRequests) ? d.fulfillmentRequests : [];
82
+ const matching = [];
83
+ for (const r of reqs) {
84
+ if (String(r?.salesOrderId ?? "") !== salesOrderId)
85
+ continue;
86
+ matching.push({
87
+ fulfillmentRequestId: String(r?.fulfillmentRequestId ?? ""),
88
+ salesOrderId: String(r?.salesOrderId ?? ""),
89
+ loadCarrier: r?.loadCarrier ?? null,
90
+ numberOfPackages: Number(r?.numberOfPackages ?? 0) || 0,
91
+ deliveryRemarks: r?.deliveryRemarks ?? null,
92
+ tradeItemId: r?.tradeItemId ?? null,
93
+ });
94
+ }
95
+ if (matching.length > 0) {
96
+ return {
97
+ deliveryOrderId: String(d?.deliveryOrderId ?? ""),
98
+ despatchWarehouseId: d?.despatchWarehouseId ?? null,
99
+ destination: d?.destination ?? null,
100
+ latestDeliveryDateTime: d?.deliveryDateTime ?? null,
101
+ requests: matching,
102
+ };
103
+ }
104
+ }
105
+ return null;
106
+ }
107
+ function buildFulfillmentPayload(order, warehouseId, ctx) {
108
+ const warnings = [];
109
+ const supplierOrganizationId = String(order?.supplierOrganizationId ?? "");
110
+ const customerOrganizationId = String(order?.customerOrganizationId ?? "");
111
+ // Group requests by load-carrier type to build one loadCarrier per group.
112
+ const byCarrier = new Map();
113
+ for (const r of ctx.requests) {
114
+ const key = r.loadCarrier ?? "DANISH_TROLLEY";
115
+ if (!byCarrier.has(key))
116
+ byCarrier.set(key, []);
117
+ byCarrier.get(key).push(r);
118
+ }
119
+ const loadCarriers = [];
120
+ let sortIndex = 1;
121
+ for (const [carrier, requests] of byCarrier) {
122
+ loadCarriers.push({
123
+ sortIndex: sortIndex++,
124
+ loadCarrierType: carrier === "NONE" ? "DANISH_TROLLEY" : carrier,
125
+ numberOfAdditionalLayers: 0,
126
+ loadCarrierItems: requests.map((r, idx) => {
127
+ const item = {
128
+ sortIndex: idx + 1,
129
+ fulfillmentRequestId: r.fulfillmentRequestId,
130
+ tradeItemId: r.tradeItemId ?? String(order?.tradeItemId ?? ""),
131
+ salesOrderId: r.salesOrderId,
132
+ numberOfPackages: r.numberOfPackages,
133
+ };
134
+ const remarks = (r.deliveryRemarks ?? "").toString().trim();
135
+ if (remarks)
136
+ item.deliveryRemarks = remarks;
137
+ return item;
138
+ }),
139
+ });
140
+ }
141
+ const payload = {
142
+ fulfillmentOrderId: crypto.randomUUID(),
143
+ type: order?.tradeInstrument ?? "DIRECT_SALES",
144
+ despatchWarehouseId: warehouseId,
145
+ latestDeliveryDateTime: ctx.latestDeliveryDateTime ?? order?.delivery?.latestDeliveryDateTime ?? null,
146
+ supplierOrganizationId,
147
+ fulfillingOrganizationId: supplierOrganizationId,
148
+ destination: ctx.destination ?? {
149
+ organizationId: customerOrganizationId,
150
+ location: order?.delivery?.location ?? null,
151
+ warehouseId: null,
152
+ },
153
+ loadCarriers,
154
+ oneLabelOnly: false,
155
+ };
156
+ return { payload, warnings };
157
+ }
158
+ const SKIPPABLE_STATUSES = new Set(["REJECTED", "CANCELLED", "CANCELED", "EXPIRED"]);
159
+ export async function processOrderForAutoFulfillment(supabase, order) {
160
+ const salesOrderId = order.floriday_id;
161
+ const connectionId = order.connection_id;
162
+ if (order.is_deleted) {
163
+ await upsertLog(supabase, connectionId, salesOrderId, {
164
+ status: "skipped",
165
+ reason: "sales-order-deleted",
166
+ });
167
+ return { status: "skipped", reason: "sales-order-deleted" };
168
+ }
169
+ if (order.status && SKIPPABLE_STATUSES.has(String(order.status).toUpperCase())) {
170
+ await upsertLog(supabase, connectionId, salesOrderId, {
171
+ status: "skipped",
172
+ reason: `status-${order.status}`,
173
+ });
174
+ return { status: "skipped", reason: `status-${order.status}` };
175
+ }
176
+ if (await alreadyProcessed(supabase, connectionId, salesOrderId)) {
177
+ return { status: "skipped", reason: "already-created" };
178
+ }
179
+ if (await existingFulfillmentForOrder(supabase, connectionId, salesOrderId)) {
180
+ await upsertLog(supabase, connectionId, salesOrderId, {
181
+ status: "skipped",
182
+ reason: "existing-fulfillment",
183
+ });
184
+ return { status: "skipped", reason: "existing-fulfillment" };
185
+ }
186
+ const conn = await loadConnectionMeta(supabase, connectionId);
187
+ if (!conn) {
188
+ await upsertLog(supabase, connectionId, salesOrderId, {
189
+ status: "failed",
190
+ error: "connection-not-found",
191
+ });
192
+ return { status: "failed", reason: "connection-not-found" };
193
+ }
194
+ // Warehouse fallback chain:
195
+ // 1) connection.preferred_warehouse_id
196
+ // 2) salesorder.data.despatchWarehouseId
197
+ // 3) first non-deleted warehouse from floriday_warehouses_cache
198
+ let warehouseId = conn.preferred_warehouse_id ?? null;
199
+ let warehouseFallback = null;
200
+ if (!warehouseId) {
201
+ const orderWarehouse = String(order.data?.despatchWarehouseId ?? "").trim();
202
+ if (orderWarehouse) {
203
+ warehouseId = orderWarehouse;
204
+ warehouseFallback = "warehouse-fallback:order";
205
+ }
206
+ }
207
+ if (!warehouseId) {
208
+ const { data: wh } = await supabase
209
+ .from("floriday_warehouses_cache")
210
+ .select("floriday_id, is_default")
211
+ .eq("connection_id", connectionId)
212
+ .eq("is_deleted", false)
213
+ .order("is_default", { ascending: false })
214
+ .limit(1)
215
+ .maybeSingle();
216
+ const cached = String(wh?.floriday_id ?? "").trim();
217
+ if (cached) {
218
+ warehouseId = cached;
219
+ warehouseFallback = "warehouse-fallback:cache";
220
+ }
221
+ }
222
+ if (!warehouseId) {
223
+ await upsertLog(supabase, connectionId, salesOrderId, {
224
+ status: "skipped",
225
+ reason: "no-warehouse",
226
+ });
227
+ return { status: "skipped", reason: "no-warehouse" };
228
+ }
229
+ // Load Floriday-issued fulfillment requests from the matching delivery order.
230
+ const deliveryCtx = await loadDeliveryContext(supabase, connectionId, salesOrderId);
231
+ if (!deliveryCtx || deliveryCtx.requests.length === 0) {
232
+ await upsertLog(supabase, connectionId, salesOrderId, {
233
+ status: "skipped",
234
+ reason: "no-delivery-order",
235
+ });
236
+ return { status: "skipped", reason: "no-delivery-order" };
237
+ }
238
+ // Prefer the delivery order's despatchWarehouseId when Floriday supplies it.
239
+ const effectiveWarehouseId = deliveryCtx.despatchWarehouseId || warehouseId;
240
+ const { payload, warnings } = buildFulfillmentPayload(order.data, effectiveWarehouseId, deliveryCtx);
241
+ if (warehouseFallback && !deliveryCtx.despatchWarehouseId)
242
+ warnings.push(warehouseFallback);
243
+ // Increment attempts.
244
+ const { data: existing } = await supabase
245
+ .from("floriday_auto_fulfillment_log")
246
+ .select("attempts")
247
+ .eq("connection_id", connectionId)
248
+ .eq("sales_order_id", salesOrderId)
249
+ .maybeSingle();
250
+ const attempts = Number(existing?.attempts ?? 0) + 1;
251
+ const env = (conn.environment === "production" || conn.environment === "live" ? "live" : "staging");
252
+ const result = await florydayWrite(supabase, env, {
253
+ module: "warehouse",
254
+ action: "add-fulfillment-order",
255
+ method: "POST",
256
+ path: "/fulfillment-orders",
257
+ referenceId: salesOrderId,
258
+ body: payload,
259
+ }, null);
260
+ if (!result.ok) {
261
+ await upsertLog(supabase, connectionId, salesOrderId, {
262
+ status: "failed",
263
+ attempts,
264
+ error: (result.error ?? `HTTP ${result.status}`).slice(0, 500),
265
+ reason: warnings.join(",") || null,
266
+ });
267
+ return { status: "failed", reason: result.error };
268
+ }
269
+ const fulfillmentOrderId = String(result.data?.fulfillmentOrderId ?? result.data?.id ?? payload.fulfillmentOrderId ?? "");
270
+ await upsertLog(supabase, connectionId, salesOrderId, {
271
+ status: "created",
272
+ attempts,
273
+ fulfillment_order_id: fulfillmentOrderId || null,
274
+ error: null,
275
+ reason: warnings.join(",") || null,
276
+ });
277
+ return { status: "created", fulfillmentOrderId };
278
+ }
279
+ export async function processRecentSalesOrders(supabase, connectionId, sinceIso) {
280
+ // 1) Recently synced / updated sales orders in this cycle.
281
+ const { data: recent } = await supabase
282
+ .from("floriday_salesorders_cache")
283
+ .select("connection_id, floriday_id, status, is_deleted, data, updated_at")
284
+ .eq("connection_id", connectionId)
285
+ .gte("updated_at", sinceIso)
286
+ .limit(200);
287
+ const rowMap = new Map();
288
+ for (const r of (recent ?? [])) {
289
+ rowMap.set(r.floriday_id, r);
290
+ }
291
+ // 2) Backlog: sales orders that never got a log row, or were previously
292
+ // skipped/failed because the preferred warehouse wasn't set yet.
293
+ const { data: allOrders } = await supabase
294
+ .from("floriday_salesorders_cache")
295
+ .select("connection_id, floriday_id, status, is_deleted, data")
296
+ .eq("connection_id", connectionId)
297
+ .eq("is_deleted", false)
298
+ .limit(500);
299
+ const orderList = allOrders ?? [];
300
+ if (orderList.length > 0) {
301
+ const ids = orderList.map((o) => o.floriday_id);
302
+ const { data: logs } = await supabase
303
+ .from("floriday_auto_fulfillment_log")
304
+ .select("sales_order_id, status, reason")
305
+ .eq("connection_id", connectionId)
306
+ .in("sales_order_id", ids);
307
+ const logMap = new Map();
308
+ for (const l of (logs ?? [])) {
309
+ logMap.set(l.sales_order_id, { status: l.status, reason: l.reason });
310
+ }
311
+ for (const o of orderList) {
312
+ const existing = logMap.get(o.floriday_id);
313
+ if (!existing) {
314
+ rowMap.set(o.floriday_id, o);
315
+ continue;
316
+ }
317
+ if (existing.status === "failed") {
318
+ rowMap.set(o.floriday_id, o);
319
+ continue;
320
+ }
321
+ if (existing.status === "skipped" &&
322
+ (existing.reason === "no-preferred-warehouse" ||
323
+ existing.reason === "no-warehouse" ||
324
+ existing.reason === "no-delivery-order")) {
325
+ rowMap.set(o.floriday_id, o);
326
+ }
327
+ }
328
+ }
329
+ const rows = Array.from(rowMap.values());
330
+ let created = 0;
331
+ let failed = 0;
332
+ let skipped = 0;
333
+ for (const row of rows) {
334
+ try {
335
+ const r = await processOrderForAutoFulfillment(supabase, row);
336
+ if (r.status === "created")
337
+ created++;
338
+ else if (r.status === "failed")
339
+ failed++;
340
+ else
341
+ skipped++;
342
+ }
343
+ catch (err) {
344
+ failed++;
345
+ await upsertLog(supabase, row.connection_id, row.floriday_id, {
346
+ status: "failed",
347
+ error: err.message?.slice(0, 500) ?? "unknown error",
348
+ });
349
+ }
350
+ }
351
+ return { processed: rows.length, created, failed, skipped };
352
+ }