@flowselections/floriday-verkoop-module 1.0.19 → 1.0.21
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/verkoop/OrderControlStatusCard.d.ts +6 -0
- package/dist-lib/components/verkoop/OrderControlStatusCard.d.ts.map +1 -0
- package/dist-lib/components/verkoop/OrderControlStatusCard.js +41 -0
- package/dist-lib/components/verkoop/SalesOrderDetailPage.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrderDetailPage.js +20 -5
- package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrderDetailView.js +2 -1
- package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrdersTable.js +4 -1
- package/dist-lib/components/verkoop/VerkoopOrdersPage.js +1 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +22 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +22 -0
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +22 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +22 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/cart-lock.functions.d.ts +22 -0
- package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -1
- package/dist-lib/lib/cart-replan.functions.d.ts +44 -0
- package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -1
- package/dist-lib/lib/create-order.functions.d.ts +264 -0
- package/dist-lib/lib/create-order.functions.d.ts.map +1 -1
- package/dist-lib/lib/create-order.functions.js +34 -25
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +66 -0
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +22 -0
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-public.functions.d.ts +157 -0
- package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-public.functions.js +114 -38
- package/dist-lib/lib/floriday-writes.functions.d.ts +110 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-shipments.functions.d.ts +88 -0
- package/dist-lib/lib/order-shipments.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-mapping.d.ts +2 -0
- package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-mapping.js +8 -1
- package/dist-lib/lib/salesorders.functions.d.ts +23785 -0
- package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders.functions.js +181 -0
- package/dist-lib/lib/tradeitems.functions.d.ts +22 -0
- package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
- package/dist-lib/lib/useSalesOrderDetailQuery.d.ts +2 -0
- package/dist-lib/lib/useSalesOrderDetailQuery.d.ts.map +1 -0
- package/dist-lib/lib/useSalesOrderDetailQuery.js +50 -0
- package/dist-lib/lib/useSalesOrdersQuery.d.ts +2 -2
- package/dist-lib/lib/useSalesOrdersQuery.d.ts.map +1 -1
- package/dist-lib/lib/useSalesOrdersQuery.js +165 -12
- package/package.json +1 -1
|
@@ -1,11 +1,67 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
1
|
+
import { useEffect, useMemo } from "react";
|
|
2
2
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
3
|
import { useServerFn } from "@tanstack/react-start";
|
|
4
4
|
import { supabase } from "../integrations/supabase/client";
|
|
5
5
|
import { getActiveFloridayEnvironment, getFlorydayCachedList } from "./floriday-public.functions";
|
|
6
6
|
import { expandOrderItemsForPlanner } from "./expand-group-order-items";
|
|
7
7
|
import { planCarts } from "./cart-planner";
|
|
8
|
-
import { centralOrderNumber, mapJsonbToSalesOrderDTO, } from "./salesorders-mapping";
|
|
8
|
+
import { centralOrderNumber, isoWeekRange, mapJsonbToSalesOrderDTO, monthRange, } from "./salesorders-mapping";
|
|
9
|
+
// Bepaal het serverside periode-venster op basis van de UI-filters.
|
|
10
|
+
// Retourneert `since`/`until` als ISO-strings + het gekozen dateField.
|
|
11
|
+
// Bij ontbrekende/onvolledige filters geen venster → server past geen
|
|
12
|
+
// periode-filter toe (client-side applyFilters filtert dan alsnog).
|
|
13
|
+
function derivePeriodWindow(filters) {
|
|
14
|
+
const dateField = filters?.dateField ?? "ordered";
|
|
15
|
+
if (!filters)
|
|
16
|
+
return { dateField };
|
|
17
|
+
const ymdToUtc = (ymd) => {
|
|
18
|
+
const [y, m, d] = ymd.split("-").map(Number);
|
|
19
|
+
return new Date(Date.UTC(y, (m ?? 1) - 1, d ?? 1));
|
|
20
|
+
};
|
|
21
|
+
const startOfTodayUtc = () => {
|
|
22
|
+
const now = new Date();
|
|
23
|
+
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
24
|
+
};
|
|
25
|
+
let start = null;
|
|
26
|
+
let end = null;
|
|
27
|
+
if (filters.period === "month" && filters.month !== undefined && filters.monthYear !== undefined) {
|
|
28
|
+
const r = monthRange(filters.month, filters.monthYear);
|
|
29
|
+
start = r.start;
|
|
30
|
+
end = r.end;
|
|
31
|
+
}
|
|
32
|
+
else if (filters.period === "day" && filters.day) {
|
|
33
|
+
start = ymdToUtc(filters.day);
|
|
34
|
+
end = new Date(start);
|
|
35
|
+
end.setUTCDate(start.getUTCDate() + 1);
|
|
36
|
+
}
|
|
37
|
+
else if (filters.period === "last7" || filters.period === "last30") {
|
|
38
|
+
const days = filters.period === "last7" ? 7 : 30;
|
|
39
|
+
end = startOfTodayUtc();
|
|
40
|
+
end.setUTCDate(end.getUTCDate() + 1);
|
|
41
|
+
start = new Date(end);
|
|
42
|
+
start.setUTCDate(end.getUTCDate() - days);
|
|
43
|
+
}
|
|
44
|
+
else if (filters.period === "custom" && filters.customFrom && filters.customTo) {
|
|
45
|
+
start = ymdToUtc(filters.customFrom);
|
|
46
|
+
end = ymdToUtc(filters.customTo);
|
|
47
|
+
end.setUTCDate(end.getUTCDate() + 1);
|
|
48
|
+
}
|
|
49
|
+
else if (filters.week && filters.year) {
|
|
50
|
+
const r = isoWeekRange(filters.week, filters.year);
|
|
51
|
+
start = r.start;
|
|
52
|
+
end = r.end;
|
|
53
|
+
}
|
|
54
|
+
// Buffer van 1 dag rond het venster: order/delivery-tijden kunnen net buiten
|
|
55
|
+
// de weekgrens vallen; de client filtert alsnog exact via applyFilters.
|
|
56
|
+
if (start && end) {
|
|
57
|
+
const s = new Date(start);
|
|
58
|
+
s.setUTCDate(s.getUTCDate() - 1);
|
|
59
|
+
const e = new Date(end);
|
|
60
|
+
e.setUTCDate(e.getUTCDate() + 1);
|
|
61
|
+
return { since: s.toISOString(), until: e.toISOString(), dateField };
|
|
62
|
+
}
|
|
63
|
+
return { dateField };
|
|
64
|
+
}
|
|
9
65
|
function pickString(v) {
|
|
10
66
|
if (v == null)
|
|
11
67
|
return null;
|
|
@@ -69,6 +125,8 @@ function statusFlags(statusValue) {
|
|
|
69
125
|
status === "approved" ||
|
|
70
126
|
status === "open" ||
|
|
71
127
|
status === "concept",
|
|
128
|
+
isControlled: false,
|
|
129
|
+
controlledAt: null,
|
|
72
130
|
};
|
|
73
131
|
}
|
|
74
132
|
async function fetchAllLocalOrders() {
|
|
@@ -91,18 +149,25 @@ async function fetchAllLocalOrders() {
|
|
|
91
149
|
}
|
|
92
150
|
return rows;
|
|
93
151
|
}
|
|
94
|
-
export function useSalesOrdersQuery() {
|
|
152
|
+
export function useSalesOrdersQuery(filters) {
|
|
95
153
|
const fetchCachedList = useServerFn(getFlorydayCachedList);
|
|
96
154
|
const fetchActiveEnv = useServerFn(getActiveFloridayEnvironment);
|
|
97
155
|
const queryClient = useQueryClient();
|
|
98
|
-
const
|
|
156
|
+
const periodWindow = useMemo(() => derivePeriodWindow(filters), [filters]);
|
|
157
|
+
const queryKey = useMemo(() => [
|
|
158
|
+
"floriday",
|
|
159
|
+
"salesorders-cache",
|
|
160
|
+
periodWindow.dateField,
|
|
161
|
+
periodWindow.since ?? null,
|
|
162
|
+
periodWindow.until ?? null,
|
|
163
|
+
], [periodWindow.dateField, periodWindow.since, periodWindow.until]);
|
|
99
164
|
useEffect(() => {
|
|
100
165
|
let timer = null;
|
|
101
166
|
const invalidate = () => {
|
|
102
167
|
if (timer)
|
|
103
168
|
clearTimeout(timer);
|
|
104
169
|
timer = setTimeout(() => {
|
|
105
|
-
queryClient.invalidateQueries({ queryKey });
|
|
170
|
+
queryClient.invalidateQueries({ queryKey: ["floriday", "salesorders-cache"] });
|
|
106
171
|
}, 250);
|
|
107
172
|
};
|
|
108
173
|
const channel = supabase
|
|
@@ -125,9 +190,17 @@ export function useSalesOrdersQuery() {
|
|
|
125
190
|
const { environment } = await fetchActiveEnv();
|
|
126
191
|
const dbEnv = environment === "live" ? "live" : "staging";
|
|
127
192
|
// Floriday-data via de centrale gateway (geen directe floriday_*_cache reads).
|
|
128
|
-
const [ordersList,
|
|
129
|
-
fetchCachedList({
|
|
130
|
-
|
|
193
|
+
const [ordersList, tradeItemsList, connectionsRes, localOrderRows, localCustomersRes, mailboxProposalsRes,] = await Promise.all([
|
|
194
|
+
fetchCachedList({
|
|
195
|
+
data: {
|
|
196
|
+
environment,
|
|
197
|
+
entity: "sales-orders",
|
|
198
|
+
limit: 20000,
|
|
199
|
+
since: periodWindow.since,
|
|
200
|
+
until: periodWindow.until,
|
|
201
|
+
dateField: periodWindow.dateField,
|
|
202
|
+
},
|
|
203
|
+
}),
|
|
131
204
|
fetchCachedList({ data: { environment, entity: "trade-items", limit: 5000 } }),
|
|
132
205
|
supabase
|
|
133
206
|
.from("floriday_connections")
|
|
@@ -151,6 +224,27 @@ export function useSalesOrdersQuery() {
|
|
|
151
224
|
throw localCustomersRes.error;
|
|
152
225
|
if (mailboxProposalsRes.error)
|
|
153
226
|
throw mailboxProposalsRes.error;
|
|
227
|
+
// Targeted customer-cache fetch: alleen orgIds die daadwerkelijk in de
|
|
228
|
+
// orders voorkomen. Voorkomt dat we vastlopen op limit 2000 terwijl er
|
|
229
|
+
// tienduizenden klanten in floriday_customers_cache staan.
|
|
230
|
+
const neededOrgIds = Array.from(new Set((ordersList.rows ?? [])
|
|
231
|
+
.map((r) => String(r?.data?.customerOrganizationId ?? ""))
|
|
232
|
+
.filter((id) => id.length > 0)));
|
|
233
|
+
const customerCacheRows = [];
|
|
234
|
+
const orgChunkSize = 500;
|
|
235
|
+
for (let i = 0; i < neededOrgIds.length; i += orgChunkSize) {
|
|
236
|
+
const chunk = neededOrgIds.slice(i, i + orgChunkSize);
|
|
237
|
+
const { data, error } = await supabase
|
|
238
|
+
.from("floriday_customers_cache")
|
|
239
|
+
.select("organization_id, data")
|
|
240
|
+
.in("organization_id", chunk);
|
|
241
|
+
if (error) {
|
|
242
|
+
console.warn("[useSalesOrdersQuery] floriday_customers_cache chunk faalde:", error.message);
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
customerCacheRows.push(...(data ?? []));
|
|
246
|
+
}
|
|
247
|
+
const customersList = { rows: customerCacheRows };
|
|
154
248
|
const lookup = new Map();
|
|
155
249
|
// Primair: gekoppelde klanten uit de Klanten Module (customers-tabel).
|
|
156
250
|
for (const c of localCustomersRes.data ?? []) {
|
|
@@ -203,7 +297,7 @@ export function useSalesOrdersQuery() {
|
|
|
203
297
|
pickString(d.articleCode);
|
|
204
298
|
tradeItemLookup.set(id, { name, imageUrl: firstPhoto, sku, raw: d });
|
|
205
299
|
}
|
|
206
|
-
const floridayOrders = (ordersList.rows ?? []).map((r) => mapJsonbToSalesOrderDTO(r.data, lookup, tradeItemLookup));
|
|
300
|
+
const floridayOrders = (ordersList.rows ?? []).map((r) => mapJsonbToSalesOrderDTO({ ...r.data, __controlledAt: r.controlled_at ?? null }, lookup, tradeItemLookup));
|
|
207
301
|
const localCustomerLookup = new Map();
|
|
208
302
|
for (const c of localCustomersRes.data ?? []) {
|
|
209
303
|
localCustomerLookup.set(String(c.id), {
|
|
@@ -213,6 +307,65 @@ export function useSalesOrdersQuery() {
|
|
|
213
307
|
}
|
|
214
308
|
const localOrders = [];
|
|
215
309
|
const includedOrderIds = new Set();
|
|
310
|
+
// Belading per productgroep: head_product_id (products-rij) heeft
|
|
311
|
+
// voorrang, val terug op de group-mirror (products.trade_item_key =
|
|
312
|
+
// 'group:<id>'). Bij verkoop-lijnen met product_name "[Groep] X"
|
|
313
|
+
// gebruiken we deze belading i.p.v. die van de eerste member.
|
|
314
|
+
const [productGroupsRes, groupMirrorRes] = await Promise.all([
|
|
315
|
+
supabase
|
|
316
|
+
.from("product_groups")
|
|
317
|
+
.select("id, name, head_product_id, head_trade_item_floriday_id")
|
|
318
|
+
.limit(5000),
|
|
319
|
+
supabase
|
|
320
|
+
.from("products")
|
|
321
|
+
.select("id, plants_per_crate, crates_per_layer, layers_per_cart, trade_item_key")
|
|
322
|
+
.like("trade_item_key", "group:%")
|
|
323
|
+
.limit(5000),
|
|
324
|
+
]);
|
|
325
|
+
const mirrorByGroupId = new Map();
|
|
326
|
+
for (const p of groupMirrorRes.data ?? []) {
|
|
327
|
+
const key = p.trade_item_key;
|
|
328
|
+
if (typeof key === "string" && key.startsWith("group:")) {
|
|
329
|
+
mirrorByGroupId.set(key.slice(6), p);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const headProductIds = Array.from(new Set((productGroupsRes.data ?? [])
|
|
333
|
+
.map((g) => g.head_product_id)
|
|
334
|
+
.filter((v) => typeof v === "string" && !!v)));
|
|
335
|
+
const headProductById = new Map();
|
|
336
|
+
if (headProductIds.length > 0) {
|
|
337
|
+
const { data: hp } = await supabase
|
|
338
|
+
.from("products")
|
|
339
|
+
.select("id, plants_per_crate, crates_per_layer, layers_per_cart")
|
|
340
|
+
.in("id", headProductIds);
|
|
341
|
+
for (const p of hp ?? [])
|
|
342
|
+
headProductById.set(p.id, p);
|
|
343
|
+
}
|
|
344
|
+
const packFromRow = (row) => row
|
|
345
|
+
? {
|
|
346
|
+
plants_per_crate: Math.max(1, Number(row.plants_per_crate ?? 1)),
|
|
347
|
+
crates_per_layer: Math.max(1, Number(row.crates_per_layer ?? 1)),
|
|
348
|
+
layers_per_cart: Math.max(1, Number(row.layers_per_cart ?? 1)),
|
|
349
|
+
}
|
|
350
|
+
: null;
|
|
351
|
+
const groupPackingByNameLower = new Map();
|
|
352
|
+
for (const g of productGroupsRes.data ?? []) {
|
|
353
|
+
const packing = packFromRow(headProductById.get(g.head_product_id)) ??
|
|
354
|
+
packFromRow(mirrorByGroupId.get(g.id));
|
|
355
|
+
const gname = g.name ? String(g.name).trim().toLowerCase() : "";
|
|
356
|
+
if (packing && gname)
|
|
357
|
+
groupPackingByNameLower.set(gname, packing);
|
|
358
|
+
}
|
|
359
|
+
const resolveLinePacking = (item) => {
|
|
360
|
+
const name = String(item?.product_name ?? "");
|
|
361
|
+
if (name.startsWith("[Groep] ")) {
|
|
362
|
+
const gname = name.slice("[Groep] ".length).trim().toLowerCase();
|
|
363
|
+
const gp = groupPackingByNameLower.get(gname);
|
|
364
|
+
if (gp)
|
|
365
|
+
return gp;
|
|
366
|
+
}
|
|
367
|
+
return item?.products ?? null;
|
|
368
|
+
};
|
|
216
369
|
// Bereken karren per lokale order via de karindeling-planner (inclusief
|
|
217
370
|
// uitpakken van groep-artikelen naar members met eigen belading). Zo
|
|
218
371
|
// matcht de kolom "Karren" in de tabel met de karindeling-kaart.
|
|
@@ -222,7 +375,7 @@ export function useSalesOrdersQuery() {
|
|
|
222
375
|
if (!lineItems.length)
|
|
223
376
|
return;
|
|
224
377
|
const raws = lineItems.map((it, idx) => {
|
|
225
|
-
const packing = it
|
|
378
|
+
const packing = resolveLinePacking(it);
|
|
226
379
|
return {
|
|
227
380
|
id: String(it?.id ?? `${r.id}#${idx}`),
|
|
228
381
|
product_id: it?.product_id ?? null,
|
|
@@ -263,7 +416,7 @@ export function useSalesOrdersQuery() {
|
|
|
263
416
|
const pieces = toNumber(item?.quantity) || 0;
|
|
264
417
|
if (pieces <= 0)
|
|
265
418
|
continue;
|
|
266
|
-
const packing = item
|
|
419
|
+
const packing = resolveLinePacking(item);
|
|
267
420
|
const plantsPerCrate = Math.max(1, Number(packing?.plants_per_crate ?? 1));
|
|
268
421
|
const cratesPerLayer = Math.max(0, Number(packing?.crates_per_layer ?? 0));
|
|
269
422
|
const layersPerCart = Math.max(0, Number(packing?.layers_per_cart ?? 0));
|
|
@@ -289,7 +442,7 @@ export function useSalesOrdersQuery() {
|
|
|
289
442
|
const customerId = r.customer_id ? String(r.customer_id) : "";
|
|
290
443
|
const localCustomer = customerId ? localCustomerLookup.get(customerId) : undefined;
|
|
291
444
|
const firstProduct = pickString(firstLine?.product_name);
|
|
292
|
-
const firstProductPacking = firstLine
|
|
445
|
+
const firstProductPacking = firstLine ? resolveLinePacking(firstLine) : null;
|
|
293
446
|
const firstPlantsPerCrate = Math.max(1, Number(firstProductPacking?.plants_per_crate ?? 1));
|
|
294
447
|
const firstCratesPerLayer = Math.max(0, Number(firstProductPacking?.crates_per_layer ?? 0));
|
|
295
448
|
const firstLayersPerCart = Math.max(0, Number(firstProductPacking?.layers_per_cart ?? 0));
|