@flowselections/logistiek-engine 1.0.177 → 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 +850 -0
- package/dist-lib/components/BeladingIndelingCard.d.ts.map +1 -1
- package/dist-lib/components/BeladingIndelingCard.js +2 -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 +137 -0
- 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/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/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 +76 -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/stickerQueue.server.d.ts +1 -1
- package/dist-lib/lib/stickerQueue.server.d.ts.map +1 -1
- package/dist-lib/lib/stickerQueue.server.js +88 -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"}
|
|
@@ -1241,6 +1241,40 @@ CONTROLELIJST
|
|
|
1241
1241
|
order: 2,
|
|
1242
1242
|
summary: "Voert belading, controle, printen en transport uit volgens de taak-payload.",
|
|
1243
1243
|
revisions: [
|
|
1244
|
+
{
|
|
1245
|
+
number: 30,
|
|
1246
|
+
title: "Karnummer koppelen aan het kar-id",
|
|
1247
|
+
date: "2026-10-21",
|
|
1248
|
+
summary: "cart_numbers heeft een kolom cart_id; vul die bij elk nieuw karnummer.",
|
|
1249
|
+
body: `KOPPELPROMPT ENGINE 1.0.179 – karnummers op kar-id
|
|
1250
|
+
|
|
1251
|
+
De Engine heeft cart_numbers uitgebreid met cart_id uuid (stabiel na herindeling) en
|
|
1252
|
+
een unieke index (pick_date, cart_id) waar cart_id gevuld is. Graag:
|
|
1253
|
+
- Vul in assign_cart_numbers bij elk nieuw karnummer ook cart_id = order_carts.id.
|
|
1254
|
+
- Zoek bestaande nummers voortaan eerst op cart_id, pas daarna op order + cart_index.
|
|
1255
|
+
- logistics_cart_seq geeft nu een duidelijke fout als een order twee karren met
|
|
1256
|
+
hetzelfde volgnummer heeft (er wordt geen kar meer weggelaten). Toon die fout.
|
|
1257
|
+
- logistics_cart_seq is niet meer aan te roepen als anon; ingelogd en server blijven werken.
|
|
1258
|
+
Bestaande nummers worden pas gekoppeld na akkoord van de opdrachtgever.
|
|
1259
|
+
Verander niets aan de Engine-functies.`,
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
number: 29,
|
|
1263
|
+
title: "Antwoord 1.0.74: fusten en land per kar",
|
|
1264
|
+
date: "2026-10-21",
|
|
1265
|
+
summary: "control_get_todays_carts() geeft per kar ook crates_on_cart en country_code.",
|
|
1266
|
+
body: `ANTWOORD ENGINE 1.0.178 – Afhandeling 1.0.74 (fusten en land per kar)
|
|
1267
|
+
|
|
1268
|
+
control_get_todays_carts() heeft achteraan twee kolommen gekregen:
|
|
1269
|
+
- crates_on_cart integer: som van order_cart_items.crates op deze kar (op kar-id).
|
|
1270
|
+
Bij een gedeelde kar het totaal van alle orders, één keer geteld. Geen indeling: NULL.
|
|
1271
|
+
- country_code text: ISO-landcode (2 letters) van de afleverlocatie van de eerste
|
|
1272
|
+
order op de kar (op ordernummer), via orders.custom_fields->>'delivery_location'
|
|
1273
|
+
-> customer_delivery_locations.id. Geen geldige 2-letterige code (bijv. "Nederland")
|
|
1274
|
+
of geen locatie: NULL; er wordt niet omgezet uit een landnaam.
|
|
1275
|
+
Bestaande kolommen, volgorde, rechten en filtering zijn ongewijzigd.
|
|
1276
|
+
Je hoeft niets aan de Engine aan te passen.`,
|
|
1277
|
+
},
|
|
1244
1278
|
{
|
|
1245
1279
|
number: 28,
|
|
1246
1280
|
title: "Geen layoutkeuze meer: document automatisch uit de order",
|
|
@@ -5317,6 +5351,48 @@ Zelf rekenen doe je niet.
|
|
|
5317
5351
|
order: 9,
|
|
5318
5352
|
summary: "Levert de documentlayouts. Kiest zelf geen layout en geen printer: de Engine bepaalt welke layout en welke printplek bij een document horen.",
|
|
5319
5353
|
revisions: [
|
|
5354
|
+
{
|
|
5355
|
+
number: 13,
|
|
5356
|
+
title: "Correctie 1.69.2: eigen traysticker per fust",
|
|
5357
|
+
date: "2026-10-21",
|
|
5358
|
+
summary: "Een tray is een fust, geen laag: de Engine maakt één eigen traysticker per fust.",
|
|
5359
|
+
body: `CORRECTIE ENGINE 1.0.178 – Layouts 1.69.2 (eigen traystickers per fust)
|
|
5360
|
+
|
|
5361
|
+
Vervangt de laag-indeling uit antwoord 12. Een tray is een FUST.
|
|
5362
|
+
- Per orderregel op een kar: order_cart_items.crates opdrachten, één per fust.
|
|
5363
|
+
- tray_nummer / tray_totaal = fust x van y binnen de hele order
|
|
5364
|
+
(volgorde: kar, regel, fust).
|
|
5365
|
+
- tray_producten: één item, het product van die regel (bij een samengesteld
|
|
5366
|
+
product zoals een mixtray het samengestelde product), met
|
|
5367
|
+
aantal = planten per fust (regel, anders product); het laatste fust van een
|
|
5368
|
+
regel krijgt de rest. Optioneel 'onderdelen' als de karregel componenten heeft.
|
|
5369
|
+
- Idempotentie: eigen_traysticker:<order>:<kar>:<karregel>:<fustnummer>:<layout>.
|
|
5370
|
+
- Aantal fusten onbekend: alleen die sticker wacht met "Aantal fusten onbekend".
|
|
5371
|
+
- Verder ongewijzigd: _documenttype 'traysticker', _herkomst 'eigen',
|
|
5372
|
+
_kopieen 1, layout via layout_voor_document, geen logo_url, zelfde vrijgave.`,
|
|
5373
|
+
},
|
|
5374
|
+
{
|
|
5375
|
+
number: 12,
|
|
5376
|
+
title: "Antwoord 1.69.2: eigen traystickers per tray",
|
|
5377
|
+
date: "2026-10-21",
|
|
5378
|
+
summary: "De Engine print traystickers voor eigen orders, één opdracht per tray, via layout_print_document.",
|
|
5379
|
+
body: `ANTWOORD ENGINE 1.0.178 – Layouts 1.69.2 (eigen traystickers)
|
|
5380
|
+
|
|
5381
|
+
Voor eigen orders (HO/MO/AO) maakt de Engine per tray (laag) van de karindeling
|
|
5382
|
+
één opdracht via layout_print_document:
|
|
5383
|
+
- _documenttype 'traysticker', _herkomst 'eigen', _kopieen 1
|
|
5384
|
+
- _layout_id uit logistics_document_for_order(order_id, 'traysticker'), die
|
|
5385
|
+
layout_voor_document aanroept (= fs.eigen_traysticker)
|
|
5386
|
+
- _payload: order_id, order_number, ordernummer, klantnaam, leverdatum,
|
|
5387
|
+
afleverlocatie, cart_id, print_plek_id, tray_nummer, tray_totaal,
|
|
5388
|
+
tray_producten [{ artikelnummer, naam, aantal }]
|
|
5389
|
+
- Geen logo_url: het logo van de eigen organisatie haal je zelf uit de bedrijfsgegevens.
|
|
5390
|
+
- Idempotentie: eigen_traysticker:<order>:<kar>:<laag>:<layout>.
|
|
5391
|
+
Vrijgave: alleen als de instelling Tray-stickers niet op "Niet printen" staat;
|
|
5392
|
+
het moment is de pakbon van de kar (eigen orders hebben geen Floriday-belading).
|
|
5393
|
+
Ontbreekt de trayindeling of de layout: voor die tray geen opdracht, met uitleg;
|
|
5394
|
+
de rest van de order gaat door. Floriday-traystickers zijn ongewijzigd.`,
|
|
5395
|
+
},
|
|
5320
5396
|
{
|
|
5321
5397
|
number: 11,
|
|
5322
5398
|
title: "Verzenddocument: de Engine bepaalt afleverbrief of veilingbrief",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const MODULE_VERSION = "1.0.
|
|
1
|
+
export declare const MODULE_VERSION = "1.0.180";
|
|
2
2
|
//# sourceMappingURL=moduleVersion.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Automatisch gegenereerd door prebuild:lib; niet handmatig aanpassen.
|
|
2
|
-
export const MODULE_VERSION = "1.0.
|
|
2
|
+
export const MODULE_VERSION = "1.0.180";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"planQueue.server.d.ts","sourceRoot":"","sources":["../../src/lib/planQueue.server.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"planQueue.server.d.ts","sourceRoot":"","sources":["../../src/lib/planQueue.server.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAS5D,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,2FAA2F;IAC3F,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAwBD,wFAAwF;AACxF,wBAAsB,YAAY,CAChC,EAAE,EAAE,cAAc,EAClB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAO,GAC3D,OAAO,CAAC,eAAe,CAAC,CA8S1B;AAgCD,iGAAiG;AACjG,eAAO,MAAM,eAAe,0EAA2E,CAAC;AAgCxG,4EAA4E;AAE5E,wBAAsB,cAAc,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAEtE"}
|