@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.
- package/dist-lib/components/afhandeling/AutoProcessStatus.d.ts +3 -1
- package/dist-lib/components/afhandeling/AutoProcessStatus.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/AutoProcessStatus.js +36 -3
- package/dist-lib/components/afhandeling/ControleLijstPrint.js +54 -54
- package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/ControlePage.js +23 -25
- package/dist-lib/components/afhandeling/DagDetailDialog.d.ts +1 -1
- package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/DagDetailDialog.js +410 -121
- package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/PickAgendaView.js +4 -4
- package/dist-lib/components/afhandeling/PickingPage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/PickingPage.js +1 -1
- package/dist-lib/components/afhandeling/ScanControleView.d.ts +2 -0
- package/dist-lib/components/afhandeling/ScanControleView.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/ScanControleView.js +158 -0
- package/dist-lib/components/afhandeling/VervoerdersPage.js +1 -1
- package/dist-lib/components/afhandeling/VerzetOrderDialog.js +1 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.js +1 -1
- package/dist-lib/components/pickstation/PakbonTemplate.js +70 -70
- package/dist-lib/hooks/useCartIndex.d.ts.map +1 -1
- package/dist-lib/hooks/useCartIndex.js +6 -1
- package/dist-lib/hooks/useEngineTasks.js +1 -1
- package/dist-lib/hooks/useKnownOrders.d.ts +11 -0
- package/dist-lib/hooks/useKnownOrders.d.ts.map +1 -0
- package/dist-lib/hooks/useKnownOrders.js +54 -0
- package/dist-lib/hooks/useOrderBuffers.d.ts +17 -0
- package/dist-lib/hooks/useOrderBuffers.d.ts.map +1 -0
- package/dist-lib/hooks/useOrderBuffers.js +53 -0
- package/dist-lib/hooks/useOrderPickDates.d.ts +10 -0
- package/dist-lib/hooks/useOrderPickDates.d.ts.map +1 -0
- package/dist-lib/hooks/useOrderPickDates.js +62 -0
- package/dist-lib/hooks/useOrderPlanning.d.ts +1 -3
- package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
- package/dist-lib/hooks/useOrderPlanning.js +9 -13
- package/dist-lib/hooks/usePickAgenda.d.ts +0 -5
- package/dist-lib/hooks/usePickAgenda.d.ts.map +1 -1
- package/dist-lib/hooks/usePickAgenda.js +62 -9
- package/dist-lib/hooks/usePickMode.d.ts +10 -0
- package/dist-lib/hooks/usePickMode.d.ts.map +1 -0
- package/dist-lib/hooks/usePickMode.js +31 -0
- package/dist-lib/index.d.ts +2 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +19 -6
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +2027 -86
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +2027 -86
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.js +1 -1
- package/dist-lib/integrations/supabase/moduleClient.d.ts +2027 -86
- package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +2097 -114
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +1 -0
- package/dist-lib/lib/afhandelingSettings.functions.d.ts +4018 -140
- package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
- package/dist-lib/lib/carrierSignupMail.server.js +26 -26
- package/dist-lib/lib/carriers.functions.d.ts +10072 -367
- package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartAssignments.functions.d.ts +10072 -367
- package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.d.ts +45879 -579
- package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.js +134 -10
- package/dist-lib/lib/deliveryNote.functions.d.ts +25891 -0
- package/dist-lib/lib/deliveryNote.functions.d.ts.map +1 -0
- package/dist-lib/lib/deliveryNote.functions.js +32 -0
- package/dist-lib/lib/engineLayouts.functions.d.ts +25889 -0
- package/dist-lib/lib/engineLayouts.functions.d.ts.map +1 -0
- package/dist-lib/lib/engineLayouts.functions.js +42 -0
- package/dist-lib/lib/floriday.functions.d.ts +27885 -52
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.js +118 -343
- package/dist-lib/lib/logisticsEngine/reschedule.d.ts +17 -0
- package/dist-lib/lib/logisticsEngine/reschedule.d.ts.map +1 -1
- package/dist-lib/lib/logisticsEngine/reschedule.js +63 -0
- package/dist-lib/lib/orderFulfilment.functions.d.ts +25891 -0
- package/dist-lib/lib/orderFulfilment.functions.d.ts.map +1 -0
- package/dist-lib/lib/orderFulfilment.functions.js +35 -0
- package/dist-lib/lib/orderStatus.d.ts +10 -0
- package/dist-lib/lib/orderStatus.d.ts.map +1 -0
- package/dist-lib/lib/orderStatus.js +81 -0
- package/dist-lib/lib/pickDates.functions.d.ts +25896 -0
- package/dist-lib/lib/pickDates.functions.d.ts.map +1 -0
- package/dist-lib/lib/pickDates.functions.js +56 -0
- package/dist-lib/lib/pickMode.d.ts +14 -0
- package/dist-lib/lib/pickMode.d.ts.map +1 -0
- package/dist-lib/lib/pickMode.js +103 -0
- package/dist-lib/lib/printing.d.ts +3 -1
- package/dist-lib/lib/printing.d.ts.map +1 -1
- package/dist-lib/lib/printing.js +1 -0
- 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 {
|
|
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,
|
|
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
|
|
104
|
-
.
|
|
105
|
-
.
|
|
106
|
-
|
|
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
|
|
110
|
-
.
|
|
111
|
-
.
|
|
112
|
-
|
|
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: ["
|
|
63
|
+
sources: ["orders"],
|
|
137
64
|
});
|
|
138
|
-
const
|
|
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(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
328
|
-
|
|
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 (
|
|
93
|
+
if (customerIds.length > 0) {
|
|
337
94
|
const { data: customers } = await supabase
|
|
338
95
|
.from("customers")
|
|
339
96
|
.select("id, country")
|
|
340
|
-
.in("id",
|
|
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 (
|
|
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",
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 ??
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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":"
|
|
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
|
+
}
|