@flowselections/logistiek-engine 1.0.175 → 1.0.180
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/db/schema/logistiek-engine.sql +2112 -0
- package/dist-lib/components/BeheerPage.d.ts.map +1 -1
- package/dist-lib/components/BeheerPage.js +2 -1
- package/dist-lib/components/BeladingIndelingCard.d.ts.map +1 -1
- package/dist-lib/components/BeladingIndelingCard.js +2 -0
- package/dist-lib/components/DocumentkeuzeCard.d.ts +2 -0
- package/dist-lib/components/DocumentkeuzeCard.d.ts.map +1 -0
- package/dist-lib/components/DocumentkeuzeCard.js +18 -0
- package/dist-lib/index.d.ts +1 -0
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +1 -0
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts +11 -5
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.js +38 -21
- package/dist-lib/integrations/supabase/types.d.ts +376 -15
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/beladingRegels.d.ts +78 -0
- package/dist-lib/lib/beladingRegels.d.ts.map +1 -0
- package/dist-lib/lib/beladingRegels.js +143 -0
- package/dist-lib/lib/bulkLoadCarrierPlanning.server.js +2 -2
- package/dist-lib/lib/controlScan.server.js +1 -1
- package/dist-lib/lib/eigenTraysticker.d.ts +17 -0
- package/dist-lib/lib/eigenTraysticker.d.ts.map +1 -0
- package/dist-lib/lib/eigenTraysticker.js +32 -0
- package/dist-lib/lib/layoutPrint.server.d.ts +10 -1
- package/dist-lib/lib/layoutPrint.server.d.ts.map +1 -1
- package/dist-lib/lib/layoutPrint.server.js +19 -1
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts +35 -3
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanning.server.js +251 -89
- package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
- package/dist-lib/lib/modulePrompts.js +233 -0
- package/dist-lib/lib/moduleVersion.d.ts +1 -1
- package/dist-lib/lib/moduleVersion.js +1 -1
- package/dist-lib/lib/planQueue.server.d.ts.map +1 -1
- package/dist-lib/lib/planQueue.server.js +26 -2
- package/dist-lib/lib/ruleEngineSchema.d.ts.map +1 -1
- package/dist-lib/lib/ruleEngineSchema.js +4 -38
- package/dist-lib/lib/ruleEngineWizard.d.ts.map +1 -1
- package/dist-lib/lib/ruleEngineWizard.js +0 -2
- package/dist-lib/lib/shippingNoteQueue.server.js +1 -1
- package/dist-lib/lib/stickerQueue.server.d.ts +25 -0
- package/dist-lib/lib/stickerQueue.server.d.ts.map +1 -1
- package/dist-lib/lib/stickerQueue.server.js +150 -22
- package/dist-lib/lib/stickerRules.d.ts +17 -0
- package/dist-lib/lib/stickerRules.d.ts.map +1 -1
- package/dist-lib/lib/stickerRules.js +30 -0
- package/package.json +1 -1
|
@@ -12,6 +12,7 @@ import { fallbackCarrierProfile } from "./loadCarrierTypes.js";
|
|
|
12
12
|
import { planByDimensions, DIMENSION_PLANNER_VERSION } from "./dimensionPlanner.js";
|
|
13
13
|
import { buildDimensionItems, carrierDimensions } from "./dimensionData.server.js";
|
|
14
14
|
import { zonderExternAfgehandeld } from "./externalFulfillment.js";
|
|
15
|
+
import { vulBeladingAan, deelUitFloridayVerpakking, plantenVanRegel, plausibiliteit, karrenOnwaarschijnlijk, verpakkingSleutel, controleerInvariant, VELD_NAAM, } from "./beladingRegels.js";
|
|
15
16
|
// ---------------------------------------------------------------------------
|
|
16
17
|
// Buffer
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -84,62 +85,62 @@ export function beladingPerRegelUitOrder(customFields) {
|
|
|
84
85
|
const tradeItemId = line.trade_item_id ? String(line.trade_item_id) : null;
|
|
85
86
|
if (!tradeItemId)
|
|
86
87
|
continue;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
// 1.0.179: per veld; 0 (klokorders) = onbekend, de rest van de verpakking telt wel.
|
|
89
|
+
const deel = deelUitFloridayVerpakking(line.packing);
|
|
90
|
+
if (deel)
|
|
91
|
+
out.set(tradeItemId, deel);
|
|
90
92
|
}
|
|
91
93
|
return out;
|
|
92
94
|
}
|
|
93
|
-
/** Waarden van de orderregel zelf
|
|
94
|
-
function
|
|
95
|
-
if (
|
|
96
|
-
positive(row.crates_per_layer) == null &&
|
|
97
|
-
positive(row.layers_per_cart) == null) {
|
|
95
|
+
/** Waarden van de orderregel zelf (per veld; 0 of leeg = onbekend). */
|
|
96
|
+
function deelVanRij(row) {
|
|
97
|
+
if (!row)
|
|
98
98
|
return null;
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
layers_per_cart: positive(row.layers_per_cart) ?? 1,
|
|
99
|
+
const d = {
|
|
100
|
+
plants_per_crate: positive(row.plants_per_crate),
|
|
101
|
+
crates_per_layer: positive(row.crates_per_layer),
|
|
102
|
+
layers_per_cart: positive(row.layers_per_cart),
|
|
104
103
|
};
|
|
104
|
+
return d.plants_per_crate != null || d.crates_per_layer != null || d.layers_per_cart != null ? d : null;
|
|
105
105
|
}
|
|
106
106
|
/**
|
|
107
|
-
*
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* 5. de order-brede Floriday-verpakking — uitsluitend als allerlaatste noodgreep
|
|
113
|
-
* voor een regel die van 1 t/m 4 niets heeft;
|
|
114
|
-
* 6. anders onbekend: er wordt niet gegokt, de indeling blokkeert.
|
|
107
|
+
* 1.0.179: belading per veld, nooit een stille 1. Volgorde per veld:
|
|
108
|
+
* handmatig vastgezet (alleen regel, dan product) of anders
|
|
109
|
+
* Floriday-regel -> orderregel-snapshot -> product -> order-brede Floriday-verpakking.
|
|
110
|
+
* De standaard van de ladingdrager vult daarna in de indeling nog fusten per laag en lagen aan.
|
|
111
|
+
* Blijft een veld leeg, dan wordt die regel geblokkeerd (belading_onvolledig).
|
|
115
112
|
*/
|
|
116
|
-
function resolveBelading(row, packing, linePacking = null) {
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
113
|
+
export function resolveBelading(row, packing, linePacking = null) {
|
|
114
|
+
const regel = deelVanRij(row);
|
|
115
|
+
const product = deelVanRij(row.products ?? null);
|
|
116
|
+
const bronnen = row.packing_locked_at && regel
|
|
117
|
+
? [
|
|
118
|
+
{ naam: "handmatig", waarden: regel },
|
|
119
|
+
{ naam: "product", waarden: product },
|
|
120
|
+
]
|
|
121
|
+
: [
|
|
122
|
+
{ naam: "floriday_line", waarden: linePacking },
|
|
123
|
+
{ naam: "order_items", waarden: regel },
|
|
124
|
+
{ naam: "product", waarden: product },
|
|
125
|
+
{ naam: "floriday_packing", waarden: packing },
|
|
126
|
+
];
|
|
127
|
+
const u = vulBeladingAan(bronnen);
|
|
128
|
+
const gebruikte = new Set(Object.values(u.bronPerVeld));
|
|
129
|
+
const source = (u.bronPerVeld.plants_per_crate ?? u.bronPerVeld.crates_per_layer ?? u.bronPerVeld.layers_per_cart ?? "geen");
|
|
130
|
+
const deel = u.belading ?? {
|
|
131
|
+
plants_per_crate: 0,
|
|
132
|
+
crates_per_layer: 0,
|
|
133
|
+
layers_per_cart: 0,
|
|
134
|
+
};
|
|
135
|
+
if (!u.belading) {
|
|
136
|
+
for (const b of bronnen) {
|
|
137
|
+
for (const v of ["plants_per_crate", "crates_per_layer", "layers_per_cart"]) {
|
|
138
|
+
if (deel[v] === 0 && positive(b.waarden?.[v]) != null)
|
|
139
|
+
deel[v] = positive(b.waarden?.[v]);
|
|
140
|
+
}
|
|
133
141
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
if (fromProduct)
|
|
137
|
-
return { belading: fromProduct, source: "product" };
|
|
138
|
-
// 5. Allerlaatste noodgreep: de order-brede Floriday-verpakking.
|
|
139
|
-
if (packing)
|
|
140
|
-
return { belading: packing, source: "floriday_packing" };
|
|
141
|
-
// 6. Onbekend.
|
|
142
|
-
return { belading: { plants_per_crate: 1, crates_per_layer: 1, layers_per_cart: 1 }, source: "geen" };
|
|
142
|
+
}
|
|
143
|
+
return { belading: deel, source, ontbrekend: u.ontbrekend, aangevuld: gebruikte.size > 1 };
|
|
143
144
|
}
|
|
144
145
|
/** Schaal de onderdelen van een setregel naar het aantal sets op één kar. */
|
|
145
146
|
export function scaleComponents(entry, plantsOnCart) {
|
|
@@ -225,8 +226,14 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
225
226
|
lineMap.get(String(row.products?.floriday_trade_item_id ?? "")) ??
|
|
226
227
|
null
|
|
227
228
|
: null;
|
|
228
|
-
const
|
|
229
|
-
const
|
|
229
|
+
const opgelost = resolveBelading(row, packing, linePacking);
|
|
230
|
+
const { belading: base, source } = opgelost;
|
|
231
|
+
// 1.0.179: intern altijd planten; eenheid fust = quantity x planten per fust van de regel.
|
|
232
|
+
const planten = plantenVanRegel(row.unit, row.quantity, row.plants_per_crate ?? base.plants_per_crate);
|
|
233
|
+
if (planten == null) {
|
|
234
|
+
options.onbekendePlanten?.push({ order_id: orderKey, order_item_id: String(row.id), naam: String(row.product_name ?? "onbekend") });
|
|
235
|
+
}
|
|
236
|
+
const plants = Math.max(0, planten ?? 0);
|
|
230
237
|
const lines = linesByItem.get(String(row.id)) ?? [];
|
|
231
238
|
const kind = lines[0]?.unit_kind ?? "artikel";
|
|
232
239
|
if (kind === "uitgesloten") {
|
|
@@ -242,6 +249,8 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
242
249
|
}
|
|
243
250
|
if (kind !== "groepslid") {
|
|
244
251
|
options.sources?.set(String(row.id), source);
|
|
252
|
+
if (opgelost.ontbrekend.length > 0)
|
|
253
|
+
options.ontbrekend?.set(String(row.id), opgelost.ontbrekend);
|
|
245
254
|
if (kind === "set") {
|
|
246
255
|
options.components?.set(String(row.id), { lineQty: plants, components: toComponents(lines[0]?.components) });
|
|
247
256
|
}
|
|
@@ -253,7 +262,7 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
253
262
|
product_name: row.product_name ?? "Onbekend",
|
|
254
263
|
plants,
|
|
255
264
|
...base,
|
|
256
|
-
packing_authoritative: isAuthoritative(source),
|
|
265
|
+
packing_authoritative: isAuthoritative(source) && !opgelost.aangevuld && opgelost.ontbrekend.length === 0,
|
|
257
266
|
weight_per_crate: row.products?.weight != null ? Number(row.products.weight) : null,
|
|
258
267
|
});
|
|
259
268
|
continue;
|
|
@@ -266,37 +275,16 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
266
275
|
if (!p) {
|
|
267
276
|
throw new Error(`Indeling geblokkeerd voor ${row.product_name ?? "groep"}: groepslid ${l.trade_item_key ?? "onbekend"} heeft geen artikel.`);
|
|
268
277
|
}
|
|
269
|
-
let belading;
|
|
270
|
-
let src;
|
|
271
278
|
const eigenLijn = lineMap?.get(String(p.floriday_trade_item_id ?? "")) ?? lineMap?.get(String(p.id)) ?? null;
|
|
272
|
-
|
|
273
|
-
belading
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
belading = { ...eigenLijn };
|
|
278
|
-
src = "floriday_line";
|
|
279
|
-
}
|
|
280
|
-
else if (positive(p.plants_per_crate) != null ||
|
|
281
|
-
positive(p.crates_per_layer) != null ||
|
|
282
|
-
positive(p.layers_per_cart) != null) {
|
|
283
|
-
belading = {
|
|
284
|
-
plants_per_crate: positive(p.plants_per_crate) ?? 1,
|
|
285
|
-
crates_per_layer: positive(p.crates_per_layer) ?? 1,
|
|
286
|
-
layers_per_cart: positive(p.layers_per_cart) ?? 1,
|
|
287
|
-
};
|
|
288
|
-
src = "product";
|
|
289
|
-
}
|
|
290
|
-
else if (packing) {
|
|
291
|
-
belading = { ...packing };
|
|
292
|
-
src = "floriday_packing";
|
|
293
|
-
}
|
|
294
|
-
else {
|
|
295
|
-
belading = { ...base };
|
|
296
|
-
src = "geen";
|
|
297
|
-
}
|
|
279
|
+
const lid = source === "handmatig"
|
|
280
|
+
? { belading: { ...base }, source: source, ontbrekend: opgelost.ontbrekend, aangevuld: opgelost.aangevuld }
|
|
281
|
+
: resolveBelading({ products: p }, packing, eigenLijn);
|
|
282
|
+
const belading = lid.belading;
|
|
283
|
+
const src = lid.source;
|
|
298
284
|
const itemKey = `${row.id}::${l.trade_item_key ?? p.id}`;
|
|
299
285
|
options.sources?.set(itemKey, src);
|
|
286
|
+
if (lid.ontbrekend.length > 0)
|
|
287
|
+
options.ontbrekend?.set(itemKey, lid.ontbrekend);
|
|
300
288
|
out.push({
|
|
301
289
|
order_item_id: itemKey,
|
|
302
290
|
order_id: row.order_id ?? null,
|
|
@@ -305,13 +293,71 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
305
293
|
product_name: `${row.product_name ?? "Groep"} — ${p.product ?? "onbekend groepslid"}`,
|
|
306
294
|
plants: qty,
|
|
307
295
|
...belading,
|
|
308
|
-
packing_authoritative: isAuthoritative(src),
|
|
296
|
+
packing_authoritative: isAuthoritative(src) && !lid.aangevuld && lid.ontbrekend.length === 0,
|
|
309
297
|
weight_per_crate: p.weight != null ? Number(p.weight) : null,
|
|
310
298
|
});
|
|
311
299
|
}
|
|
312
300
|
}
|
|
313
301
|
return out;
|
|
314
302
|
}
|
|
303
|
+
async function bevestigdeSleutels(sb, orderIds) {
|
|
304
|
+
const set = new Set();
|
|
305
|
+
if (orderIds.length === 0)
|
|
306
|
+
return set;
|
|
307
|
+
const { data, error } = await sb
|
|
308
|
+
.from("logistics_belading_bevestigingen")
|
|
309
|
+
.select("order_id, verpakking_sleutel")
|
|
310
|
+
.in("order_id", orderIds);
|
|
311
|
+
if (error)
|
|
312
|
+
throw new Error(`Indeling geblokkeerd: bevestigingen konden niet worden gelezen (${error.message}).`);
|
|
313
|
+
for (const r of (Array.isArray(data) ? data : []))
|
|
314
|
+
set.add(`${r.order_id}|${r.verpakking_sleutel}`);
|
|
315
|
+
return set;
|
|
316
|
+
}
|
|
317
|
+
async function beladingOnwaarschijnlijk(sb, inputs, rawRows, profile) {
|
|
318
|
+
const fustIds = Array.from(new Set(rawRows.map((r) => r.products?.fust_type_id).filter((x) => !!x).map(String)));
|
|
319
|
+
const fustById = new Map();
|
|
320
|
+
if (fustIds.length > 0) {
|
|
321
|
+
const { data } = await sb.from("fust_types").select("id, name, capacity_units").in("id", fustIds);
|
|
322
|
+
for (const f of (Array.isArray(data) ? data : []))
|
|
323
|
+
fustById.set(String(f.id), f);
|
|
324
|
+
}
|
|
325
|
+
const fustPerProduct = new Map();
|
|
326
|
+
for (const r of rawRows)
|
|
327
|
+
if (r.product_id)
|
|
328
|
+
fustPerProduct.set(String(r.product_id), r.products?.fust_type_id ?? null);
|
|
329
|
+
const kandidaten = [];
|
|
330
|
+
for (const i of inputs) {
|
|
331
|
+
if (i.plants <= 0)
|
|
332
|
+
continue;
|
|
333
|
+
const fustId = i.product_id ? fustPerProduct.get(String(i.product_id)) ?? null : null;
|
|
334
|
+
const redenen = plausibiliteit({
|
|
335
|
+
belading: { plants_per_crate: i.plants_per_crate, crates_per_layer: i.crates_per_layer, layers_per_cart: i.layers_per_cart },
|
|
336
|
+
fust: fustId ? fustById.get(fustId) ?? null : null,
|
|
337
|
+
maxLagenLadingdrager: i.packing_authoritative ? null : profile.layers_per_carrier,
|
|
338
|
+
});
|
|
339
|
+
if (redenen.length === 0)
|
|
340
|
+
continue;
|
|
341
|
+
kandidaten.push({
|
|
342
|
+
order_id: i.order_id ?? null,
|
|
343
|
+
product: i.product_name,
|
|
344
|
+
sleutel: verpakkingSleutel(i.product_id, i),
|
|
345
|
+
redenen,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (kandidaten.length === 0)
|
|
349
|
+
return [];
|
|
350
|
+
const ok = await bevestigdeSleutels(sb, Array.from(new Set(kandidaten.map((k) => k.order_id).filter((x) => !!x))));
|
|
351
|
+
return kandidaten.filter((k) => !ok.has(`${k.order_id}|${k.sleutel}`));
|
|
352
|
+
}
|
|
353
|
+
/** Invariant: elk fust van elke ingedeelde regel staat op precies één kar en de planten kloppen. */
|
|
354
|
+
export function invariantFouten(inputs, carts) {
|
|
355
|
+
const regels = inputs
|
|
356
|
+
.filter((i) => i.plants > 0 && i.plants_per_crate > 0)
|
|
357
|
+
.map((i) => ({ id: i.order_item_id, planten: i.plants, plants_per_crate: i.plants_per_crate }));
|
|
358
|
+
const karRegels = carts.flatMap((c, idx) => c.items.map((it) => ({ regelId: it.order_item_id, cartId: String(idx), fusten: it.crates, planten: null })));
|
|
359
|
+
return controleerInvariant(regels, karRegels);
|
|
360
|
+
}
|
|
315
361
|
// ---------------------------------------------------------------------------
|
|
316
362
|
// Ladingdragertypen
|
|
317
363
|
// ---------------------------------------------------------------------------
|
|
@@ -734,6 +780,9 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
734
780
|
// Een proefberekening schrijft niets weg en hoeft dus niets te vergrendelen.
|
|
735
781
|
if (options.dryRun)
|
|
736
782
|
return replanBufferUnlocked(sb, bufferId, options);
|
|
783
|
+
// Bijdelen binnen dezelfde ronde: de lease is al in handen, herbruiken.
|
|
784
|
+
if (options.leaseHeld)
|
|
785
|
+
return replanBufferUnlocked(sb, bufferId, options);
|
|
737
786
|
const lockKey = `buffer:${bufferId}`;
|
|
738
787
|
let held = false;
|
|
739
788
|
try {
|
|
@@ -756,7 +805,7 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
756
805
|
replacedManualCount: 0,
|
|
757
806
|
usedFloridayPacking: false,
|
|
758
807
|
warnings: [BUFFER_BUSY_MESSAGE],
|
|
759
|
-
skipped: "
|
|
808
|
+
skipped: "busy",
|
|
760
809
|
};
|
|
761
810
|
}
|
|
762
811
|
try {
|
|
@@ -866,7 +915,11 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
866
915
|
const supplement = await replanBufferInternal(sb, bufferId, {
|
|
867
916
|
...options,
|
|
868
917
|
keepCartIds: keep,
|
|
918
|
+
leaseHeld: true,
|
|
869
919
|
});
|
|
920
|
+
// Melding alleen bij succes; anders de echte uitkomst (geblokkeerd/bezet) doorgeven.
|
|
921
|
+
if (!supplement.ok || supplement.skipped)
|
|
922
|
+
return supplement;
|
|
870
923
|
return {
|
|
871
924
|
...supplement,
|
|
872
925
|
warnings: [
|
|
@@ -884,9 +937,9 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
884
937
|
const idList = orderIds.length > 0 ? orderIds : ["00000000-0000-0000-0000-000000000000"];
|
|
885
938
|
const { data: rawItems } = await sb
|
|
886
939
|
.from("order_items")
|
|
887
|
-
.select("id, order_id, product_id, product_name, quantity, plants_per_crate, crates_per_layer, layers_per_cart, " +
|
|
940
|
+
.select("id, order_id, product_id, product_name, quantity, unit, plants_per_crate, crates_per_layer, layers_per_cart, " +
|
|
888
941
|
"packing_locked_at, packing_locked_by, source_line_key, " +
|
|
889
|
-
"products(plants_per_crate, crates_per_layer, layers_per_cart, weight, floriday_trade_item_id)")
|
|
942
|
+
"products(plants_per_crate, crates_per_layer, layers_per_cart, weight, floriday_trade_item_id, fust_type_id)")
|
|
890
943
|
.in("order_id", idList)
|
|
891
944
|
// Regels in quarantaine (Verkoop-herstel) nooit inplannen.
|
|
892
945
|
.is("quarantined_at", null);
|
|
@@ -973,12 +1026,16 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
973
1026
|
const sources = new Map();
|
|
974
1027
|
const setComponents = new Map();
|
|
975
1028
|
const expandNotes = [];
|
|
1029
|
+
const ontbrekendeVelden = new Map();
|
|
1030
|
+
const onbekendePlanten = [];
|
|
976
1031
|
let inputs = await expandOrderItemsForPlanner(sb, rawRows, {
|
|
977
1032
|
packingByOrder,
|
|
978
1033
|
packingByOrderLine,
|
|
979
1034
|
sources,
|
|
980
1035
|
components: setComponents,
|
|
981
1036
|
notes: expandNotes,
|
|
1037
|
+
ontbrekend: ontbrekendeVelden,
|
|
1038
|
+
onbekendePlanten,
|
|
982
1039
|
});
|
|
983
1040
|
// Groepsregel naast ledenregels: niet ingedeeld, wel zichtbaar gemeld bij de order.
|
|
984
1041
|
for (const n of expandNotes) {
|
|
@@ -1108,23 +1165,53 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
1108
1165
|
// lagenmodel (los product / enkele eenheid) hebben de belading niet nodig.
|
|
1109
1166
|
const guardProfile = profileFor(profiles, options.carrierType ?? defaultProfile.code);
|
|
1110
1167
|
const needsBelading = guardProfile.capacity_model !== "single" && guardProfile.capacity_model !== "none";
|
|
1111
|
-
const unknownItems = inputs.filter((i) => sources.get(i.order_item_id) === "geen" && i.plants > 0);
|
|
1112
1168
|
const useDimensions = rules?.layoutMethod === "afmetingen";
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1169
|
+
// 1.0.179 (punt 1/2): ontbrekende fusten per laag en lagen aanvullen uit de standaard van de ladingdrager.
|
|
1170
|
+
const standaard = {
|
|
1171
|
+
crates_per_layer: positive(guardProfile.max_crates_per_layer),
|
|
1172
|
+
layers_per_cart: positive(guardProfile.layers_per_carrier),
|
|
1173
|
+
};
|
|
1174
|
+
const onvolledig = [];
|
|
1175
|
+
for (const i of inputs) {
|
|
1176
|
+
const mist = ontbrekendeVelden.get(i.order_item_id);
|
|
1177
|
+
if (!mist || mist.length === 0)
|
|
1178
|
+
continue;
|
|
1179
|
+
const rest = [];
|
|
1180
|
+
for (const v of mist) {
|
|
1181
|
+
const w = v === "plants_per_crate" ? null : standaard[v] ?? null;
|
|
1182
|
+
if (w != null) {
|
|
1183
|
+
i[v] = w;
|
|
1184
|
+
i.packing_authoritative = false;
|
|
1185
|
+
}
|
|
1186
|
+
else
|
|
1187
|
+
rest.push(v);
|
|
1188
|
+
}
|
|
1189
|
+
if (rest.length > 0 && i.plants > 0)
|
|
1190
|
+
onvolledig.push({ item: i, velden: rest });
|
|
1191
|
+
}
|
|
1192
|
+
// Planten per fust is altijd nodig (ook zonder lagenmodel): fusten = planten / planten per fust.
|
|
1193
|
+
const blokkeerOnvolledig = onvolledig.filter((o) => o.velden.includes("plants_per_crate") || (needsBelading && !useDimensions));
|
|
1194
|
+
if ((blokkeerOnvolledig.length > 0 || onbekendePlanten.length > 0) && !options.dryRun) {
|
|
1195
|
+
const delen = [
|
|
1196
|
+
...blokkeerOnvolledig.slice(0, 10).map((o) => `${o.item.product_name}: ${o.velden.map((v) => VELD_NAAM[v]).join(", ")} onbekend`),
|
|
1197
|
+
...onbekendePlanten.slice(0, 10).map((o) => `${o.naam}: eenheid fust zonder planten per fust`),
|
|
1198
|
+
];
|
|
1199
|
+
const reason = `Belading onvolledig voor ${blokkeerOnvolledig.length + onbekendePlanten.length} orderregel(s): ${delen.join("; ")}. ` +
|
|
1200
|
+
"Er wordt niet gegokt; vul de ontbrekende waarde in (orderregel, product of ladingdrager).";
|
|
1116
1201
|
console.error(`[logistiek-engine] buffer ${bufferId} geblokkeerd: ${reason}`);
|
|
1117
1202
|
const { error: blockError } = await sb.rpc("logistics_plan_block", {
|
|
1118
1203
|
p_order_ids: orderIds,
|
|
1119
1204
|
p_reason: reason,
|
|
1120
1205
|
p_details: {
|
|
1121
|
-
|
|
1122
|
-
reason_code: "belading_onbekend",
|
|
1206
|
+
reason_code: "belading_onvolledig",
|
|
1123
1207
|
bufferId,
|
|
1124
1208
|
path: "typescript",
|
|
1125
1209
|
carrier: guardProfile.code,
|
|
1126
1210
|
beladingSources,
|
|
1127
|
-
items:
|
|
1211
|
+
items: [
|
|
1212
|
+
...blokkeerOnvolledig.slice(0, 20).map((o) => ({ id: o.item.order_item_id, product: o.item.product_name, velden: o.velden })),
|
|
1213
|
+
...onbekendePlanten.slice(0, 20).map((o) => ({ id: o.order_item_id, product: o.naam, velden: ["plants_per_crate"] })),
|
|
1214
|
+
],
|
|
1128
1215
|
},
|
|
1129
1216
|
});
|
|
1130
1217
|
if (blockError)
|
|
@@ -1142,6 +1229,33 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
1142
1229
|
beladingSources,
|
|
1143
1230
|
};
|
|
1144
1231
|
}
|
|
1232
|
+
// 1.0.179 (punt 3): plausibiliteit, alle ordersoorten. Eén bevestiging per order en verpakking.
|
|
1233
|
+
if (!options.dryRun) {
|
|
1234
|
+
const onwaarschijnlijk = await beladingOnwaarschijnlijk(sb, inputs, rawRows, guardProfile);
|
|
1235
|
+
if (onwaarschijnlijk.length > 0) {
|
|
1236
|
+
const reason = `Belading onwaarschijnlijk: ${onwaarschijnlijk.slice(0, 10).map((o) => `${o.product}: ${o.redenen.join("; ")}`).join(" | ")}. ` +
|
|
1237
|
+
"Kantoor moet dit bevestigen voordat er wordt ingedeeld.";
|
|
1238
|
+
const { error: blockError } = await sb.rpc("logistics_plan_block", {
|
|
1239
|
+
p_order_ids: orderIds,
|
|
1240
|
+
p_reason: reason,
|
|
1241
|
+
p_details: { reason_code: "belading_onwaarschijnlijk", bufferId, path: "typescript", bevestigen: onwaarschijnlijk.slice(0, 50) },
|
|
1242
|
+
});
|
|
1243
|
+
if (blockError)
|
|
1244
|
+
warnings.push(`Blokkade kon niet worden vastgelegd: ${blockError.message}`);
|
|
1245
|
+
return {
|
|
1246
|
+
ok: false,
|
|
1247
|
+
bufferId,
|
|
1248
|
+
orderIds,
|
|
1249
|
+
cartsCreated: 0,
|
|
1250
|
+
replacedManualCount: 0,
|
|
1251
|
+
usedFloridayPacking: false,
|
|
1252
|
+
warnings: [...warnings, reason],
|
|
1253
|
+
skipped: "blocked",
|
|
1254
|
+
blockedReason: reason,
|
|
1255
|
+
beladingSources,
|
|
1256
|
+
};
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1145
1259
|
let plannedCarts = [];
|
|
1146
1260
|
// Laagindeling van de afmetingenmethode, per cart_number. Alleen gevuld als
|
|
1147
1261
|
// er daadwerkelijk op afmetingen is ingedeeld.
|
|
@@ -1319,6 +1433,54 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
1319
1433
|
beladingSources,
|
|
1320
1434
|
};
|
|
1321
1435
|
}
|
|
1436
|
+
// 1.0.179 (punt 3): meer dan 50 karren voor één order vraagt bevestiging van kantoor.
|
|
1437
|
+
// 1.0.179 (punt 11): invariant; bij een verschil wordt niets weggeschreven en niets geprint.
|
|
1438
|
+
if (!options.dryRun) {
|
|
1439
|
+
const perOrder = new Map();
|
|
1440
|
+
for (const c of plannedCarts) {
|
|
1441
|
+
for (const oid of new Set(c.items.map((it) => it.order_id).filter((x) => !!x))) {
|
|
1442
|
+
perOrder.set(oid, (perOrder.get(oid) ?? 0) + 1);
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
const teVeel = Array.from(perOrder.entries()).filter(([, n]) => karrenOnwaarschijnlijk(n) != null);
|
|
1446
|
+
if (teVeel.length > 0) {
|
|
1447
|
+
const ok = await bevestigdeSleutels(sb, teVeel.map(([oid]) => oid));
|
|
1448
|
+
const open = teVeel.filter(([oid, n]) => !ok.has(`${oid}|karren:${n}`));
|
|
1449
|
+
if (open.length > 0) {
|
|
1450
|
+
const reason = `Belading onwaarschijnlijk: ${open.map(([oid, n]) => `order ${orderRows.find((o) => String(o.id) === oid)?.order_number ?? oid}: ${karrenOnwaarschijnlijk(n)}`).join("; ")}. ` +
|
|
1451
|
+
"Kantoor moet dit bevestigen voordat er wordt ingedeeld.";
|
|
1452
|
+
await sb.rpc("logistics_plan_block", {
|
|
1453
|
+
p_order_ids: orderIds,
|
|
1454
|
+
p_reason: reason,
|
|
1455
|
+
p_details: {
|
|
1456
|
+
reason_code: "belading_onwaarschijnlijk",
|
|
1457
|
+
bufferId,
|
|
1458
|
+
path: "typescript",
|
|
1459
|
+
bevestigen: open.map(([oid, n]) => ({ order_id: oid, sleutel: `karren:${n}`, redenen: [karrenOnwaarschijnlijk(n)] })),
|
|
1460
|
+
},
|
|
1461
|
+
});
|
|
1462
|
+
return {
|
|
1463
|
+
ok: false, bufferId, orderIds, cartsCreated: 0, replacedManualCount, usedFloridayPacking,
|
|
1464
|
+
warnings: [...warnings, reason], skipped: "blocked", blockedReason: reason, beladingSources,
|
|
1465
|
+
};
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
if (!useDimensions) {
|
|
1469
|
+
const fouten = invariantFouten(inputs, plannedCarts);
|
|
1470
|
+
if (fouten.length > 0) {
|
|
1471
|
+
const reason = `Indeling klopt niet: ${fouten.slice(0, 5).join(" ")} Er is niets weggeschreven en niets geprint.`;
|
|
1472
|
+
await sb.rpc("logistics_plan_block", {
|
|
1473
|
+
p_order_ids: orderIds,
|
|
1474
|
+
p_reason: reason,
|
|
1475
|
+
p_details: { reason_code: "invariant_fusten", bufferId, path: "typescript", fouten: fouten.slice(0, 20) },
|
|
1476
|
+
});
|
|
1477
|
+
return {
|
|
1478
|
+
ok: false, bufferId, orderIds, cartsCreated: 0, replacedManualCount, usedFloridayPacking,
|
|
1479
|
+
warnings: [...warnings, reason], skipped: "blocked", blockedReason: reason, beladingSources,
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1322
1484
|
if (options.dryRun) {
|
|
1323
1485
|
return {
|
|
1324
1486
|
ok: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modulePrompts.d.ts","sourceRoot":"","sources":["../../src/lib/modulePrompts.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;CAKvB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,oBAAoB,CAAC;AAKhE,MAAM,WAAW,oBAAoB;IACnC,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,eAAe,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,oBAAoB,EAAE,CAAC;CACnC;
|
|
1
|
+
{"version":3,"file":"modulePrompts.d.ts","sourceRoot":"","sources":["../../src/lib/modulePrompts.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;;CAKvB,CAAC;AAEX,MAAM,MAAM,eAAe,GAAG,MAAM,OAAO,oBAAoB,CAAC;AAKhE,MAAM,WAAW,oBAAoB;IACnC,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,iFAAiF;IACjF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+BAA+B;IAC/B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,eAAe,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,oBAAoB,EAAE,CAAC;CACnC;AAosMD,eAAO,MAAM,cAAc,EAAE,YAAY,EAUL,CAAC;AAErC,gEAAgE;AAChE,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,YAAY,EACpB,QAAQ,EAAE,oBAAoB,GAC7B,MAAM,CAmBR;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAE9D;AAED,oEAAoE;AACpE,wBAAgB,qBAAqB,IAAI,MAAM,CAiB9C;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,eAAe,EAC1B,cAAc,CAAC,EAAE,MAAM,GACtB,MAAM,CAIR"}
|