@flowselections/logistiek-engine 1.0.112 → 1.0.117
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/MisluktVerzendbrievenCard.d.ts.map +1 -1
- package/dist-lib/components/MisluktVerzendbrievenCard.js +3 -3
- package/dist-lib/hooks/useShippingNoteStatus.d.ts +3 -1
- package/dist-lib/hooks/useShippingNoteStatus.d.ts.map +1 -1
- package/dist-lib/hooks/useShippingNoteStatus.js +6 -3
- package/dist-lib/integrations/supabase/types.d.ts +530 -3
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/handlers/logistics-shipping-note-now.server.d.ts.map +1 -1
- package/dist-lib/lib/handlers/logistics-shipping-note-now.server.js +15 -4
- package/dist-lib/lib/handlers/logistics-tick.server.js +8 -0
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts +47 -0
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanning.server.js +181 -220
- package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
- package/dist-lib/lib/modulePrompts.js +140 -0
- package/dist-lib/lib/moduleVersion.d.ts +1 -1
- package/dist-lib/lib/moduleVersion.js +1 -1
- package/dist-lib/lib/shippingNoteQueue.server.d.ts.map +1 -1
- package/dist-lib/lib/shippingNoteQueue.server.js +51 -5
- package/dist-lib/lib/shippingNoteStates.d.ts +26 -0
- package/dist-lib/lib/shippingNoteStates.d.ts.map +1 -0
- package/dist-lib/lib/shippingNoteStates.js +38 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logistics-shipping-note-now.server.d.ts","sourceRoot":"","sources":["../../../src/lib/handlers/logistics-shipping-note-now.server.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAQ5D,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"logistics-shipping-note-now.server.d.ts","sourceRoot":"","sources":["../../../src/lib/handlers/logistics-shipping-note-now.server.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAQ5D,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC,CA2GnB"}
|
|
@@ -51,14 +51,25 @@ export async function handleShippingNoteNow(request, supabase) {
|
|
|
51
51
|
catch {
|
|
52
52
|
/* opschonen mag de ronde niet blokkeren */
|
|
53
53
|
}
|
|
54
|
-
// 2.
|
|
54
|
+
// 2. Alleen de aanvraag van díe pakbon wakker maken; nooit alles.
|
|
55
55
|
let wake = null;
|
|
56
|
+
let slipJobId = null;
|
|
56
57
|
try {
|
|
57
|
-
const
|
|
58
|
-
|
|
58
|
+
const body = (await request.clone().json().catch(() => ({})));
|
|
59
|
+
const v = typeof body["slipJobId"] === "string" ? body["slipJobId"].trim() : "";
|
|
60
|
+
slipJobId = /^[0-9a-f-]{36}$/i.test(v) ? v : null;
|
|
59
61
|
}
|
|
60
62
|
catch {
|
|
61
|
-
|
|
63
|
+
slipJobId = null;
|
|
64
|
+
}
|
|
65
|
+
if (slipJobId) {
|
|
66
|
+
try {
|
|
67
|
+
const { data } = await supabase.rpc("logistics_shipping_note_wake", { _slip_job_id: slipJobId });
|
|
68
|
+
wake = { gewekt: data ?? 0, slipJobId };
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
wake = null;
|
|
72
|
+
}
|
|
62
73
|
}
|
|
63
74
|
// 3. Gebundeld verwerken tot alles op is of het tijdbudget vol is; de knop
|
|
64
75
|
// hoeft niet te wachten tot de laatste ronde klaar is.
|
|
@@ -218,6 +218,14 @@ async function runTickRound(supabase, tickJob, ident) {
|
|
|
218
218
|
catch {
|
|
219
219
|
/* zelfherstel mag de ronde niet blokkeren */
|
|
220
220
|
}
|
|
221
|
+
// Vrijgeven (alleen database): wachtende aanvragen van orders met een
|
|
222
|
+
// goedgekeurde controlescan, zodra de poort het aanmaken toestaat.
|
|
223
|
+
try {
|
|
224
|
+
await supabase.rpc("logistics_shipping_note_release", { _order_number: null });
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
/* vrijgeven mag de ronde niet blokkeren */
|
|
228
|
+
}
|
|
221
229
|
const { runShippingNoteQueue } = await import("../shippingNoteQueue.server.js");
|
|
222
230
|
if (typeof runShippingNoteQueue !== "function") {
|
|
223
231
|
throw new Error("Deze versie van de Logistiek Engine bevat geen verzendbriefverwerking. Werk de module bij.");
|
|
@@ -30,7 +30,54 @@ export interface ExpandOptions {
|
|
|
30
30
|
packingByOrderLine?: Map<string, Map<string, Belading>>;
|
|
31
31
|
/** Wordt gevuld met de gebruikte bron per orderregel-sleutel. */
|
|
32
32
|
sources?: Map<string, BeladingSource>;
|
|
33
|
+
/** Wordt gevuld met de onderdelen van een set per orderregel (aantallen voor de hele regel). */
|
|
34
|
+
components?: Map<string, {
|
|
35
|
+
lineQty: number;
|
|
36
|
+
components: SetComponent[];
|
|
37
|
+
}>;
|
|
38
|
+
/** Wordt gevuld met meldingen per order (bijv. groepsregel naast ledenregels). */
|
|
39
|
+
notes?: Array<{
|
|
40
|
+
order_id: string | null;
|
|
41
|
+
order_item_id: string;
|
|
42
|
+
note: string;
|
|
43
|
+
}>;
|
|
33
44
|
}
|
|
45
|
+
/** Onderdeel van een set op het laagste niveau, uit de vastgelegde verdeling. */
|
|
46
|
+
export interface SetComponent {
|
|
47
|
+
trade_item_key: string | null;
|
|
48
|
+
product_id: string | null;
|
|
49
|
+
product_name: string | null;
|
|
50
|
+
article_number: string | null;
|
|
51
|
+
qty: number;
|
|
52
|
+
}
|
|
53
|
+
/** Eén regel uit logistics_planner_lines: dezelfde verdeling als reservering en afboeken. */
|
|
54
|
+
export interface PlannerLineRow {
|
|
55
|
+
order_item_id: string;
|
|
56
|
+
unit_kind: "artikel" | "set" | "groepslid" | "uitgesloten" | "onbekend" | "fout" | string;
|
|
57
|
+
product_id: string | null;
|
|
58
|
+
product_name: string | null;
|
|
59
|
+
qty: number;
|
|
60
|
+
trade_item_key: string | null;
|
|
61
|
+
group_id: string | null;
|
|
62
|
+
components: unknown;
|
|
63
|
+
frozen: boolean;
|
|
64
|
+
note: string | null;
|
|
65
|
+
}
|
|
66
|
+
/** Schaal de onderdelen van een setregel naar het aantal sets op één kar. */
|
|
67
|
+
export declare function scaleComponents(entry: {
|
|
68
|
+
lineQty: number;
|
|
69
|
+
components: SetComponent[];
|
|
70
|
+
} | undefined, plantsOnCart: number): SetComponent[] | null;
|
|
71
|
+
/**
|
|
72
|
+
* Zet orderregels om naar planner-invoer. De verdeling komt uitsluitend uit
|
|
73
|
+
* logistics_planner_lines (dezelfde bron als reservering en afboeken):
|
|
74
|
+
* - gewoon artikel: één invoerregel;
|
|
75
|
+
* - set: één pakket met de eigen belading van de set, plus de onderdelen voor de pickrij;
|
|
76
|
+
* - groep (alleen via het groepshoofd van een actieve groep): per lid de vastgelegde hoeveelheid;
|
|
77
|
+
* - groepsregel naast ledenregels van dezelfde groep: niet ingedeeld, wel gemeld;
|
|
78
|
+
* - fout in de samenstelling: indeling geblokkeerd met leesbare reden (fail closed).
|
|
79
|
+
* Een groep wordt nooit via de naam herkend.
|
|
80
|
+
*/
|
|
34
81
|
export declare function expandOrderItemsForPlanner(sb: SupabaseClient, rows: Array<Record<string, any>>, options?: ExpandOptions): Promise<PlannerInputItem[]>;
|
|
35
82
|
export declare function loadCarrierProfiles(sb: SupabaseClient): Promise<LoadCarrierTypeRow[]>;
|
|
36
83
|
export interface BeladingRules {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loadCarrierPlanning.server.d.ts","sourceRoot":"","sources":["../../src/lib/loadCarrierPlanning.server.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,eAAe,EAEpB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAA0B,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAA+C,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAarG,wBAAsB,6BAA6B,CAAC,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAwB/G;AAMD,UAAU,QAAQ;IAChB,gBAAgB,EAAE,MAAM,CAAC;IACzB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,wDAAwD;AACxD,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,eAAe,GACf,kBAAkB,GAClB,aAAa,GACb,SAAS,GACT,MAAM,CAAC;AAgBX;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAQ7E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,OAAO,GAAG,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAYrF;AAgED,MAAM,WAAW,aAAa;IAC5B,mFAAmF;IACnF,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,sFAAsF;IACtF,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IACxD,iEAAiE;IACjE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"loadCarrierPlanning.server.d.ts","sourceRoot":"","sources":["../../src/lib/loadCarrierPlanning.server.ts"],"names":[],"mappings":"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"}
|
|
@@ -140,30 +140,80 @@ function resolveBelading(row, packing, linePacking = null) {
|
|
|
140
140
|
// 6. Onbekend.
|
|
141
141
|
return { belading: { plants_per_crate: 1, crates_per_layer: 1, layers_per_cart: 1 }, source: "geen" };
|
|
142
142
|
}
|
|
143
|
+
/** Schaal de onderdelen van een setregel naar het aantal sets op één kar. */
|
|
144
|
+
export function scaleComponents(entry, plantsOnCart) {
|
|
145
|
+
if (!entry)
|
|
146
|
+
return null;
|
|
147
|
+
const list = Array.isArray(entry.components) ? entry.components : [];
|
|
148
|
+
if (entry.lineQty <= 0)
|
|
149
|
+
return list;
|
|
150
|
+
const ratio = plantsOnCart / entry.lineQty;
|
|
151
|
+
// Hele stuks, som exact (grootste-restmethode), gelijk aan de bevroren verdeling in de database.
|
|
152
|
+
const raw = list.map((c) => Number(c.qty) * ratio);
|
|
153
|
+
const floors = raw.map((r) => Math.floor(r + 1e-9));
|
|
154
|
+
const target = Math.round(raw.reduce((s, r) => s + r, 0));
|
|
155
|
+
let rest = target - floors.reduce((s, f) => s + f, 0);
|
|
156
|
+
const order = raw
|
|
157
|
+
.map((r, i) => ({ i, frac: r - floors[i], old: Number(list[i].qty), key: String(list[i].trade_item_key ?? "") }))
|
|
158
|
+
.sort((a, b) => b.frac - a.frac || b.old - a.old || a.key.localeCompare(b.key));
|
|
159
|
+
for (const o of order) {
|
|
160
|
+
if (rest <= 0)
|
|
161
|
+
break;
|
|
162
|
+
floors[o.i] += 1;
|
|
163
|
+
rest -= 1;
|
|
164
|
+
}
|
|
165
|
+
return list.map((c, i) => ({ ...c, qty: floors[i] }));
|
|
166
|
+
}
|
|
167
|
+
function toComponents(raw) {
|
|
168
|
+
const list = Array.isArray(raw) ? raw : [];
|
|
169
|
+
return list.map((c) => ({
|
|
170
|
+
trade_item_key: c?.trade_item_key != null ? String(c.trade_item_key) : null,
|
|
171
|
+
product_id: c?.product_id != null ? String(c.product_id) : null,
|
|
172
|
+
product_name: c?.product_name != null ? String(c.product_name) : null,
|
|
173
|
+
article_number: c?.article_number != null ? String(c.article_number) : null,
|
|
174
|
+
qty: Number(c?.qty ?? 0),
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Zet orderregels om naar planner-invoer. De verdeling komt uitsluitend uit
|
|
179
|
+
* logistics_planner_lines (dezelfde bron als reservering en afboeken):
|
|
180
|
+
* - gewoon artikel: één invoerregel;
|
|
181
|
+
* - set: één pakket met de eigen belading van de set, plus de onderdelen voor de pickrij;
|
|
182
|
+
* - groep (alleen via het groepshoofd van een actieve groep): per lid de vastgelegde hoeveelheid;
|
|
183
|
+
* - groepsregel naast ledenregels van dezelfde groep: niet ingedeeld, wel gemeld;
|
|
184
|
+
* - fout in de samenstelling: indeling geblokkeerd met leesbare reden (fail closed).
|
|
185
|
+
* Een groep wordt nooit via de naam herkend.
|
|
186
|
+
*/
|
|
143
187
|
export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
144
188
|
const out = [];
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (g?.name)
|
|
158
|
-
groupByName.set(String(g.name).trim().toLowerCase(), String(g.id));
|
|
189
|
+
const orderIds = Array.from(new Set(rows.map((r) => (r.order_id ? String(r.order_id) : null)).filter((x) => !!x)));
|
|
190
|
+
const linesByItem = new Map();
|
|
191
|
+
for (const oid of orderIds) {
|
|
192
|
+
const { data, error } = await sb.rpc("logistics_planner_lines", { p_order_id: oid });
|
|
193
|
+
if (error) {
|
|
194
|
+
throw new Error(`Indeling geblokkeerd: de verdeling van de order kon niet worden gelezen (${error.message}).`);
|
|
195
|
+
}
|
|
196
|
+
for (const l of Array.isArray(data) ? data : []) {
|
|
197
|
+
const key = String(l.order_item_id);
|
|
198
|
+
const list = linesByItem.get(key) ?? [];
|
|
199
|
+
list.push(l);
|
|
200
|
+
linesByItem.set(key, list);
|
|
159
201
|
}
|
|
160
202
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
203
|
+
const memberIds = Array.from(new Set(Array.from(linesByItem.values())
|
|
204
|
+
.flat()
|
|
205
|
+
.filter((l) => l.unit_kind === "groepslid" && l.product_id)
|
|
206
|
+
.map((l) => String(l.product_id))));
|
|
207
|
+
const prodById = new Map();
|
|
208
|
+
if (memberIds.length > 0) {
|
|
209
|
+
const { data: prods, error } = await sb
|
|
210
|
+
.from("products")
|
|
211
|
+
.select("id, product, plants_per_crate, crates_per_layer, layers_per_cart, weight, floriday_trade_item_id")
|
|
212
|
+
.in("id", memberIds);
|
|
213
|
+
if (error)
|
|
214
|
+
throw new Error(`Indeling geblokkeerd: groepsleden konden niet worden gelezen (${error.message}).`);
|
|
215
|
+
for (const p of Array.isArray(prods) ? prods : [])
|
|
216
|
+
prodById.set(String(p.id), p);
|
|
167
217
|
}
|
|
168
218
|
for (const row of rows) {
|
|
169
219
|
const orderKey = row.order_id ? String(row.order_id) : null;
|
|
@@ -176,43 +226,24 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
176
226
|
: null;
|
|
177
227
|
const { belading: base, source } = resolveBelading(row, packing, linePacking);
|
|
178
228
|
const plants = Math.max(0, Number(row.quantity ?? 0));
|
|
179
|
-
const
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
.
|
|
183
|
-
.toLowerCase();
|
|
184
|
-
const linkedToRealProduct = typeof row.product_id === "string" && realProductIds.has(row.product_id) && !groupSet.has(row.product_id);
|
|
185
|
-
const groupId = row.product_id && groupSet.has(row.product_id)
|
|
186
|
-
? String(row.product_id)
|
|
187
|
-
: !linkedToRealProduct || explicitGroupPrefix
|
|
188
|
-
? groupByName.get(groupNameKey) ?? null
|
|
189
|
-
: null;
|
|
190
|
-
const isGroup = groupId != null || (explicitGroupPrefix && !linkedToRealProduct);
|
|
191
|
-
if (!isGroup) {
|
|
192
|
-
options.sources?.set(String(row.id), source);
|
|
193
|
-
out.push({
|
|
194
|
-
order_item_id: row.id,
|
|
195
|
-
order_id: row.order_id ?? null,
|
|
196
|
-
customer_id: row.customer_id ?? null,
|
|
197
|
-
product_id: row.product_id ?? null,
|
|
198
|
-
product_name: row.product_name ?? "Onbekend",
|
|
199
|
-
plants,
|
|
200
|
-
...base,
|
|
201
|
-
packing_authoritative: isAuthoritative(source),
|
|
202
|
-
weight_per_crate: row.products?.weight != null ? Number(row.products.weight) : null,
|
|
203
|
-
});
|
|
229
|
+
const lines = linesByItem.get(String(row.id)) ?? [];
|
|
230
|
+
const kind = lines[0]?.unit_kind ?? "artikel";
|
|
231
|
+
if (kind === "uitgesloten") {
|
|
232
|
+
options.notes?.push({ order_id: orderKey, order_item_id: String(row.id), note: String(lines[0]?.note ?? "") });
|
|
204
233
|
continue;
|
|
205
234
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
if (memberList.length === 0) {
|
|
235
|
+
if (kind === "onbekend") {
|
|
236
|
+
// Regel zonder artikel: niet raden (ook niet via de naam), indeling blokkeert met reden.
|
|
237
|
+
throw new Error(`Indeling geblokkeerd: regel "${row.product_name ?? "onbekend"}" heeft geen artikel; groepen worden alleen via het groepshoofd herkend.`);
|
|
238
|
+
}
|
|
239
|
+
if (kind === "fout") {
|
|
240
|
+
throw new Error(`Indeling geblokkeerd voor ${row.product_name ?? "onbekend artikel"}: ${lines[0]?.note ?? "fout in de samenstelling"}.`);
|
|
241
|
+
}
|
|
242
|
+
if (kind !== "groepslid") {
|
|
215
243
|
options.sources?.set(String(row.id), source);
|
|
244
|
+
if (kind === "set") {
|
|
245
|
+
options.components?.set(String(row.id), { lineQty: plants, components: toComponents(lines[0]?.components) });
|
|
246
|
+
}
|
|
216
247
|
out.push({
|
|
217
248
|
order_item_id: row.id,
|
|
218
249
|
order_id: row.order_id ?? null,
|
|
@@ -222,90 +253,61 @@ export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
|
222
253
|
plants,
|
|
223
254
|
...base,
|
|
224
255
|
packing_authoritative: isAuthoritative(source),
|
|
225
|
-
weight_per_crate: null,
|
|
256
|
+
weight_per_crate: row.products?.weight != null ? Number(row.products.weight) : null,
|
|
226
257
|
});
|
|
227
258
|
continue;
|
|
228
259
|
}
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
prodByTrade.set(String(p.id), p);
|
|
241
|
-
}
|
|
242
|
-
const resolved = memberList.map((m) => {
|
|
243
|
-
const p = prodByTrade.get(String(m.trade_item_id));
|
|
244
|
-
// Onbekend groepslid: niet gokken en geen kale id als artikelnaam.
|
|
245
|
-
if (!p)
|
|
246
|
-
return { m, p, belading: { ...base }, source: "geen" };
|
|
247
|
-
// Een handmatig vastgezette regel blijft leidend voor de hele groep.
|
|
260
|
+
for (const l of lines) {
|
|
261
|
+
const qty = Math.max(0, Number(l.qty ?? 0));
|
|
262
|
+
if (qty <= 0)
|
|
263
|
+
continue;
|
|
264
|
+
const p = l.product_id ? prodById.get(String(l.product_id)) : null;
|
|
265
|
+
if (!p) {
|
|
266
|
+
throw new Error(`Indeling geblokkeerd voor ${row.product_name ?? "groep"}: groepslid ${l.trade_item_key ?? "onbekend"} heeft geen artikel.`);
|
|
267
|
+
}
|
|
268
|
+
let belading;
|
|
269
|
+
let src;
|
|
270
|
+
const eigenLijn = lineMap?.get(String(p.floriday_trade_item_id ?? "")) ?? lineMap?.get(String(p.id)) ?? null;
|
|
248
271
|
if (source === "handmatig") {
|
|
249
|
-
|
|
272
|
+
belading = { ...base };
|
|
273
|
+
src = source;
|
|
250
274
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
positive(p.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
m,
|
|
263
|
-
p,
|
|
264
|
-
belading: {
|
|
265
|
-
plants_per_crate: positive(p.plants_per_crate) ?? 1,
|
|
266
|
-
crates_per_layer: positive(p.crates_per_layer) ?? 1,
|
|
267
|
-
layers_per_cart: positive(p.layers_per_cart) ?? 1,
|
|
268
|
-
},
|
|
269
|
-
source: "product",
|
|
275
|
+
else if (eigenLijn) {
|
|
276
|
+
belading = { ...eigenLijn };
|
|
277
|
+
src = "floriday_line";
|
|
278
|
+
}
|
|
279
|
+
else if (positive(p.plants_per_crate) != null ||
|
|
280
|
+
positive(p.crates_per_layer) != null ||
|
|
281
|
+
positive(p.layers_per_cart) != null) {
|
|
282
|
+
belading = {
|
|
283
|
+
plants_per_crate: positive(p.plants_per_crate) ?? 1,
|
|
284
|
+
crates_per_layer: positive(p.crates_per_layer) ?? 1,
|
|
285
|
+
layers_per_cart: positive(p.layers_per_cart) ?? 1,
|
|
270
286
|
};
|
|
287
|
+
src = "product";
|
|
271
288
|
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const raw = (plants * Number(r.m.quantity ?? 1)) / weightSum;
|
|
283
|
-
const ppc = r.belading.plants_per_crate;
|
|
284
|
-
const crates = Math.max(0, Math.round(raw / ppc));
|
|
285
|
-
shares.push(crates * ppc);
|
|
286
|
-
assigned += crates * ppc;
|
|
287
|
-
}
|
|
288
|
-
if (assigned !== plants && resolved.length > 0) {
|
|
289
|
-
const firstPpc = resolved[0].belading.plants_per_crate;
|
|
290
|
-
const deltaCrates = Math.round((plants - assigned) / firstPpc);
|
|
291
|
-
shares[0] = Math.max(0, shares[0] + deltaCrates * firstPpc);
|
|
292
|
-
}
|
|
293
|
-
resolved.forEach((r, idx) => {
|
|
294
|
-
if (shares[idx] <= 0)
|
|
295
|
-
return;
|
|
296
|
-
options.sources?.set(`${row.id}::${r.m.trade_item_id}`, r.source);
|
|
289
|
+
else if (packing) {
|
|
290
|
+
belading = { ...packing };
|
|
291
|
+
src = "floriday_packing";
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
belading = { ...base };
|
|
295
|
+
src = "geen";
|
|
296
|
+
}
|
|
297
|
+
const itemKey = `${row.id}::${l.trade_item_key ?? p.id}`;
|
|
298
|
+
options.sources?.set(itemKey, src);
|
|
297
299
|
out.push({
|
|
298
|
-
order_item_id:
|
|
300
|
+
order_item_id: itemKey,
|
|
299
301
|
order_id: row.order_id ?? null,
|
|
300
302
|
customer_id: row.customer_id ?? null,
|
|
301
|
-
product_id:
|
|
302
|
-
product_name: `${row.product_name ?? "Groep"} — ${
|
|
303
|
-
plants:
|
|
304
|
-
...
|
|
305
|
-
packing_authoritative: isAuthoritative(
|
|
306
|
-
weight_per_crate:
|
|
303
|
+
product_id: String(p.id),
|
|
304
|
+
product_name: `${row.product_name ?? "Groep"} — ${p.product ?? "onbekend groepslid"}`,
|
|
305
|
+
plants: qty,
|
|
306
|
+
...belading,
|
|
307
|
+
packing_authoritative: isAuthoritative(src),
|
|
308
|
+
weight_per_crate: p.weight != null ? Number(p.weight) : null,
|
|
307
309
|
});
|
|
308
|
-
}
|
|
310
|
+
}
|
|
309
311
|
}
|
|
310
312
|
return out;
|
|
311
313
|
}
|
|
@@ -967,11 +969,31 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
967
969
|
packingByOrderLine.set(String(o.id), perRegel);
|
|
968
970
|
}
|
|
969
971
|
const sources = new Map();
|
|
972
|
+
const setComponents = new Map();
|
|
973
|
+
const expandNotes = [];
|
|
970
974
|
let inputs = await expandOrderItemsForPlanner(sb, rawRows, {
|
|
971
975
|
packingByOrder,
|
|
972
976
|
packingByOrderLine,
|
|
973
977
|
sources,
|
|
978
|
+
components: setComponents,
|
|
979
|
+
notes: expandNotes,
|
|
974
980
|
});
|
|
981
|
+
// Groepsregel naast ledenregels: niet ingedeeld, wel zichtbaar gemeld bij de order.
|
|
982
|
+
for (const n of expandNotes) {
|
|
983
|
+
if (!n.order_id)
|
|
984
|
+
continue;
|
|
985
|
+
const { error: noteError } = await sb.rpc("logistics_trace_order_step", {
|
|
986
|
+
p_order_id: n.order_id,
|
|
987
|
+
p_node: "stap:beladingindeling",
|
|
988
|
+
p_step: "groepsregel niet ingedeeld",
|
|
989
|
+
p_status: "waarschuwing",
|
|
990
|
+
p_decision: "groepsregel niet meegeteld",
|
|
991
|
+
p_reason: n.note,
|
|
992
|
+
p_next_step: "Karrenindeling",
|
|
993
|
+
});
|
|
994
|
+
if (noteError)
|
|
995
|
+
console.warn(`[indeling] melding bij order ${n.order_id} mislukt: ${noteError.message}`);
|
|
996
|
+
}
|
|
975
997
|
// Wat al op een bevroren kar ligt, wordt niet opnieuw ingedeeld.
|
|
976
998
|
let frozenCartCount = 0;
|
|
977
999
|
let cartNumberStart = 1;
|
|
@@ -1192,19 +1214,8 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
1192
1214
|
});
|
|
1193
1215
|
if (blockError)
|
|
1194
1216
|
warnings.push(`Blokkade kon niet worden vastgelegd: ${blockError.message}`);
|
|
1195
|
-
//
|
|
1196
|
-
//
|
|
1197
|
-
// afmetingen ontbreken. Karren van de afmetingenmethode blijven staan.
|
|
1198
|
-
// Bij aanvullen blijven bestaande (bevroren) karren altijd staan.
|
|
1199
|
-
if (!isSupplement) {
|
|
1200
|
-
const { error: staleError } = await sb
|
|
1201
|
-
.from("order_carts")
|
|
1202
|
-
.delete()
|
|
1203
|
-
.eq("buffer_id", bufferId)
|
|
1204
|
-
.or("layout_method.is.null,layout_method.neq.afmetingen");
|
|
1205
|
-
if (staleError)
|
|
1206
|
-
warnings.push(`Oude karrenindeling kon niet worden opgeruimd: ${staleError.message}`);
|
|
1207
|
-
}
|
|
1217
|
+
// A3: bij een mislukte indeling blijven bestaande karren staan; de blokkade
|
|
1218
|
+
// hierboven meldt dat de indeling verouderd is en waarom.
|
|
1208
1219
|
return {
|
|
1209
1220
|
ok: false,
|
|
1210
1221
|
bufferId,
|
|
@@ -1398,22 +1409,10 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
1398
1409
|
beladingSources,
|
|
1399
1410
|
};
|
|
1400
1411
|
}
|
|
1401
|
-
//
|
|
1402
|
-
//
|
|
1403
|
-
//
|
|
1404
|
-
// blijven altijd staan.
|
|
1412
|
+
// Bevroren karren (geprint, gescand, gecontroleerd) blijven altijd staan.
|
|
1413
|
+
// A3: bestaande karren worden nooit weggehaald als de nieuwe indeling mislukt;
|
|
1414
|
+
// vervangen gebeurt alleen in één transactie (logistics_apply_cart_swap).
|
|
1405
1415
|
const keepFilter = keepCartIds.length > 0 ? `(${keepCartIds.join(",")})` : null;
|
|
1406
|
-
const clearExistingCarts = async () => {
|
|
1407
|
-
const ids = orderIds.filter(Boolean);
|
|
1408
|
-
const del = () => {
|
|
1409
|
-
const q = sb.from("order_carts").delete();
|
|
1410
|
-
return keepFilter ? q.not("id", "in", keepFilter) : q;
|
|
1411
|
-
};
|
|
1412
|
-
await del().eq("buffer_id", bufferId);
|
|
1413
|
-
if (ids.length > 0) {
|
|
1414
|
-
await del().in("order_id", ids);
|
|
1415
|
-
}
|
|
1416
|
-
};
|
|
1417
1416
|
// ---------------------------------------------------------------------------
|
|
1418
1417
|
// Vergelijken in plaats van wissen. Een kar die exact hetzelfde blijft, blijft
|
|
1419
1418
|
// staan: zelfde rij, zelfde id, zelfde barcode. Alleen echte verschillen worden
|
|
@@ -1476,6 +1475,10 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
1476
1475
|
};
|
|
1477
1476
|
const items = cart.items.map((i) => ({
|
|
1478
1477
|
order_item_id: i.order_item_id.includes("::") ? i.order_item_id.split("::")[0] : i.order_item_id,
|
|
1478
|
+
// Set: onderdelen op laagste niveau, geschaald naar het aantal sets op deze kar.
|
|
1479
|
+
components: i.order_item_id.includes("::")
|
|
1480
|
+
? null
|
|
1481
|
+
: scaleComponents(setComponents.get(i.order_item_id), Math.round(i.crates) * i.plants_per_crate),
|
|
1479
1482
|
product_id: i.product_id,
|
|
1480
1483
|
product_name: i.product_name,
|
|
1481
1484
|
crates: Math.round(i.crates),
|
|
@@ -1547,72 +1550,34 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
1547
1550
|
const teVerwijderen = [];
|
|
1548
1551
|
for (const lijst of beschikbaarPerSig.values())
|
|
1549
1552
|
teVerwijderen.push(...lijst);
|
|
1550
|
-
if (teVerwijderen.length > 0) {
|
|
1551
|
-
for (let s = 0; s < teVerwijderen.length; s += 200) {
|
|
1552
|
-
const slice = teVerwijderen.slice(s, s + 200);
|
|
1553
|
-
// Alleen voor karren die echt verdwijnen: openstaande printopdrachten en barcodes vervallen.
|
|
1554
|
-
await sb
|
|
1555
|
-
.from("print_jobs")
|
|
1556
|
-
.update({ status: "geannuleerd", foutmelding: "Vervallen: kar verwijderd bij herindeling", updated_at: new Date().toISOString() })
|
|
1557
|
-
.in("status", ["wachtend", "bezig"])
|
|
1558
|
-
.in("payload->>cart_id", slice);
|
|
1559
|
-
await sb.from("pakbon_barcode_inhoud").delete().in("cart_id", slice);
|
|
1560
|
-
await sb.from("order_cart_layer_positions").delete().in("cart_id", slice);
|
|
1561
|
-
const { error: delError } = await sb.from("order_carts").delete().in("id", slice);
|
|
1562
|
-
if (delError) {
|
|
1563
|
-
throw new Error(`Verouderde karren konden niet worden opgeruimd: ${delError.message}`);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
1553
|
let created = behouden.size;
|
|
1568
1554
|
const idByPlanIndex = new Map(behouden);
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
const { data: insertedCarts, error } = await sb
|
|
1579
|
-
.from("order_carts")
|
|
1580
|
-
.insert(cartRows)
|
|
1581
|
-
.select("id, order_id, cart_number");
|
|
1582
|
-
if (error || !Array.isArray(insertedCarts)) {
|
|
1583
|
-
const message = `Ladingdragers konden niet worden opgeslagen: ${error?.message ?? "onbekende fout"}`;
|
|
1555
|
+
// A3: verouderde karren weg en nieuwe karren erbij in één transactie. Mislukt dit,
|
|
1556
|
+
// dan blijven alle bestaande karren onveranderd staan.
|
|
1557
|
+
if (teVerwijderen.length > 0 || nieuw.length > 0) {
|
|
1558
|
+
const { data: swap, error: swapError } = await sb.rpc("logistics_apply_cart_swap", {
|
|
1559
|
+
p_delete_ids: teVerwijderen,
|
|
1560
|
+
p_new_carts: nieuw.map((g) => ({ planIndex: g.planIndex, row: g.row, items: g.items })),
|
|
1561
|
+
});
|
|
1562
|
+
if (swapError) {
|
|
1563
|
+
const message = `Nieuwe karindeling kon niet worden opgeslagen; de bestaande karren blijven staan: ${swapError.message}`;
|
|
1584
1564
|
console.error(`[logistiek-engine] buffer ${bufferId}: ${message}`);
|
|
1585
|
-
await clearExistingCarts();
|
|
1586
1565
|
throw new Error(message);
|
|
1587
1566
|
}
|
|
1588
|
-
|
|
1589
|
-
const
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
for (const g of chunk) {
|
|
1593
|
-
const cartId = idByIdentity.get(identityKey(g.row["order_id"], g.row["cart_number"]));
|
|
1594
|
-
if (!cartId)
|
|
1595
|
-
continue;
|
|
1596
|
-
idByPlanIndex.set(g.planIndex, cartId);
|
|
1597
|
-
for (const i of g.items)
|
|
1598
|
-
itemRows.push({ ...i, cart_id: cartId });
|
|
1599
|
-
}
|
|
1600
|
-
for (let s = 0; s < itemRows.length; s += 500) {
|
|
1601
|
-
const slice = itemRows.slice(s, s + 500);
|
|
1602
|
-
if (slice.length === 0)
|
|
1603
|
-
continue;
|
|
1604
|
-
const { error: itemError } = await sb.from("order_cart_items").insert(slice);
|
|
1605
|
-
if (itemError) {
|
|
1606
|
-
// Ladingdragers zonder inhoud zijn een ongeldige uitkomst: alles terugdraaien
|
|
1607
|
-
// en de planrun laten falen, zodat de wachtrij het opnieuw of geblokkeerd oppakt.
|
|
1608
|
-
const message = `Inhoud van de ladingdragers kon niet worden opgeslagen: ${itemError.message}` +
|
|
1609
|
-
(itemError.details ? ` — ${itemError.details}` : "") +
|
|
1610
|
-
(itemError.hint ? ` (${itemError.hint})` : "");
|
|
1611
|
-
console.error(`[logistiek-engine] buffer ${bufferId}: ${message}`);
|
|
1612
|
-
await clearExistingCarts();
|
|
1613
|
-
throw new Error(message);
|
|
1567
|
+
const mapping = Array.isArray(swap?.karren) ? swap.karren : [];
|
|
1568
|
+
for (const m of mapping) {
|
|
1569
|
+
if (m && m.id != null && Number.isFinite(Number(m.planIndex))) {
|
|
1570
|
+
idByPlanIndex.set(Number(m.planIndex), String(m.id));
|
|
1614
1571
|
}
|
|
1615
1572
|
}
|
|
1573
|
+
created += mapping.length;
|
|
1574
|
+
}
|
|
1575
|
+
if (nieuw.length === 0) {
|
|
1576
|
+
console.log(`[logistiek-engine] buffer ${bufferId}: indeling ongewijzigd, ${behouden.size} kar(ren) behouden, ` +
|
|
1577
|
+
`${teVerwijderen.length} verouderde kar(ren) verwijderd.`);
|
|
1578
|
+
}
|
|
1579
|
+
{
|
|
1580
|
+
const chunk = nieuw;
|
|
1616
1581
|
// Fysieke laagindeling per kar. Alleen de afmetingenmethode levert posities;
|
|
1617
1582
|
// bij belading blijft de indeling leeg ("nog geen laagindeling aangeleverd").
|
|
1618
1583
|
if (dimensionPlan) {
|
|
@@ -1633,9 +1598,7 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
1633
1598
|
.from("order_cart_layer_positions")
|
|
1634
1599
|
.upsert(slice, { onConflict: "cart_id,layer,keg_id" });
|
|
1635
1600
|
if (posError) {
|
|
1636
|
-
//
|
|
1637
|
-
// en de wachtrij laten falen, zodat niets als geldig wordt getoond.
|
|
1638
|
-
await clearExistingCarts();
|
|
1601
|
+
// A3: karren blijven staan; de planrun faalt met een duidelijke reden.
|
|
1639
1602
|
throw new Error(`De fysieke laagindeling van kar ${cart.cart_number} kon niet worden opgeslagen: ${posError.message}`);
|
|
1640
1603
|
}
|
|
1641
1604
|
}
|
|
@@ -1645,7 +1608,6 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
1645
1608
|
.select("id", { count: "exact", head: true })
|
|
1646
1609
|
.eq("cart_id", cartId);
|
|
1647
1610
|
if (countError || storedPositions !== expectedPositions) {
|
|
1648
|
-
await clearExistingCarts();
|
|
1649
1611
|
throw new Error(`De fysieke indeling van kar ${cart.cart_number} is niet volledig opgeslagen: ` +
|
|
1650
1612
|
`${expectedPositions} fusten verwacht, ${storedPositions ?? 0} opgeslagen.`);
|
|
1651
1613
|
}
|
|
@@ -1674,9 +1636,8 @@ async function replanBufferUnlocked(sb, bufferId, options = {}) {
|
|
|
1674
1636
|
const reason = verifyError
|
|
1675
1637
|
? `Karrenindeling kon niet worden gecontroleerd: ${verifyError.message}`
|
|
1676
1638
|
: `Karrenindeling is niet volgens de ingestelde werkwijze "${expectedMethod}" vastgelegd ` +
|
|
1677
|
-
`(${wrong.length} van ${rows.length} karren).
|
|
1639
|
+
`(${wrong.length} van ${rows.length} karren). De karren blijven staan; de order is geblokkeerd.`;
|
|
1678
1640
|
console.error(`[logistiek-engine] buffer ${bufferId}: ${reason}`);
|
|
1679
|
-
await clearExistingCarts();
|
|
1680
1641
|
await sb.rpc("logistics_plan_block", {
|
|
1681
1642
|
p_order_ids: orderIds,
|
|
1682
1643
|
p_reason: reason,
|