@flowselections/floriday-afhandeling-module 1.0.42 → 1.0.43

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 (92) hide show
  1. package/dist-lib/components/afhandeling/AutoProcessStatus.d.ts +3 -1
  2. package/dist-lib/components/afhandeling/AutoProcessStatus.d.ts.map +1 -1
  3. package/dist-lib/components/afhandeling/AutoProcessStatus.js +36 -3
  4. package/dist-lib/components/afhandeling/ControleLijstPrint.js +54 -54
  5. package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -1
  6. package/dist-lib/components/afhandeling/ControlePage.js +23 -25
  7. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts +1 -1
  8. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -1
  9. package/dist-lib/components/afhandeling/DagDetailDialog.js +410 -121
  10. package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -1
  11. package/dist-lib/components/afhandeling/PickAgendaView.js +4 -4
  12. package/dist-lib/components/afhandeling/PickingPage.d.ts.map +1 -1
  13. package/dist-lib/components/afhandeling/PickingPage.js +1 -1
  14. package/dist-lib/components/afhandeling/ScanControleView.d.ts +2 -0
  15. package/dist-lib/components/afhandeling/ScanControleView.d.ts.map +1 -0
  16. package/dist-lib/components/afhandeling/ScanControleView.js +158 -0
  17. package/dist-lib/components/afhandeling/VervoerdersPage.js +1 -1
  18. package/dist-lib/components/afhandeling/VerzetOrderDialog.js +1 -1
  19. package/dist-lib/components/afhandeling/WeekAgendaView.js +1 -1
  20. package/dist-lib/components/pickstation/PakbonTemplate.js +70 -70
  21. package/dist-lib/hooks/useCartIndex.d.ts.map +1 -1
  22. package/dist-lib/hooks/useCartIndex.js +6 -1
  23. package/dist-lib/hooks/useEngineTasks.js +1 -1
  24. package/dist-lib/hooks/useKnownOrders.d.ts +11 -0
  25. package/dist-lib/hooks/useKnownOrders.d.ts.map +1 -0
  26. package/dist-lib/hooks/useKnownOrders.js +54 -0
  27. package/dist-lib/hooks/useOrderBuffers.d.ts +17 -0
  28. package/dist-lib/hooks/useOrderBuffers.d.ts.map +1 -0
  29. package/dist-lib/hooks/useOrderBuffers.js +53 -0
  30. package/dist-lib/hooks/useOrderPickDates.d.ts +10 -0
  31. package/dist-lib/hooks/useOrderPickDates.d.ts.map +1 -0
  32. package/dist-lib/hooks/useOrderPickDates.js +62 -0
  33. package/dist-lib/hooks/useOrderPlanning.d.ts +1 -3
  34. package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
  35. package/dist-lib/hooks/useOrderPlanning.js +9 -13
  36. package/dist-lib/hooks/usePickAgenda.d.ts +0 -5
  37. package/dist-lib/hooks/usePickAgenda.d.ts.map +1 -1
  38. package/dist-lib/hooks/usePickAgenda.js +62 -9
  39. package/dist-lib/hooks/usePickMode.d.ts +10 -0
  40. package/dist-lib/hooks/usePickMode.d.ts.map +1 -0
  41. package/dist-lib/hooks/usePickMode.js +31 -0
  42. package/dist-lib/index.d.ts +2 -0
  43. package/dist-lib/index.d.ts.map +1 -1
  44. package/dist-lib/index.js +19 -6
  45. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2027 -86
  46. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  47. package/dist-lib/integrations/supabase/client.server.d.ts +2027 -86
  48. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  49. package/dist-lib/integrations/supabase/client.server.js +1 -1
  50. package/dist-lib/integrations/supabase/moduleClient.d.ts +2027 -86
  51. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  52. package/dist-lib/integrations/supabase/types.d.ts +2097 -114
  53. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  54. package/dist-lib/integrations/supabase/types.js +1 -0
  55. package/dist-lib/lib/afhandelingSettings.functions.d.ts +4018 -140
  56. package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
  57. package/dist-lib/lib/carrierSignupMail.server.js +26 -26
  58. package/dist-lib/lib/carriers.functions.d.ts +10072 -367
  59. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  60. package/dist-lib/lib/cartAssignments.functions.d.ts +10072 -367
  61. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
  62. package/dist-lib/lib/cartControl.functions.d.ts +45879 -579
  63. package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
  64. package/dist-lib/lib/cartControl.functions.js +134 -10
  65. package/dist-lib/lib/deliveryNote.functions.d.ts +25891 -0
  66. package/dist-lib/lib/deliveryNote.functions.d.ts.map +1 -0
  67. package/dist-lib/lib/deliveryNote.functions.js +32 -0
  68. package/dist-lib/lib/engineLayouts.functions.d.ts +25889 -0
  69. package/dist-lib/lib/engineLayouts.functions.d.ts.map +1 -0
  70. package/dist-lib/lib/engineLayouts.functions.js +42 -0
  71. package/dist-lib/lib/floriday.functions.d.ts +27885 -52
  72. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  73. package/dist-lib/lib/floriday.functions.js +118 -343
  74. package/dist-lib/lib/logisticsEngine/reschedule.d.ts +17 -0
  75. package/dist-lib/lib/logisticsEngine/reschedule.d.ts.map +1 -1
  76. package/dist-lib/lib/logisticsEngine/reschedule.js +63 -0
  77. package/dist-lib/lib/orderFulfilment.functions.d.ts +25891 -0
  78. package/dist-lib/lib/orderFulfilment.functions.d.ts.map +1 -0
  79. package/dist-lib/lib/orderFulfilment.functions.js +35 -0
  80. package/dist-lib/lib/orderStatus.d.ts +10 -0
  81. package/dist-lib/lib/orderStatus.d.ts.map +1 -0
  82. package/dist-lib/lib/orderStatus.js +81 -0
  83. package/dist-lib/lib/pickDates.functions.d.ts +25896 -0
  84. package/dist-lib/lib/pickDates.functions.d.ts.map +1 -0
  85. package/dist-lib/lib/pickDates.functions.js +56 -0
  86. package/dist-lib/lib/pickMode.d.ts +14 -0
  87. package/dist-lib/lib/pickMode.d.ts.map +1 -0
  88. package/dist-lib/lib/pickMode.js +103 -0
  89. package/dist-lib/lib/printing.d.ts +3 -1
  90. package/dist-lib/lib/printing.d.ts.map +1 -1
  91. package/dist-lib/lib/printing.js +1 -0
  92. package/package.json +1 -1
