@flowselections/floriday-verkoop-module 1.0.38 → 1.0.39
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/VerkoopOrdersPage.d.ts.map +1 -1
- package/dist-lib/components/verkoop/VerkoopOrdersPage.js +50 -87
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +849 -60
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +849 -60
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +849 -60
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +830 -39
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/cart-lock.functions.d.ts +849 -60
- package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -1
- package/dist-lib/lib/cart-planning-settings.functions.d.ts +8679 -0
- package/dist-lib/lib/cart-planning-settings.functions.d.ts.map +1 -1
- package/dist-lib/lib/cart-replan.functions.d.ts +2565 -198
- package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -1
- package/dist-lib/lib/create-order.functions.d.ts +11835 -0
- package/dist-lib/lib/create-order.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +2565 -198
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +849 -60
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-mirror.functions.d.ts +849 -60
- package/dist-lib/lib/floriday-mirror.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-public.functions.d.ts +5523 -0
- package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-writes.functions.d.ts +4505 -560
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/load-carriers.functions.d.ts +849 -60
- package/dist-lib/lib/load-carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/logistics-engine.functions.d.ts +4511 -562
- package/dist-lib/lib/logistics-engine.functions.d.ts.map +1 -1
- package/dist-lib/lib/logistics-stock.functions.d.ts +1578 -0
- package/dist-lib/lib/logistics-stock.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-addresses.functions.d.ts +849 -60
- package/dist-lib/lib/order-addresses.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-buffers.functions.d.ts +2565 -198
- package/dist-lib/lib/order-buffers.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-carrier.functions.d.ts +1578 -0
- package/dist-lib/lib/order-carrier.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-pickup.functions.d.ts +1578 -0
- package/dist-lib/lib/order-pickup.functions.d.ts.map +1 -1
- package/dist-lib/lib/order-shipments.functions.d.ts +3535 -379
- package/dist-lib/lib/order-shipments.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-list.functions.d.ts +20393 -0
- package/dist-lib/lib/salesorders-list.functions.d.ts.map +1 -0
- package/dist-lib/lib/salesorders-list.functions.js +21 -0
- package/dist-lib/lib/salesorders-list.server.d.ts +28 -0
- package/dist-lib/lib/salesorders-list.server.d.ts.map +1 -0
- package/dist-lib/lib/salesorders-list.server.js +713 -0
- package/dist-lib/lib/salesorders.functions.d.ts +4505 -560
- package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
- package/dist-lib/lib/tradeitems.functions.d.ts +849 -60
- package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
- package/dist-lib/lib/useSalesOrdersQuery.d.ts +24 -3
- package/dist-lib/lib/useSalesOrdersQuery.d.ts.map +1 -1
- package/dist-lib/lib/useSalesOrdersQuery.js +49 -594
- package/package.json +2 -2
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
import { centralOrderNumber, computeTotals, mapJsonbToSalesOrderDTO, } from "./salesorders-mapping";
|
|
2
|
+
import { cratePackageName, loadCarrierName, mergeTypeNames } from "./load-carrier-labels";
|
|
3
|
+
import { normalizeLoadCarrierCode } from "./load-carriers";
|
|
4
|
+
import { normalizeTime, resolveDeliveryTimeForDate } from "./carrier-availability";
|
|
5
|
+
import { expandOrderItemsForPlanner } from "./expand-group-order-items";
|
|
6
|
+
import { planCarts } from "./cart-planner";
|
|
7
|
+
const FO_CAP = 20000;
|
|
8
|
+
const LOCAL_CAP = 5000;
|
|
9
|
+
const PROPOSAL_CAP = 2000;
|
|
10
|
+
const EXPORT_CAP = 10000;
|
|
11
|
+
const PAGE_SIZE_MAX = 200;
|
|
12
|
+
const mailboxSources = new Set([
|
|
13
|
+
"mailbox",
|
|
14
|
+
"mailbox_order",
|
|
15
|
+
"mailbox-bestellingen",
|
|
16
|
+
"mailbox_bestellingen",
|
|
17
|
+
"mail",
|
|
18
|
+
"email",
|
|
19
|
+
"email_order",
|
|
20
|
+
]);
|
|
21
|
+
// ── kleine helpers ────────────────────────────────────────────────────────
|
|
22
|
+
function pickString(v) {
|
|
23
|
+
if (v == null)
|
|
24
|
+
return null;
|
|
25
|
+
if (typeof v === "string")
|
|
26
|
+
return v;
|
|
27
|
+
if (typeof v === "number")
|
|
28
|
+
return String(v);
|
|
29
|
+
if (Array.isArray(v)) {
|
|
30
|
+
for (const item of v) {
|
|
31
|
+
const s = pickString(item?.text ?? item?.value ?? item);
|
|
32
|
+
if (s)
|
|
33
|
+
return s;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
if (typeof v === "object") {
|
|
38
|
+
return (v.nl ?? v.NL ?? v.en ?? v.EN ?? v.text ?? v.value ??
|
|
39
|
+
Object.values(v).find((x) => typeof x === "string") ??
|
|
40
|
+
null);
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function toNumber(value) {
|
|
45
|
+
if (typeof value === "number")
|
|
46
|
+
return Number.isFinite(value) ? value : 0;
|
|
47
|
+
if (typeof value !== "string")
|
|
48
|
+
return 0;
|
|
49
|
+
const cleaned = value.replace(/[^0-9,.-]/g, "").trim();
|
|
50
|
+
if (!cleaned)
|
|
51
|
+
return 0;
|
|
52
|
+
const lastComma = cleaned.lastIndexOf(",");
|
|
53
|
+
const lastDot = cleaned.lastIndexOf(".");
|
|
54
|
+
const lastSeparator = Math.max(lastComma, lastDot);
|
|
55
|
+
const hasRepeated = (cleaned.match(/[,.]/g) ?? []).length > 1;
|
|
56
|
+
const normalized = hasRepeated && lastSeparator >= 0
|
|
57
|
+
? `${cleaned.slice(0, lastSeparator).replace(/[,.]/g, "")}.${cleaned.slice(lastSeparator + 1)}`
|
|
58
|
+
: lastComma > lastDot
|
|
59
|
+
? cleaned.replace(/\./g, "").replace(",", ".")
|
|
60
|
+
: cleaned.replace(/,/g, "");
|
|
61
|
+
const n = Number.parseFloat(normalized);
|
|
62
|
+
return Number.isFinite(n) ? n : 0;
|
|
63
|
+
}
|
|
64
|
+
function statusFlags(statusValue) {
|
|
65
|
+
const status = String(statusValue ?? "").toLowerCase();
|
|
66
|
+
return {
|
|
67
|
+
isCancelled: status === "cancelled" || status === "geannuleerd" || status === "rejected",
|
|
68
|
+
isShipped: status === "shipped" || status === "verzonden" || status === "delivered" || status === "geleverd",
|
|
69
|
+
isInvoiced: status === "invoiced" || status === "gefactureerd",
|
|
70
|
+
isConfirmed: status === "confirmed" ||
|
|
71
|
+
status === "bevestigd" ||
|
|
72
|
+
status === "nieuw" ||
|
|
73
|
+
status === "approved" ||
|
|
74
|
+
status === "open" ||
|
|
75
|
+
status === "concept",
|
|
76
|
+
isControlled: false,
|
|
77
|
+
controlledAt: null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
function pickPackingConfigForLine(configs, packing) {
|
|
81
|
+
if (!Array.isArray(configs) || configs.length === 0)
|
|
82
|
+
return null;
|
|
83
|
+
const ppc = Number(packing?.plants_per_crate ?? 0);
|
|
84
|
+
const cpl = Number(packing?.crates_per_layer ?? 0);
|
|
85
|
+
const lpc = Number(packing?.layers_per_cart ?? 0);
|
|
86
|
+
const match = configs.find((c) => {
|
|
87
|
+
const a = Number(c?.piecesPerPackage ?? c?.unitsPerPackage ?? 0);
|
|
88
|
+
const b = Number(c?.packagesPerLayer ?? 0);
|
|
89
|
+
const d = Number(c?.layersPerLoadCarrier ?? 0);
|
|
90
|
+
return ppc > 0 && a === ppc && (!cpl || b === cpl) && (!lpc || d === lpc);
|
|
91
|
+
});
|
|
92
|
+
return match ?? configs.find((c) => c?.primary === true || c?.isPrimary === true) ?? configs[0] ?? null;
|
|
93
|
+
}
|
|
94
|
+
async function chunked(ids, size, fn) {
|
|
95
|
+
const out = [];
|
|
96
|
+
for (let i = 0; i < ids.length; i += size) {
|
|
97
|
+
out.push(...(await fn(ids.slice(i, i + size))));
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
const TTL_MS = 5 * 60 * 1000;
|
|
102
|
+
let crateDescCache = null;
|
|
103
|
+
async function loadCrateDescriptions(supabase, warnings) {
|
|
104
|
+
if (crateDescCache && Date.now() - crateDescCache.at < TTL_MS)
|
|
105
|
+
return crateDescCache.value;
|
|
106
|
+
const map = new Map();
|
|
107
|
+
try {
|
|
108
|
+
const { data, error } = await supabase
|
|
109
|
+
.from("floricode_records")
|
|
110
|
+
.select("data")
|
|
111
|
+
.eq("resource", "LoadCarrier")
|
|
112
|
+
.limit(5000);
|
|
113
|
+
if (error)
|
|
114
|
+
throw new Error(error.message);
|
|
115
|
+
for (const row of data ?? []) {
|
|
116
|
+
const d = row.data ?? {};
|
|
117
|
+
if (d?.code == null)
|
|
118
|
+
continue;
|
|
119
|
+
const desc = typeof d.description === "string" ? d.description.trim() : "";
|
|
120
|
+
if (desc)
|
|
121
|
+
map.set(String(d.code), desc);
|
|
122
|
+
}
|
|
123
|
+
crateDescCache = { at: Date.now(), value: map };
|
|
124
|
+
}
|
|
125
|
+
catch (e) {
|
|
126
|
+
warnings.push(`Fustomschrijvingen niet geladen: ${e.message}`);
|
|
127
|
+
if (crateDescCache)
|
|
128
|
+
return crateDescCache.value;
|
|
129
|
+
}
|
|
130
|
+
return map;
|
|
131
|
+
}
|
|
132
|
+
// ── hoofdfunctie ──────────────────────────────────────────────────────────
|
|
133
|
+
export async function buildSalesOrdersList(supabase, params) {
|
|
134
|
+
const warnings = [];
|
|
135
|
+
const pageSize = Math.min(PAGE_SIZE_MAX, Math.max(1, Math.floor(params.pageSize || 20)));
|
|
136
|
+
const page = Math.max(1, Math.floor(params.page || 1));
|
|
137
|
+
// 1) Actieve omgeving + connectie.
|
|
138
|
+
const envRes = await supabase
|
|
139
|
+
.from("floriday_settings")
|
|
140
|
+
.select("active_environment")
|
|
141
|
+
.eq("id", 1)
|
|
142
|
+
.maybeSingle();
|
|
143
|
+
const rawEnv = String(envRes.data?.active_environment ?? "staging");
|
|
144
|
+
const environment = rawEnv === "production" || rawEnv === "prod" || rawEnv === "live" ? "live" : "staging";
|
|
145
|
+
const connRes = await supabase
|
|
146
|
+
.from("floriday_connections")
|
|
147
|
+
.select("id, organization_name, label")
|
|
148
|
+
.eq("is_active", true)
|
|
149
|
+
.eq("environment", environment)
|
|
150
|
+
.limit(1);
|
|
151
|
+
const conn = (connRes.data ?? [])[0];
|
|
152
|
+
const sellerFallback = conn?.organization_name ?? conn?.label ?? null;
|
|
153
|
+
let truncated = false;
|
|
154
|
+
// 2) Floriday-orders binnen het venster (alleen benodigde kolommen).
|
|
155
|
+
const dateColumn = params.dateField === "delivery"
|
|
156
|
+
? "data->delivery->>latestDeliveryDateTime"
|
|
157
|
+
: "data->>orderDateTime";
|
|
158
|
+
const foRows = [];
|
|
159
|
+
if (conn?.id) {
|
|
160
|
+
const pageStep = 1000;
|
|
161
|
+
for (let from = 0; from < FO_CAP; from += pageStep) {
|
|
162
|
+
let q = supabase
|
|
163
|
+
.from("floriday_salesorders_cache")
|
|
164
|
+
.select("floriday_id, data, controlled_at")
|
|
165
|
+
.eq("connection_id", conn.id)
|
|
166
|
+
.eq("is_deleted", false)
|
|
167
|
+
.order("sequence_number", { ascending: false })
|
|
168
|
+
.range(from, from + pageStep - 1);
|
|
169
|
+
if (params.since)
|
|
170
|
+
q = q.gte(dateColumn, params.since);
|
|
171
|
+
if (params.until)
|
|
172
|
+
q = q.lt(dateColumn, params.until);
|
|
173
|
+
const { data, error } = await q;
|
|
174
|
+
if (error)
|
|
175
|
+
throw new Error(`Floriday-orders lezen faalde: ${error.message}`);
|
|
176
|
+
const batch = data ?? [];
|
|
177
|
+
foRows.push(...batch);
|
|
178
|
+
if (batch.length < pageStep)
|
|
179
|
+
break;
|
|
180
|
+
if (foRows.length >= FO_CAP)
|
|
181
|
+
truncated = true;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// 3) Lokale orders binnen hetzelfde venster.
|
|
185
|
+
const localOrderSelect = "id, order_number, customer_name, customer_id, order_date, delivery_date, status, total, custom_fields, order_source, created_at, is_reservation, buffer_id, floriday_sales_order_id, order_items(id, product_id, product_name, quantity, unit, unit_price, products(plants_per_crate, crates_per_layer, layers_per_cart, floriday_trade_item_id, custom_fields))";
|
|
186
|
+
let localQ = supabase
|
|
187
|
+
.from("orders")
|
|
188
|
+
.select(localOrderSelect)
|
|
189
|
+
.is("deleted_at", null)
|
|
190
|
+
.order("created_at", { ascending: false })
|
|
191
|
+
.limit(LOCAL_CAP);
|
|
192
|
+
if (params.since && params.until) {
|
|
193
|
+
if (params.dateField === "delivery") {
|
|
194
|
+
localQ = localQ
|
|
195
|
+
.gte("delivery_date", params.since.slice(0, 10))
|
|
196
|
+
.lt("delivery_date", params.until.slice(0, 10));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
localQ = localQ.gte("order_date", params.since).lt("order_date", params.until);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const localRes = await localQ;
|
|
203
|
+
if (localRes.error)
|
|
204
|
+
throw new Error(`Lokale orders lezen faalde: ${localRes.error.message}`);
|
|
205
|
+
const localOrderRows = (localRes.data ?? []);
|
|
206
|
+
// 4) Mailbox-voorstellen (best-effort).
|
|
207
|
+
let proposalRows = [];
|
|
208
|
+
try {
|
|
209
|
+
let pq = supabase
|
|
210
|
+
.from("mailbox_order_proposals")
|
|
211
|
+
.select("id, order_number, status, delivery_date, created_order_id, created_at, matched_customer_id, mailbox_messages(from_name, from_email, subject, received_at), mailbox_proposal_lines(raw_product_text, quantity, unit)")
|
|
212
|
+
.neq("status", "rejected")
|
|
213
|
+
.order("created_at", { ascending: false })
|
|
214
|
+
.limit(PROPOSAL_CAP);
|
|
215
|
+
if (params.since && params.until && params.dateField === "ordered") {
|
|
216
|
+
pq = pq.gte("created_at", params.since).lt("created_at", params.until);
|
|
217
|
+
}
|
|
218
|
+
const pr = await pq;
|
|
219
|
+
if (pr.error)
|
|
220
|
+
throw new Error(pr.error.message);
|
|
221
|
+
proposalRows = (pr.data ?? []);
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
warnings.push(`Mailbox-voorstellen niet geladen: ${e.message}`);
|
|
225
|
+
}
|
|
226
|
+
// 5) Referentiedata — alleen wat nodig is voor deze rijen.
|
|
227
|
+
const neededOrgIds = Array.from(new Set(foRows.map((r) => String(r?.data?.customerOrganizationId ?? "")).filter((s) => s.length > 0)));
|
|
228
|
+
const neededCustomerIds = Array.from(new Set(localOrderRows.map((r) => String(r.customer_id ?? "")).filter((s) => s.length > 0)));
|
|
229
|
+
const neededTradeItemIds = Array.from(new Set([
|
|
230
|
+
...foRows.map((r) => String(r?.data?.tradeItemId ?? "")),
|
|
231
|
+
...localOrderRows.flatMap((r) => (r.order_items ?? []).map((it) => String(it?.products?.floriday_trade_item_id ?? ""))),
|
|
232
|
+
].filter((s) => s.length > 0)));
|
|
233
|
+
const neededCarrierIds = Array.from(new Set(localOrderRows
|
|
234
|
+
.map((r) => String((r.custom_fields ?? {}).carrier ?? ""))
|
|
235
|
+
.filter((s) => s.length > 0)));
|
|
236
|
+
const foIdsForNumbers = Array.from(new Set(foRows.map((r) => String(r.floriday_id ?? "")).filter((s) => s.length > 0)));
|
|
237
|
+
const safe = async (label, fn, fallback) => {
|
|
238
|
+
try {
|
|
239
|
+
return await fn();
|
|
240
|
+
}
|
|
241
|
+
catch (e) {
|
|
242
|
+
warnings.push(`${label}: ${e.message}`);
|
|
243
|
+
return fallback;
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
const [orderNumberRows, customerRows, floridayCustomerRows, tradeItemRows, carrierRows, crateDescriptions, groupPacking,] = await Promise.all([
|
|
247
|
+
safe("Ordernummers", () => chunked(foIdsForNumbers, 400, async (chunk) => {
|
|
248
|
+
const { data, error } = await supabase
|
|
249
|
+
.from("floriday_order_number_map")
|
|
250
|
+
.select("floriday_id, order_number")
|
|
251
|
+
.in("floriday_id", chunk);
|
|
252
|
+
if (error)
|
|
253
|
+
throw new Error(error.message);
|
|
254
|
+
return (data ?? []);
|
|
255
|
+
}), []),
|
|
256
|
+
safe("Klanten", async () => {
|
|
257
|
+
const orFilter = [
|
|
258
|
+
neededOrgIds.length ? `floriday_organization_id.in.(${neededOrgIds.join(",")})` : null,
|
|
259
|
+
neededCustomerIds.length ? `id.in.(${neededCustomerIds.join(",")})` : null,
|
|
260
|
+
]
|
|
261
|
+
.filter(Boolean)
|
|
262
|
+
.join(",") || "id.is.null";
|
|
263
|
+
const { data, error } = await supabase
|
|
264
|
+
.from("customers")
|
|
265
|
+
.select("id, company_name, commercial_name, city, postal_code, logo_url, floriday_organization_id")
|
|
266
|
+
.or(orFilter)
|
|
267
|
+
.limit(5000);
|
|
268
|
+
if (error)
|
|
269
|
+
throw new Error(error.message);
|
|
270
|
+
return (data ?? []);
|
|
271
|
+
}, []),
|
|
272
|
+
safe("Floriday-klantcache", () => chunked(neededOrgIds, 300, async (chunk) => {
|
|
273
|
+
const { data, error } = await supabase
|
|
274
|
+
.from("floriday_customers_cache")
|
|
275
|
+
.select("organization_id, data")
|
|
276
|
+
.in("organization_id", chunk);
|
|
277
|
+
if (error)
|
|
278
|
+
throw new Error(error.message);
|
|
279
|
+
return (data ?? []);
|
|
280
|
+
}), []),
|
|
281
|
+
safe("Artikelcache", () => chunked(neededTradeItemIds, 300, async (chunk) => {
|
|
282
|
+
const { data, error } = await supabase
|
|
283
|
+
.from("floriday_trade_items_cache")
|
|
284
|
+
.select("floriday_id, data")
|
|
285
|
+
.in("floriday_id", chunk);
|
|
286
|
+
if (error)
|
|
287
|
+
throw new Error(error.message);
|
|
288
|
+
return (data ?? []);
|
|
289
|
+
}), []),
|
|
290
|
+
safe("Vervoerders", async () => {
|
|
291
|
+
if (!neededCarrierIds.length)
|
|
292
|
+
return [];
|
|
293
|
+
const { data, error } = await supabase
|
|
294
|
+
.from("carriers")
|
|
295
|
+
.select("id, delivery_days, signup_time_1, signup_time_2, signup_days_before_delivery, pickup_weekdays, delivery_weekdays, pickup_moments")
|
|
296
|
+
.in("id", neededCarrierIds);
|
|
297
|
+
if (error)
|
|
298
|
+
throw new Error(error.message);
|
|
299
|
+
return (data ?? []);
|
|
300
|
+
}, []),
|
|
301
|
+
loadCrateDescriptions(supabase, warnings),
|
|
302
|
+
safe("Productgroepen", () => loadGroupPacking(supabase), new Map()),
|
|
303
|
+
]);
|
|
304
|
+
const orderNumberByFoId = new Map();
|
|
305
|
+
for (const m of orderNumberRows) {
|
|
306
|
+
orderNumberByFoId.set(String(m.floriday_id), String(m.order_number ?? ""));
|
|
307
|
+
}
|
|
308
|
+
const carrierById = new Map();
|
|
309
|
+
for (const c of carrierRows)
|
|
310
|
+
carrierById.set(String(c.id), c);
|
|
311
|
+
// 6) Klant-lookups.
|
|
312
|
+
const lookup = new Map();
|
|
313
|
+
const localCustomerLookup = new Map();
|
|
314
|
+
for (const c of customerRows) {
|
|
315
|
+
localCustomerLookup.set(String(c.id), {
|
|
316
|
+
name: c.company_name ?? "Onbekende klant",
|
|
317
|
+
city: c.city ?? "",
|
|
318
|
+
});
|
|
319
|
+
const orgId = c.floriday_organization_id;
|
|
320
|
+
if (!orgId)
|
|
321
|
+
continue;
|
|
322
|
+
lookup.set(String(orgId), {
|
|
323
|
+
name: c.commercial_name ?? c.company_name ?? "Onbekende klant",
|
|
324
|
+
postalCode: c.postal_code ?? "",
|
|
325
|
+
city: c.city ?? "",
|
|
326
|
+
logoUrl: c.logo_url ?? null,
|
|
327
|
+
raw: null,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
for (const row of floridayCustomerRows) {
|
|
331
|
+
const d = row.data ?? {};
|
|
332
|
+
const orgId = row.organization_id ?? d.organizationId;
|
|
333
|
+
if (!orgId || lookup.has(String(orgId)))
|
|
334
|
+
continue;
|
|
335
|
+
const addr = d.physicalAddress ?? d.mailingAddress ?? {};
|
|
336
|
+
lookup.set(String(orgId), {
|
|
337
|
+
name: d.commercialName ?? d.name ?? "Onbekende klant",
|
|
338
|
+
postalCode: addr.postalCode ?? "",
|
|
339
|
+
city: addr.city ?? "",
|
|
340
|
+
logoUrl: d.logoUrl ?? null,
|
|
341
|
+
raw: null,
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
// 7) Artikel-lookup.
|
|
345
|
+
const tradeItemLookup = new Map();
|
|
346
|
+
for (const row of tradeItemRows) {
|
|
347
|
+
const d = row.data ?? {};
|
|
348
|
+
const id = String(row.floriday_id ?? d.tradeItemId ?? "");
|
|
349
|
+
if (!id)
|
|
350
|
+
continue;
|
|
351
|
+
const photos = d.photos ?? d.images ?? d.tradeItemPhotos ?? [];
|
|
352
|
+
const firstPhotoRaw = Array.isArray(photos) && photos.length > 0
|
|
353
|
+
? (photos[0]?.url ?? photos[0]?.imageUrl ?? photos[0]?.href ?? photos[0])
|
|
354
|
+
: null;
|
|
355
|
+
tradeItemLookup.set(id, {
|
|
356
|
+
name: pickString(d.name) ??
|
|
357
|
+
pickString(d.productName) ??
|
|
358
|
+
pickString(d.tradeItemName) ??
|
|
359
|
+
pickString(d.tradeItemTexts) ??
|
|
360
|
+
"Onbekend artikel",
|
|
361
|
+
imageUrl: pickString(firstPhotoRaw),
|
|
362
|
+
sku: pickString(d.vbnProductCode) ?? pickString(d.gtin) ?? pickString(d.articleCode),
|
|
363
|
+
raw: d,
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
// 8) Buffers.
|
|
367
|
+
const bufferByFoId = new Map();
|
|
368
|
+
const bufferByLocalId = new Map();
|
|
369
|
+
for (const r of localOrderRows) {
|
|
370
|
+
const bid = r.buffer_id;
|
|
371
|
+
if (!bid)
|
|
372
|
+
continue;
|
|
373
|
+
if (r.floriday_sales_order_id)
|
|
374
|
+
bufferByFoId.set(String(r.floriday_sales_order_id), bid);
|
|
375
|
+
else
|
|
376
|
+
bufferByLocalId.set(String(r.id), bid);
|
|
377
|
+
}
|
|
378
|
+
// 9) Floriday-orders → DTO (zonder zware raw-payload richting browser).
|
|
379
|
+
const floridayOrders = foRows.map((r) => {
|
|
380
|
+
const enriched = {
|
|
381
|
+
...r.data,
|
|
382
|
+
...(orderNumberByFoId.get(String(r.floriday_id))
|
|
383
|
+
? { orderNumber: orderNumberByFoId.get(String(r.floriday_id)) }
|
|
384
|
+
: {}),
|
|
385
|
+
__controlledAt: r.controlled_at ?? null,
|
|
386
|
+
};
|
|
387
|
+
const dto = mapJsonbToSalesOrderDTO(enriched, lookup, tradeItemLookup, crateDescriptions);
|
|
388
|
+
dto.raw = { order: { status: r.data?.status ?? null }, customer: null, tradeItem: null };
|
|
389
|
+
const bid = bufferByFoId.get(dto.id) ?? null;
|
|
390
|
+
if (bid)
|
|
391
|
+
dto.bufferId = bid;
|
|
392
|
+
return dto;
|
|
393
|
+
});
|
|
394
|
+
// 10) Lokale orders → DTO.
|
|
395
|
+
const resolveLinePacking = (item) => {
|
|
396
|
+
const name = String(item?.product_name ?? "");
|
|
397
|
+
if (name.startsWith("[Groep] ")) {
|
|
398
|
+
const gname = name.slice("[Groep] ".length).trim().toLowerCase();
|
|
399
|
+
const gp = groupPacking.get(gname);
|
|
400
|
+
if (gp)
|
|
401
|
+
return gp;
|
|
402
|
+
}
|
|
403
|
+
return item?.products ?? null;
|
|
404
|
+
};
|
|
405
|
+
const localOrders = [];
|
|
406
|
+
const includedOrderIds = new Set();
|
|
407
|
+
for (const r of localOrderRows) {
|
|
408
|
+
if (r.floriday_sales_order_id)
|
|
409
|
+
continue;
|
|
410
|
+
const cf = r.custom_fields ?? {};
|
|
411
|
+
const sourceRaw = String(cf?.source ?? r.order_source ?? "manual").toLowerCase();
|
|
412
|
+
const source = mailboxSources.has(sourceRaw) ? "mailbox" : "local";
|
|
413
|
+
includedOrderIds.add(String(r.id));
|
|
414
|
+
const lineItems = Array.isArray(r.order_items) ? r.order_items : [];
|
|
415
|
+
const firstLine = lineItems[0] ?? null;
|
|
416
|
+
const lineTotal = lineItems.reduce((s, it) => s + (toNumber(it?.quantity) || 0) * (toNumber(it?.unit_price) || 0), 0);
|
|
417
|
+
const total = lineTotal > 0 ? lineTotal : toNumber(r.total);
|
|
418
|
+
const totalQuantity = lineItems.reduce((s, it) => s + (toNumber(it?.quantity) || 0), 0);
|
|
419
|
+
let fustTotal = 0;
|
|
420
|
+
let karTotalRaw = 0;
|
|
421
|
+
const crateNames = [];
|
|
422
|
+
const cartNames = [];
|
|
423
|
+
for (const item of lineItems) {
|
|
424
|
+
const pieces = toNumber(item?.quantity) || 0;
|
|
425
|
+
if (pieces <= 0)
|
|
426
|
+
continue;
|
|
427
|
+
const packing = resolveLinePacking(item);
|
|
428
|
+
const plantsPerCrate = Math.max(1, Number(packing?.plants_per_crate ?? 1));
|
|
429
|
+
const cratesPerLayer = Math.max(0, Number(packing?.crates_per_layer ?? 0));
|
|
430
|
+
const layersPerCart = Math.max(0, Number(packing?.layers_per_cart ?? 0));
|
|
431
|
+
const fust = Math.ceil(pieces / plantsPerCrate);
|
|
432
|
+
fustTotal += fust;
|
|
433
|
+
const packagesPerCart = cratesPerLayer * layersPerCart;
|
|
434
|
+
if (packagesPerCart > 0)
|
|
435
|
+
karTotalRaw += fust / packagesPerCart;
|
|
436
|
+
const prod = item?.products ?? null;
|
|
437
|
+
const fid = prod?.floriday_trade_item_id ? String(prod.floriday_trade_item_id) : null;
|
|
438
|
+
const configs = (() => {
|
|
439
|
+
const fromCache = fid ? tradeItemLookup.get(fid)?.raw?.packingConfigurations : null;
|
|
440
|
+
if (Array.isArray(fromCache) && fromCache.length > 0)
|
|
441
|
+
return fromCache;
|
|
442
|
+
const payload = prod?.custom_fields?.floriday_payload?.packingConfigurations;
|
|
443
|
+
return Array.isArray(payload) ? payload : [];
|
|
444
|
+
})();
|
|
445
|
+
const cfg = pickPackingConfigForLine(configs, packing);
|
|
446
|
+
crateNames.push(cratePackageName(cfg?.package?.vbnPackageCode ?? cfg?.package?.customPackageId ?? null, crateDescriptions));
|
|
447
|
+
cartNames.push(loadCarrierName(cfg?.loadCarrier ?? null));
|
|
448
|
+
}
|
|
449
|
+
const karCount = karTotalRaw > 0 ? Math.max(1, Math.ceil(karTotalRaw)) : totalQuantity > 0 ? 1 : 0;
|
|
450
|
+
const fustCount = fustTotal > 0 ? fustTotal : totalQuantity;
|
|
451
|
+
const unitPrice = firstLine ? toNumber(firstLine.unit_price) : total;
|
|
452
|
+
const rawOrderDate = r.order_date ?? "";
|
|
453
|
+
const orderedAt = typeof rawOrderDate === "string" && rawOrderDate.includes("T")
|
|
454
|
+
? rawOrderDate
|
|
455
|
+
: (r.created_at ?? rawOrderDate ?? "");
|
|
456
|
+
const rawDeliveryDate = String(r.delivery_date ?? "");
|
|
457
|
+
let deliveryTime = normalizeTime(cf.delivery_time);
|
|
458
|
+
if (!deliveryTime && rawDeliveryDate && cf.carrier) {
|
|
459
|
+
const createdAt = r.created_at ? new Date(r.created_at) : new Date();
|
|
460
|
+
deliveryTime = resolveDeliveryTimeForDate(carrierById.get(String(cf.carrier)) ?? null, rawDeliveryDate.slice(0, 10), createdAt);
|
|
461
|
+
}
|
|
462
|
+
const deliveryAt = rawDeliveryDate && !rawDeliveryDate.includes("T") && deliveryTime
|
|
463
|
+
? `${rawDeliveryDate.slice(0, 10)}T${deliveryTime}:00`
|
|
464
|
+
: rawDeliveryDate;
|
|
465
|
+
const customerId = r.customer_id ? String(r.customer_id) : "";
|
|
466
|
+
const localCustomer = customerId ? localCustomerLookup.get(customerId) : undefined;
|
|
467
|
+
const firstProduct = pickString(firstLine?.product_name);
|
|
468
|
+
const firstPacking = firstLine ? resolveLinePacking(firstLine) : null;
|
|
469
|
+
const firstPlantsPerCrate = Math.max(1, Number(firstPacking?.plants_per_crate ?? 1));
|
|
470
|
+
localOrders.push({
|
|
471
|
+
id: `local:${r.id}`,
|
|
472
|
+
orderNumber: centralOrderNumber(r.order_number),
|
|
473
|
+
productName: firstProduct
|
|
474
|
+
? lineItems.length > 1
|
|
475
|
+
? `${firstProduct} +${lineItems.length - 1}`
|
|
476
|
+
: firstProduct
|
|
477
|
+
: source === "mailbox"
|
|
478
|
+
? "Mailbox-bestelling"
|
|
479
|
+
: "Lokale bestelling",
|
|
480
|
+
sku: source === "mailbox" ? "Mailbox-bestelling" : "Lokale bestelling",
|
|
481
|
+
imageUrl: null,
|
|
482
|
+
orderedAt,
|
|
483
|
+
deliveryAt,
|
|
484
|
+
quantity: {
|
|
485
|
+
multiplier: totalQuantity > 0 ? Math.ceil(totalQuantity / firstPlantsPerCrate) : 1,
|
|
486
|
+
count: firstPlantsPerCrate,
|
|
487
|
+
unitPrice,
|
|
488
|
+
},
|
|
489
|
+
revenue: total,
|
|
490
|
+
fustCount,
|
|
491
|
+
karCount,
|
|
492
|
+
crateTypeName: mergeTypeNames(crateNames),
|
|
493
|
+
cartTypeName: mergeTypeNames(cartNames),
|
|
494
|
+
loadCarrierType: normalizeLoadCarrierCode(cf.load_carrier_type),
|
|
495
|
+
totalPieces: totalQuantity,
|
|
496
|
+
isLocal: true,
|
|
497
|
+
isReservation: !!r.is_reservation,
|
|
498
|
+
customer: {
|
|
499
|
+
name: r.customer_name ?? localCustomer?.name ?? "Onbekende klant",
|
|
500
|
+
postalCode: "",
|
|
501
|
+
city: localCustomer?.city ?? "",
|
|
502
|
+
logoUrl: null,
|
|
503
|
+
},
|
|
504
|
+
status: statusFlags(r.status),
|
|
505
|
+
raw: { order: { status: r.status ?? null }, customer: null, tradeItem: null },
|
|
506
|
+
bufferId: bufferByLocalId.get(String(r.id)) ?? null,
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
// 11) Mailbox-voorstellen zonder aangemaakte order.
|
|
510
|
+
for (const p of proposalRows) {
|
|
511
|
+
if (p.created_order_id && includedOrderIds.has(String(p.created_order_id)))
|
|
512
|
+
continue;
|
|
513
|
+
const msgRaw = p.mailbox_messages;
|
|
514
|
+
const msg = Array.isArray(msgRaw) ? msgRaw[0] : msgRaw;
|
|
515
|
+
const lines = Array.isArray(p.mailbox_proposal_lines) ? p.mailbox_proposal_lines : [];
|
|
516
|
+
const firstLine = lines[0] ?? null;
|
|
517
|
+
const matchedCustomerId = p.matched_customer_id ? String(p.matched_customer_id) : "";
|
|
518
|
+
const matchedCustomer = matchedCustomerId ? localCustomerLookup.get(matchedCustomerId) : undefined;
|
|
519
|
+
const quantity = Number(firstLine?.quantity ?? 1) || 1;
|
|
520
|
+
localOrders.push({
|
|
521
|
+
id: `mailbox-proposal:${p.id}`,
|
|
522
|
+
orderNumber: centralOrderNumber(p.order_number),
|
|
523
|
+
productName: pickString(firstLine?.raw_product_text) ?? pickString(msg?.subject) ?? "Mailbox-bestelling",
|
|
524
|
+
sku: "Mailbox-bestelling",
|
|
525
|
+
imageUrl: null,
|
|
526
|
+
orderedAt: msg?.received_at ?? p.created_at ?? "",
|
|
527
|
+
deliveryAt: p.delivery_date ?? "",
|
|
528
|
+
quantity: { multiplier: quantity, count: 1, unitPrice: 0 },
|
|
529
|
+
revenue: 0,
|
|
530
|
+
fustCount: 0,
|
|
531
|
+
karCount: quantity > 0 ? 1 : 0,
|
|
532
|
+
totalPieces: quantity,
|
|
533
|
+
isLocal: true,
|
|
534
|
+
customer: {
|
|
535
|
+
name: matchedCustomer?.name ?? msg?.from_name ?? msg?.from_email ?? "Onbekende klant",
|
|
536
|
+
postalCode: "",
|
|
537
|
+
city: matchedCustomer?.city ?? "",
|
|
538
|
+
logoUrl: null,
|
|
539
|
+
},
|
|
540
|
+
status: statusFlags(p.status),
|
|
541
|
+
raw: { order: { status: p.status ?? null }, customer: null, tradeItem: null },
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
// 12) Samenvoegen, buffergroottes, sorteren.
|
|
545
|
+
const all = [...localOrders, ...floridayOrders].sort((a, b) => (b.orderedAt ?? "").localeCompare(a.orderedAt ?? ""));
|
|
546
|
+
const sizeByBuffer = new Map();
|
|
547
|
+
for (const o of all) {
|
|
548
|
+
if (o.bufferId)
|
|
549
|
+
sizeByBuffer.set(o.bufferId, (sizeByBuffer.get(o.bufferId) ?? 0) + 1);
|
|
550
|
+
}
|
|
551
|
+
for (const o of all)
|
|
552
|
+
if (o.bufferId)
|
|
553
|
+
o.bufferSize = sizeByBuffer.get(o.bufferId) ?? 1;
|
|
554
|
+
// 13) Filteren (zoek + status), tellen, pagineren.
|
|
555
|
+
const search = (params.search ?? "").trim().toLowerCase();
|
|
556
|
+
const matchesSearch = (o) => !search ||
|
|
557
|
+
`${o.productName} ${o.sku} ${o.orderNumber} ${o.customer.name}`.toLowerCase().includes(search);
|
|
558
|
+
const matchesStatusFilter = (o) => {
|
|
559
|
+
switch (params.status) {
|
|
560
|
+
case "cancelled":
|
|
561
|
+
return o.status.isCancelled;
|
|
562
|
+
case "shipped":
|
|
563
|
+
return o.status.isShipped;
|
|
564
|
+
case "invoiced":
|
|
565
|
+
return o.status.isInvoiced;
|
|
566
|
+
case "open":
|
|
567
|
+
return !o.status.isCancelled && !o.status.isShipped;
|
|
568
|
+
default:
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
const matchesTab = (o) => {
|
|
573
|
+
switch (params.tab) {
|
|
574
|
+
case "open":
|
|
575
|
+
return !o.status.isCancelled && !o.status.isShipped;
|
|
576
|
+
case "shipped":
|
|
577
|
+
return o.status.isShipped && !o.status.isCancelled;
|
|
578
|
+
case "invoiced":
|
|
579
|
+
return o.status.isInvoiced;
|
|
580
|
+
case "cancelled":
|
|
581
|
+
return o.status.isCancelled;
|
|
582
|
+
default:
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
const withoutTab = all.filter((o) => matchesSearch(o) && matchesStatusFilter(o));
|
|
587
|
+
const filtered = withoutTab.filter(matchesTab);
|
|
588
|
+
const collapse = (rows) => {
|
|
589
|
+
const seen = new Set();
|
|
590
|
+
const out = [];
|
|
591
|
+
for (const o of rows) {
|
|
592
|
+
const key = o.bufferId && (o.bufferSize ?? 1) > 1 ? `buf:${o.bufferId}` : `one:${o.id}`;
|
|
593
|
+
if (seen.has(key))
|
|
594
|
+
continue;
|
|
595
|
+
seen.add(key);
|
|
596
|
+
out.push(o);
|
|
597
|
+
}
|
|
598
|
+
return out;
|
|
599
|
+
};
|
|
600
|
+
const collapsedAll = collapse(withoutTab);
|
|
601
|
+
const statusCounts = {
|
|
602
|
+
all: collapsedAll.length,
|
|
603
|
+
open: 0,
|
|
604
|
+
shipped: 0,
|
|
605
|
+
invoiced: 0,
|
|
606
|
+
cancelled: 0,
|
|
607
|
+
};
|
|
608
|
+
for (const o of collapsedAll) {
|
|
609
|
+
if (o.status.isCancelled)
|
|
610
|
+
statusCounts.cancelled++;
|
|
611
|
+
else if (o.status.isShipped)
|
|
612
|
+
statusCounts.shipped++;
|
|
613
|
+
else
|
|
614
|
+
statusCounts.open++;
|
|
615
|
+
if (o.status.isInvoiced)
|
|
616
|
+
statusCounts.invoiced++;
|
|
617
|
+
}
|
|
618
|
+
const totals = computeTotals(collapse(filtered));
|
|
619
|
+
const totalCount = filtered.length;
|
|
620
|
+
const rows = params.all
|
|
621
|
+
? filtered.slice(0, EXPORT_CAP)
|
|
622
|
+
: filtered.slice((page - 1) * pageSize, page * pageSize);
|
|
623
|
+
// 14) Karindeling alleen voor de zichtbare rijen (begrensd werk).
|
|
624
|
+
if (!params.all) {
|
|
625
|
+
const localById = new Map(localOrderRows.map((r) => [String(r.id), r]));
|
|
626
|
+
await Promise.all(rows
|
|
627
|
+
.filter((o) => o.id.startsWith("local:"))
|
|
628
|
+
.map(async (o) => {
|
|
629
|
+
const r = localById.get(o.id.slice("local:".length));
|
|
630
|
+
const lineItems = Array.isArray(r?.order_items) ? r.order_items : [];
|
|
631
|
+
if (!lineItems.length)
|
|
632
|
+
return;
|
|
633
|
+
const raws = lineItems.map((it, idx) => {
|
|
634
|
+
const packing = resolveLinePacking(it);
|
|
635
|
+
return {
|
|
636
|
+
id: String(it?.id ?? `${r.id}#${idx}`),
|
|
637
|
+
product_id: it?.product_id ?? null,
|
|
638
|
+
product_name: String(it?.product_name ?? ""),
|
|
639
|
+
quantity: Number(it?.quantity ?? 0),
|
|
640
|
+
plants_per_crate: Math.max(1, Number(packing?.plants_per_crate ?? 1)),
|
|
641
|
+
crates_per_layer: Math.max(1, Number(packing?.crates_per_layer ?? 1)),
|
|
642
|
+
layers_per_cart: Math.max(1, Number(packing?.layers_per_cart ?? 1)),
|
|
643
|
+
};
|
|
644
|
+
});
|
|
645
|
+
try {
|
|
646
|
+
const expanded = await expandOrderItemsForPlanner(supabase, raws);
|
|
647
|
+
if (!expanded.length)
|
|
648
|
+
return;
|
|
649
|
+
const planned = planCarts(expanded);
|
|
650
|
+
if (planned.carts.length > 0)
|
|
651
|
+
o.karCount = planned.carts.length;
|
|
652
|
+
}
|
|
653
|
+
catch {
|
|
654
|
+
/* fallback: reeds berekende karCount */
|
|
655
|
+
}
|
|
656
|
+
}));
|
|
657
|
+
}
|
|
658
|
+
return {
|
|
659
|
+
rows,
|
|
660
|
+
totalCount,
|
|
661
|
+
totals,
|
|
662
|
+
statusCounts,
|
|
663
|
+
sellerFallback,
|
|
664
|
+
environment,
|
|
665
|
+
warnings,
|
|
666
|
+
truncated,
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
// Belading per productgroep (op naam), gebruikt voor "[Groep] X"-regels.
|
|
670
|
+
async function loadGroupPacking(supabase) {
|
|
671
|
+
const [groupsRes, mirrorRes] = await Promise.all([
|
|
672
|
+
supabase.from("product_groups").select("id, name, head_product_id").limit(5000),
|
|
673
|
+
supabase
|
|
674
|
+
.from("products")
|
|
675
|
+
.select("id, plants_per_crate, crates_per_layer, layers_per_cart, trade_item_key")
|
|
676
|
+
.like("trade_item_key", "group:%")
|
|
677
|
+
.limit(5000),
|
|
678
|
+
]);
|
|
679
|
+
if (groupsRes.error)
|
|
680
|
+
throw new Error(groupsRes.error.message);
|
|
681
|
+
const mirrorByGroupId = new Map();
|
|
682
|
+
for (const p of (mirrorRes.data ?? [])) {
|
|
683
|
+
const key = p.trade_item_key;
|
|
684
|
+
if (typeof key === "string" && key.startsWith("group:"))
|
|
685
|
+
mirrorByGroupId.set(key.slice(6), p);
|
|
686
|
+
}
|
|
687
|
+
const groups = (groupsRes.data ?? []);
|
|
688
|
+
const headIds = Array.from(new Set(groups.map((g) => g.head_product_id).filter((v) => typeof v === "string" && !!v)));
|
|
689
|
+
const headById = new Map();
|
|
690
|
+
if (headIds.length) {
|
|
691
|
+
const { data } = await supabase
|
|
692
|
+
.from("products")
|
|
693
|
+
.select("id, plants_per_crate, crates_per_layer, layers_per_cart")
|
|
694
|
+
.in("id", headIds);
|
|
695
|
+
for (const p of (data ?? []))
|
|
696
|
+
headById.set(p.id, p);
|
|
697
|
+
}
|
|
698
|
+
const packFromRow = (row) => row
|
|
699
|
+
? {
|
|
700
|
+
plants_per_crate: Math.max(1, Number(row.plants_per_crate ?? 1)),
|
|
701
|
+
crates_per_layer: Math.max(1, Number(row.crates_per_layer ?? 1)),
|
|
702
|
+
layers_per_cart: Math.max(1, Number(row.layers_per_cart ?? 1)),
|
|
703
|
+
}
|
|
704
|
+
: null;
|
|
705
|
+
const byName = new Map();
|
|
706
|
+
for (const g of groups) {
|
|
707
|
+
const packing = packFromRow(headById.get(g.head_product_id)) ?? packFromRow(mirrorByGroupId.get(g.id));
|
|
708
|
+
const gname = g.name ? String(g.name).trim().toLowerCase() : "";
|
|
709
|
+
if (packing && gname)
|
|
710
|
+
byName.set(gname, packing);
|
|
711
|
+
}
|
|
712
|
+
return byName;
|
|
713
|
+
}
|