@@ -1,83 +1,6 @@
1
1
  import { createServerFn } from "@tanstack/react-start";
2
2
  import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
3
- import { normalizeOrderNumber } from "./orderRefs";
4
- function pickStr(r, ...keys) {
5
- if (!r)
6
- return null;
7
- for (const k of keys) {
8
- const v = r[k];
9
- if (typeof v === "string" && v.length > 0)
10
- return v;
11
- }
12
- return null;
13
- }
14
- function pickDeliveryDate(raw) {
15
- const delivery = raw.delivery;
16
- const candidates = [
17
- delivery?.latestDeliveryDateTime,
18
- raw.deliveryDate,
19
- raw.requestedDeliveryDate,
20
- raw.expectedDeliveryDate,
21
- raw.loadingDateTime,
22
- raw.deliveryDateTime,
23
- ];
24
- const found = candidates.find((v) => typeof v === "string" && v);
25
- return found ?? null;
26
- }
27
- function pickAddress(raw) {
28
- const buyer = raw.buyer;
29
- const customer = raw.customer;
30
- const delivery = raw.delivery;
31
- const addr = raw.deliveryAddress ??
32
- raw.shipTo ??
33
- delivery?.address ??
34
- buyer?.address ??
35
- customer?.address;
36
- return {
37
- country: pickStr(addr, "countryCode", "country") ?? pickStr(raw, "deliveryCountry", "countryCode"),
38
- province: pickStr(addr, "province", "state", "region"),
39
- city: pickStr(addr, "city", "locality"),
40
- };
41
- }
42
- function pickDeliveryType(raw) {
43
- const delivery = raw.delivery;
44
- return (raw.tradeInstrument ??
45
- delivery?.incoterm ??
46
- raw.deliveryType ??
47
- "—");
48
- }
49
- function pickTotal(raw) {
50
- const calc = raw.calculatedFields;
51
- const orderAmount = calc?.orderAmount;
52
- if (orderAmount && typeof orderAmount.value === "number") {
53
- return { amount: orderAmount.value, currency: orderAmount.currency ?? null };
54
- }
55
- const ppp = raw.pricePerPiece;
56
- const pieces = typeof raw.numberOfPieces === "number" ? raw.numberOfPieces : null;
57
- if (ppp && typeof ppp.value === "number" && pieces !== null) {
58
- return { amount: ppp.value * pieces, currency: ppp.currency ?? null };
59
- }
60
- return { amount: null, currency: null };
61
- }
62
- async function getActiveConnectionId(supabase) {
63
- const { data: settings } = await supabase
64
- .from("floriday_settings")
65
- .select("active_environment")
66
- .eq("id", 1)
67
- .maybeSingle();
68
- const env = settings?.active_environment ?? null;
69
- let q = supabase
70
- .from("floriday_connections")
71
- .select("id")
72
- .eq("is_active", true)
73
- .order("updated_at", { ascending: false })
74
- .limit(1);
75
- if (env)
76
- q = q.eq("environment", env);
77
- const { data: conn } = await q.maybeSingle();
78
- return conn?.id ?? null;
79
- }
80
- const DELIVERY_PATH = "data->delivery->>latestDeliveryDateTime";
3
+ import { isVisibleOrderStatus } from "./orderStatus";
81
4
  function dayKey(d) {
82
5
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
83
6
  }
@@ -88,28 +11,26 @@ function defaultRange() {
88
11
  return { from: dayKey(from), to: dayKey(to) };
89
12
  }
90
13
  /** Eerste leverdatum ná het venster, anders de laatste ervóór. Voor auto-jump. */
91
- async function findNearestDeliveryDate(supabase, connectionId, range) {
92
- const base = () => {
93
- let q = supabase
94
- .from("floriday_salesorders_cache")
95
- .select("dd:data->delivery->>latestDeliveryDateTime")
96
- .eq("is_deleted", false)
97
- .limit(1);
98
- if (connectionId)
99
- q = q.eq("connection_id", connectionId);
100
- return q;
101
- };
14
+ async function findNearestDeliveryDate(supabase, range) {
102
15
  try {
103
- const { data: next } = await base()
104
- .gt(DELIVERY_PATH, `${range.to}T23:59:59Z`)
105
- .order(DELIVERY_PATH, { ascending: true });
106
- const nextDate = next?.[0]?.dd;
16
+ const { data: next } = await supabase
17
+ .from("orders")
18
+ .select("delivery_date")
19
+ .is("deleted_at", null)
20
+ .gt("delivery_date", range.to)
21
+ .order("delivery_date", { ascending: true })
22
+ .limit(1);
23
+ const nextDate = next?.[0]?.delivery_date;
107
24
  if (nextDate)
108
25
  return nextDate;
109
- const { data: prev } = await base()
110
- .lt(DELIVERY_PATH, `${range.from}T00:00:00Z`)
111
- .order(DELIVERY_PATH, { ascending: false });
112
- return prev?.[0]?.dd ?? null;
26
+ const { data: prev } = await supabase
27
+ .from("orders")
28
+ .select("delivery_date")
29
+ .is("deleted_at", null)
30
+ .lt("delivery_date", range.from)
31
+ .order("delivery_date", { ascending: false })
32
+ .limit(1);
33
+ return prev?.[0]?.delivery_date ?? null;
113
34
  }
114
35
  catch (e) {
115
36
  console.warn("[planning-orders] nearest delivery date lookup failed", {
@@ -118,6 +39,12 @@ async function findNearestDeliveryDate(supabase, connectionId, range) {
118
39
  return null;
119
40
  }
120
41
  }
42
+ /**
43
+ * Orders van deze module. De orderadministratie van de Verkoop-module
44
+ * (public.orders) is de ENIGE bron: staat een order daar niet in, dan wordt hij
45
+ * hier ook niet getoond. De ruwe Floriday-ordercache wordt alleen nog gebruikt
46
+ * om extra gegevens (stickers) op te zoeken bij orders die al bestaan.
47
+ */
121
48
  export const getFloridayOrders = createServerFn({ method: "GET" })
122
49
  .middleware([requireSupabaseAuth])
123
50
  .inputValidator((input) => input ?? {})
@@ -133,199 +60,29 @@ export const getFloridayOrders = createServerFn({ method: "GET" })
133
60
  at: new Date().toISOString(),
134
61
  userId: context.userId,
135
62
  range,
136
- sources: ["floriday_salesorders_cache", "orders"],
63
+ sources: ["orders"],
137
64
  });
138
- const connectionId = await getActiveConnectionId(supabase);
139
- let ordersQuery = supabase
140
- .from("floriday_salesorders_cache")
141
- .select("floriday_id, status, buyer_organization_id, data, updated_at")
142
- .eq("is_deleted", false)
143
- .gte(DELIVERY_PATH, `${range.from}T00:00:00Z`)
144
- .lte(DELIVERY_PATH, `${range.to}T23:59:59Z`)
145
- .order(DELIVERY_PATH, { ascending: true })
146
- .limit(1500);
147
- if (connectionId)
148
- ordersQuery = ordersQuery.eq("connection_id", connectionId);
149
- const { data: rows, error } = await ordersQuery;
150
- if (error) {
151
- console.error("[planning-orders] floriday cache query failed", {
152
- message: error.message,
153
- code: error.code,
154
- details: error.details,
155
- connectionId,
156
- });
157
- throw new Error(`Kon Floriday-orders niet laden: ${error.message}`);
158
- }
159
- // Sommige rijen hebben de kolom buyer_organization_id (nog) niet gevuld
160
- // terwijl de JSON `customerOrganizationId` wel bevat. Vul lokaal aan zodat
161
- // deze orders óók een klantnaam krijgen en gewoon meegepland worden.
162
- const buyerOrgByFloridayId = new Map();
163
- for (const r of rows ?? []) {
164
- const fid = r.floriday_id ?? undefined;
165
- if (!fid)
166
- continue;
167
- const fromCol = r.buyer_organization_id ?? null;
168
- const data = (r.data ?? {});
169
- const fromData = data.customerOrganizationId ??
170
- data.buyerOrganizationId ??
171
- null;
172
- const org = fromCol ?? fromData;
173
- if (org)
174
- buyerOrgByFloridayId.set(fid, org);
175
- }
176
- const buyerIds = Array.from(new Set(Array.from(buyerOrgByFloridayId.values())));
177
- const nameByOrgId = new Map();
178
- if (buyerIds.length > 0) {
179
- // Via de geïndexeerde kolom organization_id; het JSON-pad (data->>'organizationId')
180
- // dwingt een seq scan over ~24k rijen af en kost seconden.
181
- const CHUNK = 200;
182
- for (let i = 0; i < buyerIds.length; i += CHUNK) {
183
- const { data: customers, error: custErr } = await supabase
184
- .from("floriday_customers_cache")
185
- .select("organization_id, data")
186
- .in("organization_id", buyerIds.slice(i, i + CHUNK));
187
- if (custErr) {
188
- console.warn("[planning-orders] customer lookup failed", { message: custErr.message });
189
- break;
190
- }
191
- for (const c of customers ?? []) {
192
- const d = (c.data ?? {});
193
- const orgId = c.organization_id ??
194
- d.organizationId ??
195
- null;
196
- const name = d.commercialName ??
197
- d.name ??
198
- d.company_name ??
199
- d.commercial_name;
200
- if (orgId && name)
201
- nameByOrgId.set(orgId, name);
202
- }
203
- }
204
- }
205
- // Reserve/lookup interne FO-nummers voor alle Floriday-orders (centrale teller).
206
- // Eerst bestaande mapping ophalen; alleen voor onbekende IDs de RPC aanroepen
207
- // (die per ID een insert/counter-update doet en anders een statement timeout geeft).
208
- const floridayIds = (rows ?? [])
209
- .map((r) => r.floriday_id ?? null)
210
- .filter((v) => !!v);
211
- const foNumberById = new Map();
212
- if (floridayIds.length > 0) {
213
- const { data: existing, error: existErr } = await supabase
214
- .from("floriday_order_number_map")
215
- .select("floriday_id, order_number")
216
- .in("floriday_id", floridayIds);
217
- if (existErr) {
218
- console.warn("[planning-orders] floriday_order_number_map lookup failed", {
219
- message: existErr.message,
220
- });
221
- }
222
- else {
223
- for (const m of (existing ?? [])) {
224
- foNumberById.set(m.floriday_id, m.order_number);
225
- }
226
- }
227
- const missing = floridayIds.filter((id) => !foNumberById.has(id));
228
- if (missing.length > 0) {
229
- const { data: mapped, error: mapErr } = await supabase.rpc("assign_floriday_order_numbers", { p_ids: missing });
230
- if (mapErr) {
231
- console.warn("[planning-orders] assign_floriday_order_numbers failed", {
232
- message: mapErr.message,
233
- missingCount: missing.length,
234
- });
235
- }
236
- else {
237
- for (const m of (mapped ?? [])) {
238
- foNumberById.set(m.floriday_id, m.order_number);
239
- }
240
- }
241
- }
242
- }
243
- // Bepaal welke Floriday-orders klant-stickers hebben.
244
- const stickerOrderIds = new Set();
245
- if (floridayIds.length > 0) {
246
- const { data: stickerRows, error: stickerErr } = await supabase
247
- .from("floriday_customer_stickers_cache")
248
- .select("sales_order_id, quantity, is_deleted")
249
- .in("sales_order_id", floridayIds)
250
- .eq("is_deleted", false);
251
- if (stickerErr) {
252
- console.warn("[planning-orders] sticker lookup failed", {
253
- message: stickerErr.message,
254
- });
255
- }
256
- else {
257
- for (const s of stickerRows ?? []) {
258
- const soId = s.sales_order_id ?? null;
259
- const qty = Number(s.quantity ?? 1);
260
- if (soId && (!Number.isFinite(qty) || qty > 0))
261
- stickerOrderIds.add(soId);
262
- }
263
- }
264
- }
265
- const orders = (rows ?? []).map((row) => {
266
- const r = (row.data ?? {});
267
- const total = pickTotal(r);
268
- const floridayId = row.floriday_id ??
269
- r.salesOrderId ??
270
- crypto.randomUUID();
271
- const orgId = buyerOrgByFloridayId.get(floridayId) ?? null;
272
- const fallbackNumber = pickStr(r, "customerOrderId", "salesChannelOrderId", "salesOrderNumber") ?? "—";
273
- // cartCount uit packingConfiguration afleiden zodat Floriday-orders
274
- // niet standaard op 1 kar blijven staan.
275
- let cartCount = null;
276
- let plantCount = null;
277
- const pieces = typeof r.numberOfPieces === "number" ? r.numberOfPieces : null;
278
- if (pieces != null && Number.isFinite(pieces) && pieces > 0) {
279
- plantCount = pieces;
280
- const pkg = (r.packingConfiguration ?? {});
281
- const perPkg = Number(pkg.piecesPerPackage ?? 0);
282
- const pkgsPerLayer = Number(pkg.packagesPerLayer ?? 0);
283
- const layers = Number(pkg.layersPerLoadCarrier ?? 0);
284
- const perCart = perPkg > 0 && pkgsPerLayer > 0 && layers > 0
285
- ? perPkg * pkgsPerLayer * layers
286
- : 0;
287
- if (perCart > 0)
288
- cartCount = Math.max(1, Math.ceil(pieces / perCart));
289
- }
290
- return {
291
- id: floridayId,
292
- source: "floriday",
293
- orderNumber: foNumberById.get(floridayId) ?? fallbackNumber,
294
- customerName: (orgId && nameByOrgId.get(orgId)) || "—",
295
- deliveryDate: pickDeliveryDate(r),
296
- deliveryType: pickDeliveryType(r),
297
- status: row.status ?? r.status ?? "—",
298
- lineCount: 1,
299
- cartCount,
300
- plantCount,
301
- totalAmount: total.amount,
302
- currency: total.currency,
303
- createdAt: pickStr(r, "creationDateTime", "orderDateTime", "createDateTime") ?? null,
304
- hasStickers: stickerOrderIds.has(floridayId),
305
- salesOrderId: floridayId,
306
- };
307
- });
308
- // Ook handmatige / mailbox-orders uit public.orders meenemen — alleen
309
- // binnen hetzelfde leverdatumvenster.
310
- const { data: manualRows, error: manualError } = await supabase
65
+ const { data: orderRows, error: ordersError } = await supabase
311
66
  .from("orders")
312
67
  .select("id, order_number, customer_name, customer_id, delivery_date, status, total, created_at, custom_fields, order_items(id, quantity, products(plants_per_crate, crates_per_layer, layers_per_cart))")
313
68
  .is("deleted_at", null)
314
69
  .gte("delivery_date", range.from)
315
70
  .lte("delivery_date", range.to)
316
71
  .order("delivery_date", { ascending: true })
317
- .limit(1000);
318
- let partialError;
319
- if (manualError) {
320
- partialError = `Handmatige orders konden niet geladen worden: ${manualError.message}`;
321
- console.error("[planning-orders] manual orders query failed", {
322
- message: manualError.message,
323
- code: manualError.code,
324
- details: manualError.details,
72
+ .limit(2000);
73
+ if (ordersError) {
74
+ console.error("[planning-orders] orders query failed", {
75
+ message: ordersError.message,
76
+ code: ordersError.code,
77
+ details: ordersError.details,
325
78
  });
79
+ throw new Error(`Kon orders niet laden: ${ordersError.message}`);
326
80
  }
327
- const manualCustomerIds = Array.from(new Set((manualRows ?? []).map((row) => row.customer_id).filter((v) => !!v)));
328
- const manualDeliveryLocationIds = Array.from(new Set((manualRows ?? [])
81
+ // Orders die in de Verkoop-module open of net afgehandeld zijn, blijven
82
+ // zichtbaar; geannuleerde/vervallen/concept/gereserveerde orders niet.
83
+ const rows = (orderRows ?? []).filter((row) => isVisibleOrderStatus(row.status));
84
+ const customerIds = Array.from(new Set(rows.map((row) => row.customer_id).filter((v) => !!v)));
85
+ const deliveryLocationIds = Array.from(new Set(rows
329
86
  .map((row) => {
330
87
  const fields = (row.custom_fields ?? {});
331
88
  const id = fields.delivery_location;
@@ -333,21 +90,21 @@ export const getFloridayOrders = createServerFn({ method: "GET" })
333
90
  })
334
91
  .filter((v) => !!v)));
335
92
  const customerCountryById = new Map();
336
- if (manualCustomerIds.length > 0) {
93
+ if (customerIds.length > 0) {
337
94
  const { data: customers } = await supabase
338
95
  .from("customers")
339
96
  .select("id, country")
340
- .in("id", manualCustomerIds);
97
+ .in("id", customerIds);
341
98
  for (const c of customers ?? []) {
342
99
  customerCountryById.set(c.id, c.country ?? null);
343
100
  }
344
101
  }
345
102
  const deliveryLocationById = new Map();
346
- if (manualDeliveryLocationIds.length > 0) {
103
+ if (deliveryLocationIds.length > 0) {
347
104
  const { data: locations } = await supabase
348
105
  .from("customer_delivery_locations")
349
106
  .select("id, country, province, city")
350
- .in("id", manualDeliveryLocationIds);
107
+ .in("id", deliveryLocationIds);
351
108
  for (const loc of locations ?? []) {
352
109
  deliveryLocationById.set(loc.id, {
353
110
  country: loc.country ?? null,
@@ -356,7 +113,36 @@ export const getFloridayOrders = createServerFn({ method: "GET" })
356
113
  });
357
114
  }
358
115
  }
359
- const manualOrders = (manualRows ?? []).map((row) => {
116
+ // Floriday sales-order-id's van de orders die de Verkoop-module kent.
117
+ const salesOrderIdByOrderId = new Map();
118
+ for (const row of rows) {
119
+ const fields = (row.custom_fields ?? {});
120
+ const fid = fields.floriday_sales_order_id;
121
+ if (typeof fid === "string" && fid)
122
+ salesOrderIdByOrderId.set(row.id, fid);
123
+ }
124
+ // Stickers alleen opzoeken voor die bekende Floriday-orders.
125
+ const stickerOrderIds = new Set();
126
+ const salesOrderIds = Array.from(new Set(salesOrderIdByOrderId.values()));
127
+ if (salesOrderIds.length > 0) {
128
+ const { data: stickerRows, error: stickerErr } = await supabase
129
+ .from("floriday_customer_stickers_cache")
130
+ .select("sales_order_id, quantity, is_deleted")
131
+ .in("sales_order_id", salesOrderIds)
132
+ .eq("is_deleted", false);
133
+ if (stickerErr) {
134
+ console.warn("[planning-orders] sticker lookup failed", { message: stickerErr.message });
135
+ }
136
+ else {
137
+ for (const s of stickerRows ?? []) {
138
+ const soId = s.sales_order_id ?? null;
139
+ const qty = Number(s.quantity ?? 1);
140
+ if (soId && (!Number.isFinite(qty) || qty > 0))
141
+ stickerOrderIds.add(soId);
142
+ }
143
+ }
144
+ }
145
+ const orders = rows.map((row) => {
360
146
  const totalNum = row.total != null ? Number(row.total) : NaN;
361
147
  const items = (row.order_items ?? []);
362
148
  let cartCount = null;
@@ -370,22 +156,26 @@ export const getFloridayOrders = createServerFn({ method: "GET" })
370
156
  const perCart = p && p.plants_per_crate && p.crates_per_layer && p.layers_per_cart
371
157
  ? Number(p.plants_per_crate) * Number(p.crates_per_layer) * Number(p.layers_per_cart)
372
158
  : 0;
373
- if (perCart > 0) {
159
+ if (perCart > 0)
374
160
  cartCount = (cartCount ?? 0) + Math.ceil(qty / perCart);
375
- }
376
161
  }
377
162
  const fields = (row.custom_fields ?? {});
378
163
  const deliveryLocationId = typeof fields.delivery_location === "string" ? fields.delivery_location : null;
379
- const deliveryLocation = deliveryLocationId ? deliveryLocationById.get(deliveryLocationId) : undefined;
164
+ const deliveryLocation = deliveryLocationId
165
+ ? deliveryLocationById.get(deliveryLocationId)
166
+ : undefined;
380
167
  const customerId = row.customer_id ?? null;
381
168
  const carrierId = typeof fields.carrier === "string" && fields.carrier ? fields.carrier : null;
169
+ const orderId = row.id;
170
+ const salesOrderId = salesOrderIdByOrderId.get(orderId) ?? null;
382
171
  return {
383
- id: row.id,
172
+ id: orderId,
173
+ // Orders komen altijd uit public.orders; regels staan in order_items.
384
174
  source: "manual",
385
175
  orderNumber: row.order_number ?? "—",
386
176
  customerName: row.customer_name ?? "—",
387
177
  deliveryDate: row.delivery_date ?? null,
388
- deliveryType: "Mailbox",
178
+ deliveryType: salesOrderId ? "Floriday" : "Mailbox",
389
179
  status: row.status ?? "—",
390
180
  lineCount: items.length,
391
181
  cartCount,
@@ -394,65 +184,50 @@ export const getFloridayOrders = createServerFn({ method: "GET" })
394
184
  currency: null,
395
185
  createdAt: row.created_at ?? null,
396
186
  carrierId,
397
- country: deliveryLocation?.country ?? (customerId ? customerCountryById.get(customerId) ?? null : null),
187
+ country: deliveryLocation?.country ??
188
+ (customerId ? customerCountryById.get(customerId) ?? null : null),
398
189
  province: deliveryLocation?.province ?? null,
399
190
  city: deliveryLocation?.city ?? null,
191
+ hasStickers: salesOrderId ? stickerOrderIds.has(salesOrderId) : false,
192
+ salesOrderId,
193
+ mirrored: true,
194
+ mirroredOrderId: orderId,
400
195
  };
401
196
  });
402
- // Verkoop-module schrijft Floriday-orders óók in public.orders. Zonder dedup
403
- // verschijnt elke order dubbel: één keer vanuit de cache, één keer vanuit
404
- // orders. De orders-versie wint (juist FO-nummer, juiste klantnaam).
405
- //
406
- // Dedup op DRIE sleutels, want custom_fields.floriday_sales_order_id is niet
407
- // gegarandeerd gevuld: (1) floriday_sales_order_id, (2) genormaliseerd
408
- // ordernummer, (3) orders.id gelijk aan het cache-id. Cache-rijen zonder
409
- // pendant blijven staan en worden gemarkeerd als niet-gemirrord.
410
- const manualIdByFloridayId = new Map();
411
- const manualIdByNumber = new Map();
412
- const manualIds = new Set();
413
- for (const row of manualRows ?? []) {
414
- const fields = (row.custom_fields ?? {});
415
- const fid = fields.floriday_sales_order_id;
416
- const rowId = row.id;
417
- manualIds.add(rowId);
418
- if (typeof fid === "string" && fid)
419
- manualIdByFloridayId.set(fid, rowId);
420
- const num = normalizeOrderNumber(row.order_number);
421
- if (num && !manualIdByNumber.has(num))
422
- manualIdByNumber.set(num, rowId);
423
- }
424
- const dedupedFloriday = [];
425
- for (const o of orders) {
426
- const mirroredId = manualIdByFloridayId.get(o.id) ??
427
- manualIdByNumber.get(normalizeOrderNumber(o.orderNumber) ?? "\u0000") ??
428
- (manualIds.has(o.id) ? o.id : null) ??
429
- null;
430
- if (mirroredId)
431
- continue; // pendant in public.orders bestaat al
432
- dedupedFloriday.push({ ...o, mirrored: false, mirroredOrderId: null });
433
- }
434
- // Laatste net: nooit twee items met dezelfde ref-sleutel.
435
- const seenRefs = new Set();
436
- const combined = [];
437
- for (const o of [
438
- ...manualOrders.map((m) => ({ ...m, mirrored: true, mirroredOrderId: m.id })),
439
- ...dedupedFloriday,
440
- ]) {
441
- const key = normalizeOrderNumber(o.orderNumber) ?? o.id;
442
- if (seenRefs.has(key))
443
- continue;
444
- seenRefs.add(key);
445
- combined.push(o);
446
- }
447
- const nearestDeliveryDate = combined.length === 0 ? await findNearestDeliveryDate(supabase, connectionId, range) : null;
197
+ const nearestDeliveryDate = orders.length === 0 ? await findNearestDeliveryDate(supabase, range) : null;
448
198
  console.info("[planning-orders] load completed", {
449
- floridayOrders: orders.length,
450
- manualOrders: manualOrders.length,
451
- totalOrders: combined.length,
199
+ totalOrders: orders.length,
452
200
  range,
453
201
  nearestDeliveryDate,
454
- partialError: partialError ?? null,
455
202
  durationMs: Date.now() - startedAt,
456
203
  });
457
- return { orders: combined, error: partialError, range, nearestDeliveryDate };
204
+ return { orders, range, nearestDeliveryDate };
205
+ });
206
+ /**
207
+ * Alle orders die in de Verkoop-module open of net afgehandeld zijn. Deze lijst
208
+ * bepaalt wat deze module überhaupt mag tonen: agenda, dagdetails, karren en
209
+ * controle filteren erop. Alleen geannuleerde, vervallen, concept- en
210
+ * gereserveerde orders verdwijnen hieruit.
211
+ */
212
+ export const listKnownOrders = createServerFn({ method: "GET" })
213
+ .middleware([requireSupabaseAuth])
214
+ .handler(async ({ context }) => {
215
+ const supabase = context.supabase;
216
+ const { data, error } = await supabase
217
+ .from("orders")
218
+ .select("id, order_number, status")
219
+ .is("deleted_at", null)
220
+ .limit(5000);
221
+ if (error) {
222
+ console.warn("[planning-orders] known orders lookup failed", { message: error.message });
223
+ return { orders: [], error: error.message };
224
+ }
225
+ return {
226
+ orders: (data ?? [])
227
+ .filter((r) => isVisibleOrderStatus(r.status))
228
+ .map((r) => ({
229
+ id: r.id,
230
+ orderNumber: r.order_number ?? null,
231
+ })),
232
+ };
458
233
  });
@@ -62,4 +62,21 @@ export declare function setPickDay(orderRef: string, day: string): Promise<{
62
62
  error?: string;
63
63
  updated: number;
64
64
  }>;
65
+ export interface NextDeliveryMoment {
66
+ date: string;
67
+ time: string;
68
+ label?: string | null;
69
+ }
70
+ /**
71
+ * Het eerstvolgende aflever-/ophaalmoment uit de agenda van de Engine
72
+ * (tabel logistics_pickup_moments). Momenten in het verleden vallen af.
73
+ */
74
+ export declare function getNextDeliveryMoment(): Promise<NextDeliveryMoment | null>;
75
+ /**
76
+ * Plant een order opnieuw in op het eerstvolgende aflevermoment.
77
+ * De Engine bepaalt op basis daarvan zelf de pickdag en de taken.
78
+ */
79
+ export declare function replanToNextDeliveryMoment(orderRef: string): Promise<RescheduleResult & {
80
+ moment?: NextDeliveryMoment;
81
+ }>;
65
82
  //# sourceMappingURL=reschedule.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reschedule.d.ts","sourceRoot":"","sources":["../../../src/lib/logisticsEngine/reschedule.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE,gBAAgB,CAAC;IAC3B,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,SAAS,CAAC,EAAE,SAAS,GAAG,OAAO,GAAG,MAAM,CAAC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAY1F;AAED,wBAAsB,eAAe,CACnC,KAAK,EAAE,eAAe,GAAG;IAAE,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAC7F,OAAO,CAAC,gBAAgB,CAAC,CAgB3B;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAY7F;AAID,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,+EAA+E;AAC/E,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAgB5E;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAezH"}
1
+ {"version":3,"file":"reschedule.d.ts","sourceRoot":"","sources":["../../../src/lib/logisticsEngine/reschedule.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@
3
3
  * Deze module rekent zelf niets uit en doet nooit een eigen UPDATE.
4
4
  */
5
5
  import { supabase } from "../../integrations/supabase/moduleClient";
6
+ import { amsterdamDay, formatAmsterdam } from "../timezone";
6
7
  export async function previewReschedule(input) {
7
8
  const { data, error } = await supabase.rpc("logistics_reschedule_preview", {
8
9
  p_order_ref: input.orderRef,
@@ -84,3 +85,65 @@ export async function setPickDay(orderRef, day) {
84
85
  updated: result?.updated ?? 0,
85
86
  };
86
87
  }
88
+ /**
89
+ * Het eerstvolgende aflever-/ophaalmoment uit de agenda van de Engine
90
+ * (tabel logistics_pickup_moments). Momenten in het verleden vallen af.
91
+ */
92
+ export async function getNextDeliveryMoment() {
93
+ const today = amsterdamDay(new Date());
94
+ const { data, error } = await supabase
95
+ .from("logistics_pickup_moments")
96
+ .select("moment_date, pickup_time, label, sequence")
97
+ .gte("moment_date", today)
98
+ .order("moment_date", { ascending: true })
99
+ .order("sequence", { ascending: true })
100
+ .order("pickup_time", { ascending: true })
101
+ .limit(50);
102
+ if (error) {
103
+ console.error("[reschedule] next moment failed", { message: error.message });
104
+ return null;
105
+ }
106
+ const nowTime = formatAmsterdam(new Date(), { hour: "2-digit", minute: "2-digit" });
107
+ for (const r of (data ?? [])) {
108
+ const time = (r.pickup_time ?? "").slice(0, 5);
109
+ if (r.moment_date === today && time && time <= nowTime)
110
+ continue;
111
+ return { date: r.moment_date, time, label: r.label ?? null };
112
+ }
113
+ // Geen ophaalmomenten ingesteld in de Engine: val terug op de eerstvolgende
114
+ // afleverdag uit de leveragenda van de Engine. Nog steeds Engine-data; deze
115
+ // module verzint zelf geen datum.
116
+ const { data: agenda, error: agendaError } = await supabase.rpc("logistics_delivery_agenda", {
117
+ p_from: today,
118
+ p_to: undefined,
119
+ });
120
+ if (agendaError) {
121
+ console.error("[reschedule] delivery agenda failed", { message: agendaError.message });
122
+ return null;
123
+ }
124
+ const rows = (Array.isArray(agenda) ? agenda : []);
125
+ const dates = rows
126
+ .map((r) => r.delivery_date ?? null)
127
+ .filter((d) => !!d && d > today)
128
+ .sort();
129
+ const first = dates[0];
130
+ return first ? { date: first, time: "", label: null } : null;
131
+ }
132
+ /**
133
+ * Plant een order opnieuw in op het eerstvolgende aflevermoment.
134
+ * De Engine bepaalt op basis daarvan zelf de pickdag en de taken.
135
+ */
136
+ export async function replanToNextDeliveryMoment(orderRef) {
137
+ const moment = await getNextDeliveryMoment();
138
+ if (!moment) {
139
+ return { ok: false, error: "Geen eerstvolgend aflevermoment bekend: er staan geen ophaalmomenten en geen toekomstige afleverdagen in de Logistics Engine." };
140
+ }
141
+ const res = await applyReschedule({
142
+ orderRef,
143
+ pickupDate: moment.date,
144
+ pickupFrom: moment.time || null,
145
+ pickupTo: null,
146
+ reason: "Handmatig ingepland op het eerstvolgende aflevermoment",
147
+ });
148
+ return { ...res, moment };
149
+ }