@flowselections/logistiek-engine 1.0.14 → 1.0.16
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/integrations/supabase/auth-middleware.d.ts +53 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +53 -0
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/moduleClient.d.ts +53 -0
- package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +53 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/bulkLoadCarrierPlanning.server.d.ts +2 -0
- package/dist-lib/lib/bulkLoadCarrierPlanning.server.d.ts.map +1 -1
- package/dist-lib/lib/bulkLoadCarrierPlanning.server.js +11 -0
- package/dist-lib/lib/floridayDocuments.functions.d.ts +159 -0
- package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -1
- package/dist-lib/lib/floridayPush.functions.d.ts +53 -0
- package/dist-lib/lib/floridayPush.functions.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanner.d.ts +1 -1
- package/dist-lib/lib/loadCarrierPlanner.js +1 -1
- package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +265 -0
- package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts +33 -2
- package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
- package/dist-lib/lib/loadCarrierPlanning.server.js +245 -47
- package/dist-lib/lib/loadCarrierTaskSync.server.js +1 -1
- package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +106 -0
- package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -1
- package/dist-lib/lib/logisticsRecalculate.functions.d.ts +53 -0
- package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -1
- package/dist-lib/lib/planQueue.server.d.ts +2 -0
- package/dist-lib/lib/planQueue.server.d.ts.map +1 -1
- package/dist-lib/lib/planQueue.server.js +22 -4
- package/package.json +1 -1
|
@@ -12,7 +12,27 @@ export declare function buildBufferKey(order: {
|
|
|
12
12
|
location: string;
|
|
13
13
|
};
|
|
14
14
|
export declare function resolveBufferForOrderInternal(sb: SupabaseClient, orderId: string): Promise<string | null>;
|
|
15
|
-
|
|
15
|
+
interface Belading {
|
|
16
|
+
plants_per_crate: number;
|
|
17
|
+
crates_per_layer: number;
|
|
18
|
+
layers_per_cart: number;
|
|
19
|
+
}
|
|
20
|
+
/** Waar de belading van een orderregel vandaan komt. */
|
|
21
|
+
export type BeladingSource = "floriday_packing" | "order_items" | "product" | "geen";
|
|
22
|
+
/**
|
|
23
|
+
* Zet een Floriday-verpakking om naar beladingwaarden.
|
|
24
|
+
* pieces_per_package -> planten per fust
|
|
25
|
+
* packages_per_layer -> fusten per laag
|
|
26
|
+
* layers_per_load_carrier -> lagen per ladingdrager
|
|
27
|
+
*/
|
|
28
|
+
export declare function beladingFromFloridayPacking(packing: unknown): Belading | null;
|
|
29
|
+
export interface ExpandOptions {
|
|
30
|
+
/** Beladingwaarden per order uit custom_fields.floriday_packing. */
|
|
31
|
+
packingByOrder?: Map<string, Belading>;
|
|
32
|
+
/** Wordt gevuld met de gebruikte bron per orderregel-sleutel. */
|
|
33
|
+
sources?: Map<string, BeladingSource>;
|
|
34
|
+
}
|
|
35
|
+
export declare function expandOrderItemsForPlanner(sb: SupabaseClient, rows: Array<Record<string, any>>, options?: ExpandOptions): Promise<PlannerInputItem[]>;
|
|
16
36
|
export declare function loadCarrierProfiles(sb: SupabaseClient): Promise<LoadCarrierTypeRow[]>;
|
|
17
37
|
export interface BeladingRules {
|
|
18
38
|
maxOrders: number | null;
|
|
@@ -25,6 +45,12 @@ export interface BeladingRules {
|
|
|
25
45
|
export declare function loadBeladingRules(sb: SupabaseClient): Promise<BeladingRules | null>;
|
|
26
46
|
/** Past de grenzen uit de werkwijze toe op het capaciteitsprofiel van een ladingdrager. */
|
|
27
47
|
export declare function applyRulesToProfile(profile: CarrierProfile, rules: BeladingRules | null): CarrierProfile;
|
|
48
|
+
/**
|
|
49
|
+
* Verdeelt de planten over ladingdragers volgens de Floriday-verhouding:
|
|
50
|
+
* planten per kar = pieces_per_package x packages_per_layer x layers_per_load_carrier.
|
|
51
|
+
* Geen optimalisatie, geen mengregels, geen laagherverdeling — alleen vullen.
|
|
52
|
+
*/
|
|
53
|
+
export declare function planByFloridayPacking(inputs: PlannerInputItem[], profile: CarrierProfile, packing: Belading): PlannedLoadUnit[];
|
|
28
54
|
export interface ReplanResult {
|
|
29
55
|
ok: boolean;
|
|
30
56
|
bufferId: string;
|
|
@@ -33,7 +59,11 @@ export interface ReplanResult {
|
|
|
33
59
|
replacedManualCount: number;
|
|
34
60
|
usedFloridayPacking: boolean;
|
|
35
61
|
warnings: string[];
|
|
36
|
-
skipped?: "locked";
|
|
62
|
+
skipped?: "locked" | "blocked";
|
|
63
|
+
/** Leesbare reden waarom er niets is ingedeeld. */
|
|
64
|
+
blockedReason?: string;
|
|
65
|
+
/** Gebruikte beladingbronnen in deze buffer, met aantal orderregels per bron. */
|
|
66
|
+
beladingSources?: Record<string, number>;
|
|
37
67
|
carts?: PlannedLoadUnit[];
|
|
38
68
|
}
|
|
39
69
|
export interface ReplanOptions {
|
|
@@ -44,4 +74,5 @@ export interface ReplanOptions {
|
|
|
44
74
|
}
|
|
45
75
|
export declare function replanBufferInternal(sb: SupabaseClient, bufferId: string, options?: ReplanOptions): Promise<ReplanResult>;
|
|
46
76
|
export declare function replanOrderInternal(sb: SupabaseClient, orderId: string, options?: ReplanOptions): Promise<ReplanResult | null>;
|
|
77
|
+
export {};
|
|
47
78
|
//# sourceMappingURL=loadCarrierPlanning.server.d.ts.map
|
|
@@ -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,
|
|
1
|
+
{"version":3,"file":"loadCarrierPlanning.server.d.ts","sourceRoot":"","sources":["../../src/lib/loadCarrierPlanning.server.ts"],"names":[],"mappings":"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"}
|
|
@@ -128,10 +128,53 @@ export async function resolveBufferForOrderInternal(sb, orderId) {
|
|
|
128
128
|
}
|
|
129
129
|
return bufferId;
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
const positive = (v) => {
|
|
132
|
+
const n = Number(v);
|
|
133
|
+
return Number.isFinite(n) && n > 0 ? Math.round(n) : null;
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Zet een Floriday-verpakking om naar beladingwaarden.
|
|
137
|
+
* pieces_per_package -> planten per fust
|
|
138
|
+
* packages_per_layer -> fusten per laag
|
|
139
|
+
* layers_per_load_carrier -> lagen per ladingdrager
|
|
140
|
+
*/
|
|
141
|
+
export function beladingFromFloridayPacking(packing) {
|
|
142
|
+
if (!packing || typeof packing !== "object")
|
|
143
|
+
return null;
|
|
144
|
+
const p = packing;
|
|
145
|
+
const ppc = positive(p.pieces_per_package);
|
|
146
|
+
const cpl = positive(p.packages_per_layer);
|
|
147
|
+
const lpc = positive(p.layers_per_load_carrier);
|
|
148
|
+
if (ppc == null || cpl == null || lpc == null)
|
|
149
|
+
return null;
|
|
150
|
+
return { plants_per_crate: ppc, crates_per_layer: cpl, layers_per_cart: lpc };
|
|
151
|
+
}
|
|
152
|
+
/** Beladingbronnen in volgorde: Floriday-verpakking > orderregel > product > geen. */
|
|
153
|
+
function resolveBelading(row, packing) {
|
|
154
|
+
if (packing)
|
|
155
|
+
return { belading: packing, source: "floriday_packing" };
|
|
156
|
+
const fromRow = positive(row.plants_per_crate) != null || positive(row.crates_per_layer) != null || positive(row.layers_per_cart) != null
|
|
157
|
+
? {
|
|
158
|
+
plants_per_crate: positive(row.plants_per_crate) ?? 1,
|
|
159
|
+
crates_per_layer: positive(row.crates_per_layer) ?? 1,
|
|
160
|
+
layers_per_cart: positive(row.layers_per_cart) ?? 1,
|
|
161
|
+
}
|
|
162
|
+
: null;
|
|
163
|
+
if (fromRow)
|
|
164
|
+
return { belading: fromRow, source: "order_items" };
|
|
165
|
+
const p = row.products ?? null;
|
|
166
|
+
const fromProduct = p && (positive(p.plants_per_crate) != null || positive(p.crates_per_layer) != null || positive(p.layers_per_cart) != null)
|
|
167
|
+
? {
|
|
168
|
+
plants_per_crate: positive(p.plants_per_crate) ?? 1,
|
|
169
|
+
crates_per_layer: positive(p.crates_per_layer) ?? 1,
|
|
170
|
+
layers_per_cart: positive(p.layers_per_cart) ?? 1,
|
|
171
|
+
}
|
|
172
|
+
: null;
|
|
173
|
+
if (fromProduct)
|
|
174
|
+
return { belading: fromProduct, source: "product" };
|
|
175
|
+
return { belading: { plants_per_crate: 1, crates_per_layer: 1, layers_per_cart: 1 }, source: "geen" };
|
|
133
176
|
}
|
|
134
|
-
export async function expandOrderItemsForPlanner(sb, rows) {
|
|
177
|
+
export async function expandOrderItemsForPlanner(sb, rows, options = {}) {
|
|
135
178
|
const out = [];
|
|
136
179
|
const groupIds = rows
|
|
137
180
|
.map((r) => r.product_id)
|
|
@@ -142,15 +185,13 @@ export async function expandOrderItemsForPlanner(sb, rows) {
|
|
|
142
185
|
groupSet = new Set((groups ?? []).map((g) => g.id));
|
|
143
186
|
}
|
|
144
187
|
for (const row of rows) {
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
crates_per_layer: Math.max(1, Number(row.crates_per_layer ?? row.products?.crates_per_layer ?? 1)),
|
|
148
|
-
layers_per_cart: Math.max(1, Number(row.layers_per_cart ?? row.products?.layers_per_cart ?? 1)),
|
|
149
|
-
};
|
|
188
|
+
const packing = row.order_id ? options.packingByOrder?.get(String(row.order_id)) ?? null : null;
|
|
189
|
+
const { belading: base, source } = resolveBelading(row, packing);
|
|
150
190
|
const plants = Math.max(0, Number(row.quantity ?? 0));
|
|
151
191
|
const isGroup = (row.product_id && groupSet.has(row.product_id)) ||
|
|
152
192
|
String(row.product_name ?? "").startsWith("[Groep]");
|
|
153
193
|
if (!isGroup) {
|
|
194
|
+
options.sources?.set(String(row.id), source);
|
|
154
195
|
out.push({
|
|
155
196
|
order_item_id: row.id,
|
|
156
197
|
order_id: row.order_id ?? null,
|
|
@@ -170,6 +211,7 @@ export async function expandOrderItemsForPlanner(sb, rows) {
|
|
|
170
211
|
.order("sort_order", { ascending: true });
|
|
171
212
|
const memberList = Array.isArray(members) ? members : [];
|
|
172
213
|
if (memberList.length === 0) {
|
|
214
|
+
options.sources?.set(String(row.id), source);
|
|
173
215
|
out.push({
|
|
174
216
|
order_item_id: row.id,
|
|
175
217
|
order_id: row.order_id ?? null,
|
|
@@ -195,22 +237,22 @@ export async function expandOrderItemsForPlanner(sb, rows) {
|
|
|
195
237
|
}
|
|
196
238
|
const resolved = memberList.map((m) => {
|
|
197
239
|
const p = prodByTrade.get(String(m.trade_item_id));
|
|
198
|
-
|
|
240
|
+
// Een Floriday-verpakking geldt voor de hele orderregel, dus ook voor de leden.
|
|
241
|
+
if (packing)
|
|
242
|
+
return { m, p, belading: { ...base }, source: "floriday_packing" };
|
|
243
|
+
const known = p &&
|
|
244
|
+
(positive(p.plants_per_crate) != null ||
|
|
245
|
+
positive(p.crates_per_layer) != null ||
|
|
246
|
+
positive(p.layers_per_cart) != null);
|
|
247
|
+
const belading = known
|
|
199
248
|
? {
|
|
200
|
-
plants_per_crate:
|
|
201
|
-
crates_per_layer:
|
|
202
|
-
layers_per_cart:
|
|
249
|
+
plants_per_crate: positive(p.plants_per_crate) ?? 1,
|
|
250
|
+
crates_per_layer: positive(p.crates_per_layer) ?? 1,
|
|
251
|
+
layers_per_cart: positive(p.layers_per_cart) ?? 1,
|
|
203
252
|
}
|
|
204
|
-
: {
|
|
205
|
-
return { m, p, belading };
|
|
253
|
+
: { ...base };
|
|
254
|
+
return { m, p, belading, source: (known ? "product" : source) };
|
|
206
255
|
});
|
|
207
|
-
// Fallback: sibling met bekende belading, anders de groepsregel zelf.
|
|
208
|
-
const known = resolved.find((r) => !isDefaultBelading(r.belading))?.belading;
|
|
209
|
-
for (const r of resolved) {
|
|
210
|
-
if (isDefaultBelading(r.belading)) {
|
|
211
|
-
r.belading = known ?? (isDefaultBelading(base) ? r.belading : base);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
256
|
const weightSum = resolved.reduce((s, r) => s + Number(r.m.quantity ?? 1), 0) || resolved.length;
|
|
215
257
|
const shares = [];
|
|
216
258
|
let assigned = 0;
|
|
@@ -229,6 +271,7 @@ export async function expandOrderItemsForPlanner(sb, rows) {
|
|
|
229
271
|
resolved.forEach((r, idx) => {
|
|
230
272
|
if (shares[idx] <= 0)
|
|
231
273
|
return;
|
|
274
|
+
options.sources?.set(`${row.id}::${r.m.trade_item_id}`, r.source);
|
|
232
275
|
out.push({
|
|
233
276
|
order_item_id: `${row.id}::${r.m.trade_item_id}`,
|
|
234
277
|
order_id: row.order_id ?? null,
|
|
@@ -299,6 +342,75 @@ export function applyRulesToProfile(profile, rules) {
|
|
|
299
342
|
};
|
|
300
343
|
}
|
|
301
344
|
// ---------------------------------------------------------------------------
|
|
345
|
+
// Overname-pad: de beladingverhouding van Floriday letterlijk volgen
|
|
346
|
+
// ---------------------------------------------------------------------------
|
|
347
|
+
/**
|
|
348
|
+
* Verdeelt de planten over ladingdragers volgens de Floriday-verhouding:
|
|
349
|
+
* planten per kar = pieces_per_package x packages_per_layer x layers_per_load_carrier.
|
|
350
|
+
* Geen optimalisatie, geen mengregels, geen laagherverdeling — alleen vullen.
|
|
351
|
+
*/
|
|
352
|
+
export function planByFloridayPacking(inputs, profile, packing) {
|
|
353
|
+
const ppc = Math.max(1, packing.plants_per_crate);
|
|
354
|
+
const cpl = Math.max(1, packing.crates_per_layer);
|
|
355
|
+
const lpc = Math.max(1, packing.layers_per_cart);
|
|
356
|
+
const perCart = ppc * cpl * lpc;
|
|
357
|
+
const carts = [];
|
|
358
|
+
let current = null;
|
|
359
|
+
let currentPlants = 0;
|
|
360
|
+
const openCart = () => {
|
|
361
|
+
current = {
|
|
362
|
+
cart_number: carts.length + 1,
|
|
363
|
+
carrier_type: profile.code,
|
|
364
|
+
carrier_type_id: profile.id ?? null,
|
|
365
|
+
source: "auto",
|
|
366
|
+
is_complete: false,
|
|
367
|
+
occupancy_pct: 0,
|
|
368
|
+
total_layers_used: 0,
|
|
369
|
+
max_layers: lpc,
|
|
370
|
+
weight_kg: null,
|
|
371
|
+
items: [],
|
|
372
|
+
};
|
|
373
|
+
currentPlants = 0;
|
|
374
|
+
carts.push(current);
|
|
375
|
+
};
|
|
376
|
+
for (const input of inputs) {
|
|
377
|
+
let left = Math.max(0, Math.round(input.plants));
|
|
378
|
+
while (left > 0) {
|
|
379
|
+
if (!current || currentPlants >= perCart)
|
|
380
|
+
openCart();
|
|
381
|
+
const cart = current;
|
|
382
|
+
const take = Math.min(left, perCart - currentPlants);
|
|
383
|
+
const crates = Math.max(1, Math.ceil(take / ppc));
|
|
384
|
+
const layerFraction = crates / cpl;
|
|
385
|
+
const item = {
|
|
386
|
+
order_item_id: input.order_item_id,
|
|
387
|
+
order_id: input.order_id ?? null,
|
|
388
|
+
product_id: input.product_id,
|
|
389
|
+
product_name: input.product_name,
|
|
390
|
+
crates,
|
|
391
|
+
plants: take,
|
|
392
|
+
boxes: crates,
|
|
393
|
+
weight_kg: input.weight_per_crate != null ? Number((input.weight_per_crate * crates).toFixed(2)) : null,
|
|
394
|
+
plants_per_crate: ppc,
|
|
395
|
+
crates_per_layer: cpl,
|
|
396
|
+
layers_per_cart: lpc,
|
|
397
|
+
layer_fraction: Number(layerFraction.toFixed(3)),
|
|
398
|
+
layer_share_pct: Number(((100 * layerFraction) / lpc).toFixed(2)),
|
|
399
|
+
};
|
|
400
|
+
cart.items.push(item);
|
|
401
|
+
currentPlants += take;
|
|
402
|
+
left -= take;
|
|
403
|
+
cart.total_layers_used = Number(cart.items.reduce((s, i) => s + i.layer_fraction, 0).toFixed(2));
|
|
404
|
+
cart.occupancy_pct = Number(((100 * currentPlants) / perCart).toFixed(1));
|
|
405
|
+
cart.is_complete = currentPlants >= perCart;
|
|
406
|
+
cart.weight_kg = cart.items.some((i) => i.weight_kg != null)
|
|
407
|
+
? Number(cart.items.reduce((s, i) => s + (i.weight_kg ?? 0), 0).toFixed(2))
|
|
408
|
+
: null;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return carts;
|
|
412
|
+
}
|
|
413
|
+
// ---------------------------------------------------------------------------
|
|
302
414
|
// Herplannen van een buffer
|
|
303
415
|
// ---------------------------------------------------------------------------
|
|
304
416
|
/** Maximaal aantal ladingdragers dat één orderbuffer mag opleveren. */
|
|
@@ -335,11 +447,23 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
335
447
|
...it,
|
|
336
448
|
customer_id: customerByOrder.get(it.order_id) ?? null,
|
|
337
449
|
}));
|
|
338
|
-
|
|
450
|
+
// Floriday-verpakking per order is de eerste beladingbron.
|
|
451
|
+
const packingByOrder = new Map();
|
|
452
|
+
for (const o of orderRows) {
|
|
453
|
+
const belading = beladingFromFloridayPacking(o.custom_fields?.floriday_packing);
|
|
454
|
+
if (belading)
|
|
455
|
+
packingByOrder.set(String(o.id), belading);
|
|
456
|
+
}
|
|
457
|
+
const sources = new Map();
|
|
458
|
+
const inputs = await expandOrderItemsForPlanner(sb, rawRows, { packingByOrder, sources });
|
|
339
459
|
const itemToOrder = new Map();
|
|
340
460
|
for (const i of inputs)
|
|
341
461
|
if (i.order_id)
|
|
342
462
|
itemToOrder.set(i.order_item_id, i.order_id);
|
|
463
|
+
const beladingSources = {};
|
|
464
|
+
for (const s of sources.values())
|
|
465
|
+
beladingSources[s] = (beladingSources[s] ?? 0) + 1;
|
|
466
|
+
console.log(`[logistiek-engine] buffer ${bufferId}: beladingbronnen ${JSON.stringify(beladingSources)}`);
|
|
343
467
|
// Bestaande beladingen tellen en wissen.
|
|
344
468
|
const { data: existingCarts } = await sb
|
|
345
469
|
.from("order_carts")
|
|
@@ -355,27 +479,59 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
355
479
|
const profiles = await loadCarrierProfiles(sb);
|
|
356
480
|
const rules = await loadBeladingRules(sb);
|
|
357
481
|
const defaultProfile = profiles.find((p) => p.is_default) ?? profileFor(profiles, "trolley");
|
|
482
|
+
// Guard: zonder beladingbron mag er niet gegokt worden. Ladingdragers zonder
|
|
483
|
+
// lagenmodel (los product / enkele eenheid) hebben de belading niet nodig.
|
|
484
|
+
const guardProfile = profileFor(profiles, options.carrierType ?? defaultProfile.code);
|
|
485
|
+
const needsBelading = guardProfile.capacity_model !== "single" && guardProfile.capacity_model !== "none";
|
|
486
|
+
const unknownItems = inputs.filter((i) => sources.get(i.order_item_id) === "geen" && i.plants > 0);
|
|
487
|
+
if (needsBelading && unknownItems.length > 0 && !options.dryRun) {
|
|
488
|
+
const reason = `Belading onbekend voor ${unknownItems.length} orderregel(s): geen Floriday-verpakking, ` +
|
|
489
|
+
`geen waarden op de orderregel en geen productinstelling. Er is niets ingedeeld.`;
|
|
490
|
+
console.error(`[logistiek-engine] buffer ${bufferId} geblokkeerd: ${reason}`);
|
|
491
|
+
const { error: blockError } = await sb.rpc("logistics_plan_block", {
|
|
492
|
+
p_order_ids: orderIds,
|
|
493
|
+
p_reason: reason,
|
|
494
|
+
p_details: {
|
|
495
|
+
bufferId,
|
|
496
|
+
path: "typescript",
|
|
497
|
+
carrier: guardProfile.code,
|
|
498
|
+
beladingSources,
|
|
499
|
+
items: unknownItems.slice(0, 20).map((i) => ({ id: i.order_item_id, product: i.product_name })),
|
|
500
|
+
},
|
|
501
|
+
});
|
|
502
|
+
if (blockError)
|
|
503
|
+
warnings.push(`Blokkade kon niet worden vastgelegd: ${blockError.message}`);
|
|
504
|
+
return {
|
|
505
|
+
ok: false,
|
|
506
|
+
bufferId,
|
|
507
|
+
orderIds,
|
|
508
|
+
cartsCreated: 0,
|
|
509
|
+
replacedManualCount: 0,
|
|
510
|
+
usedFloridayPacking: false,
|
|
511
|
+
warnings: [...warnings, reason],
|
|
512
|
+
skipped: "blocked",
|
|
513
|
+
blockedReason: reason,
|
|
514
|
+
beladingSources,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
358
517
|
let plannedCarts = [];
|
|
359
518
|
let usedFloridayPacking = false;
|
|
519
|
+
// Overname-pad: één order in de buffer én een volledige Floriday-verpakking.
|
|
520
|
+
// Dan neemt de engine de verhouding van Floriday over en optimaliseert niet.
|
|
521
|
+
// (De oude tak eiste `floriday_packing.carts` als array; Floriday stuurt zo'n
|
|
522
|
+
// kant-en-klare karrenlijst nergens mee, die tak is nooit genomen en is
|
|
523
|
+
// daarom verwijderd.)
|
|
360
524
|
const solo = orderRows.length === 1 ? orderRows[0] : null;
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
|
|
525
|
+
const soloPacking = solo ? beladingFromFloridayPacking(solo.custom_fields?.floriday_packing) : null;
|
|
526
|
+
const takeoverProfile = profileFor(profiles, options.carrierType ?? defaultProfile.code);
|
|
527
|
+
const takeoverAllowed = takeoverProfile.capacity_model === "layered" || takeoverProfile.capacity_model === "stacked";
|
|
528
|
+
if (solo && soloPacking && takeoverAllowed) {
|
|
364
529
|
usedFloridayPacking = true;
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
carrier_type_id: profile.id ?? null,
|
|
371
|
-
source: "auto",
|
|
372
|
-
is_complete: Boolean(c.is_complete),
|
|
373
|
-
occupancy_pct: Number(c.occupancy_pct ?? 100),
|
|
374
|
-
total_layers_used: Number(c.total_layers_used ?? profile.layers_per_carrier),
|
|
375
|
-
max_layers: profile.layers_per_carrier,
|
|
376
|
-
weight_kg: null,
|
|
377
|
-
items: Array.isArray(c.items) ? c.items : [],
|
|
378
|
-
}));
|
|
530
|
+
plannedCarts = planByFloridayPacking(inputs, takeoverProfile, soloPacking);
|
|
531
|
+
console.log(`[logistiek-engine] buffer ${bufferId}: overname Floriday-verpakking ` +
|
|
532
|
+
`(${soloPacking.plants_per_crate}x${soloPacking.crates_per_layer}x${soloPacking.layers_per_cart} = ` +
|
|
533
|
+
`${soloPacking.plants_per_crate * soloPacking.crates_per_layer * soloPacking.layers_per_cart} planten/kar) ` +
|
|
534
|
+
`-> ${plannedCarts.length} ladingdragers`);
|
|
379
535
|
}
|
|
380
536
|
else {
|
|
381
537
|
// Ladingdrager per orderregel bepalen en per type een ronde draaien.
|
|
@@ -407,11 +563,39 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
407
563
|
c.cart_number = idx + 1;
|
|
408
564
|
});
|
|
409
565
|
}
|
|
410
|
-
// Veiligheidsgrens: een onrealistisch grote
|
|
411
|
-
//
|
|
566
|
+
// Veiligheidsgrens: een onrealistisch grote uitkomst wordt niet afgekapt maar
|
|
567
|
+
// geblokkeerd. Een half plan laat planten stilzwijgend uit de pickstroom vallen.
|
|
412
568
|
if (plannedCarts.length > MAX_CARTS_PER_BUFFER) {
|
|
413
|
-
|
|
414
|
-
|
|
569
|
+
const reason = `Deze orderbuffer levert ${plannedCarts.length} ladingdragers op, meer dan de grens van ` +
|
|
570
|
+
`${MAX_CARTS_PER_BUFFER}. Er is niets ingedeeld; controleer de aantallen of de belading van de producten.`;
|
|
571
|
+
console.error(`[logistiek-engine] buffer ${bufferId} geblokkeerd: ${reason}`);
|
|
572
|
+
if (!options.dryRun) {
|
|
573
|
+
const { error: blockError } = await sb.rpc("logistics_plan_block", {
|
|
574
|
+
p_order_ids: orderIds,
|
|
575
|
+
p_reason: reason,
|
|
576
|
+
p_details: {
|
|
577
|
+
bufferId,
|
|
578
|
+
path: "typescript",
|
|
579
|
+
carts: plannedCarts.length,
|
|
580
|
+
limit: MAX_CARTS_PER_BUFFER,
|
|
581
|
+
beladingSources,
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
if (blockError)
|
|
585
|
+
warnings.push(`Blokkade kon niet worden vastgelegd: ${blockError.message}`);
|
|
586
|
+
}
|
|
587
|
+
return {
|
|
588
|
+
ok: false,
|
|
589
|
+
bufferId,
|
|
590
|
+
orderIds,
|
|
591
|
+
cartsCreated: 0,
|
|
592
|
+
replacedManualCount,
|
|
593
|
+
usedFloridayPacking,
|
|
594
|
+
warnings: [...warnings, reason],
|
|
595
|
+
skipped: "blocked",
|
|
596
|
+
blockedReason: reason,
|
|
597
|
+
beladingSources,
|
|
598
|
+
};
|
|
415
599
|
}
|
|
416
600
|
if (options.dryRun) {
|
|
417
601
|
return {
|
|
@@ -460,8 +644,10 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
460
644
|
.insert(cartRows)
|
|
461
645
|
.select("id, cart_number");
|
|
462
646
|
if (error || !Array.isArray(insertedCarts)) {
|
|
463
|
-
|
|
464
|
-
|
|
647
|
+
const message = `Ladingdragers konden niet worden opgeslagen: ${error?.message ?? "onbekende fout"}`;
|
|
648
|
+
console.error(`[logistiek-engine] buffer ${bufferId}: ${message}`);
|
|
649
|
+
await sb.from("order_carts").delete().eq("buffer_id", bufferId);
|
|
650
|
+
throw new Error(message);
|
|
465
651
|
}
|
|
466
652
|
created += insertedCarts.length;
|
|
467
653
|
const idByNumber = new Map(insertedCarts.map((c) => [Number(c.cart_number), String(c.id)]));
|
|
@@ -490,8 +676,19 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
490
676
|
}
|
|
491
677
|
for (let s = 0; s < itemRows.length; s += 500) {
|
|
492
678
|
const slice = itemRows.slice(s, s + 500);
|
|
493
|
-
if (slice.length
|
|
494
|
-
|
|
679
|
+
if (slice.length === 0)
|
|
680
|
+
continue;
|
|
681
|
+
const { error: itemError } = await sb.from("order_cart_items").insert(slice);
|
|
682
|
+
if (itemError) {
|
|
683
|
+
// Ladingdragers zonder inhoud zijn een ongeldige uitkomst: alles terugdraaien
|
|
684
|
+
// en de planrun laten falen, zodat de wachtrij het opnieuw of geblokkeerd oppakt.
|
|
685
|
+
const message = `Inhoud van de ladingdragers kon niet worden opgeslagen: ${itemError.message}` +
|
|
686
|
+
(itemError.details ? ` — ${itemError.details}` : "") +
|
|
687
|
+
(itemError.hint ? ` (${itemError.hint})` : "");
|
|
688
|
+
console.error(`[logistiek-engine] buffer ${bufferId}: ${message}`);
|
|
689
|
+
await sb.from("order_carts").delete().eq("buffer_id", bufferId);
|
|
690
|
+
throw new Error(message);
|
|
691
|
+
}
|
|
495
692
|
}
|
|
496
693
|
}
|
|
497
694
|
return {
|
|
@@ -502,6 +699,7 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
|
|
|
502
699
|
replacedManualCount,
|
|
503
700
|
usedFloridayPacking,
|
|
504
701
|
warnings,
|
|
702
|
+
beladingSources,
|
|
505
703
|
carts: plannedCarts,
|
|
506
704
|
};
|
|
507
705
|
}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// ============================================================================
|
|
7
7
|
const OPEN_STATUSES = ["planned", "ready", "in_progress"];
|
|
8
8
|
export async function attachIndelingToTasks(sb, result) {
|
|
9
|
-
if (!result || result.skipped
|
|
9
|
+
if (!result || result.skipped)
|
|
10
10
|
return 0;
|
|
11
11
|
const carts = Array.isArray(result.carts) ? result.carts : [];
|
|
12
12
|
if (result.orderIds.length === 0)
|
|
@@ -15727,6 +15727,45 @@ export declare const startRecalcJob: import("@tanstack/start-client-core").Optio
|
|
|
15727
15727
|
};
|
|
15728
15728
|
Relationships: [];
|
|
15729
15729
|
};
|
|
15730
|
+
stock_consumption_alerts: {
|
|
15731
|
+
Row: {
|
|
15732
|
+
alert_type: string;
|
|
15733
|
+
created_at: string;
|
|
15734
|
+
id: string;
|
|
15735
|
+
mutation_log_id: string | null;
|
|
15736
|
+
order_id: string | null;
|
|
15737
|
+
order_item_id: string | null;
|
|
15738
|
+
qty: number;
|
|
15739
|
+
reason: string | null;
|
|
15740
|
+
resolved_at: string | null;
|
|
15741
|
+
updated_at: string;
|
|
15742
|
+
};
|
|
15743
|
+
Insert: {
|
|
15744
|
+
alert_type: string;
|
|
15745
|
+
created_at?: string;
|
|
15746
|
+
id?: string;
|
|
15747
|
+
mutation_log_id?: string | null;
|
|
15748
|
+
order_id?: string | null;
|
|
15749
|
+
order_item_id?: string | null;
|
|
15750
|
+
qty?: number;
|
|
15751
|
+
reason?: string | null;
|
|
15752
|
+
resolved_at?: string | null;
|
|
15753
|
+
updated_at?: string;
|
|
15754
|
+
};
|
|
15755
|
+
Update: {
|
|
15756
|
+
alert_type?: string;
|
|
15757
|
+
created_at?: string;
|
|
15758
|
+
id?: string;
|
|
15759
|
+
mutation_log_id?: string | null;
|
|
15760
|
+
order_id?: string | null;
|
|
15761
|
+
order_item_id?: string | null;
|
|
15762
|
+
qty?: number;
|
|
15763
|
+
reason?: string | null;
|
|
15764
|
+
resolved_at?: string | null;
|
|
15765
|
+
updated_at?: string;
|
|
15766
|
+
};
|
|
15767
|
+
Relationships: [];
|
|
15768
|
+
};
|
|
15730
15769
|
stock_import_runs: {
|
|
15731
15770
|
Row: {
|
|
15732
15771
|
connection_id: string | null;
|
|
@@ -21399,6 +21438,14 @@ export declare const startRecalcJob: import("@tanstack/start-client-core").Optio
|
|
|
21399
21438
|
};
|
|
21400
21439
|
Returns: import("../integrations/supabase/types").Json;
|
|
21401
21440
|
};
|
|
21441
|
+
logistics_plan_block: {
|
|
21442
|
+
Args: {
|
|
21443
|
+
p_details?: import("../integrations/supabase/types").Json;
|
|
21444
|
+
p_order_ids: string[];
|
|
21445
|
+
p_reason: string;
|
|
21446
|
+
};
|
|
21447
|
+
Returns: number;
|
|
21448
|
+
};
|
|
21402
21449
|
logistics_plan_buffer_sql: {
|
|
21403
21450
|
Args: {
|
|
21404
21451
|
p_buffer_id: string;
|
|
@@ -21767,6 +21814,12 @@ export declare const startRecalcJob: import("@tanstack/start-client-core").Optio
|
|
|
21767
21814
|
verwijderd: number;
|
|
21768
21815
|
}[];
|
|
21769
21816
|
};
|
|
21817
|
+
order_departure_proof: {
|
|
21818
|
+
Args: {
|
|
21819
|
+
p_order_id: string;
|
|
21820
|
+
};
|
|
21821
|
+
Returns: import("../integrations/supabase/types").Json;
|
|
21822
|
+
};
|
|
21770
21823
|
order_number_prefix: {
|
|
21771
21824
|
Args: {
|
|
21772
21825
|
p_custom_fields?: import("../integrations/supabase/types").Json;
|
|
@@ -38604,6 +38657,45 @@ export declare const tickRecalcJob: import("@tanstack/start-client-core").Option
|
|
|
38604
38657
|
};
|
|
38605
38658
|
Relationships: [];
|
|
38606
38659
|
};
|
|
38660
|
+
stock_consumption_alerts: {
|
|
38661
|
+
Row: {
|
|
38662
|
+
alert_type: string;
|
|
38663
|
+
created_at: string;
|
|
38664
|
+
id: string;
|
|
38665
|
+
mutation_log_id: string | null;
|
|
38666
|
+
order_id: string | null;
|
|
38667
|
+
order_item_id: string | null;
|
|
38668
|
+
qty: number;
|
|
38669
|
+
reason: string | null;
|
|
38670
|
+
resolved_at: string | null;
|
|
38671
|
+
updated_at: string;
|
|
38672
|
+
};
|
|
38673
|
+
Insert: {
|
|
38674
|
+
alert_type: string;
|
|
38675
|
+
created_at?: string;
|
|
38676
|
+
id?: string;
|
|
38677
|
+
mutation_log_id?: string | null;
|
|
38678
|
+
order_id?: string | null;
|
|
38679
|
+
order_item_id?: string | null;
|
|
38680
|
+
qty?: number;
|
|
38681
|
+
reason?: string | null;
|
|
38682
|
+
resolved_at?: string | null;
|
|
38683
|
+
updated_at?: string;
|
|
38684
|
+
};
|
|
38685
|
+
Update: {
|
|
38686
|
+
alert_type?: string;
|
|
38687
|
+
created_at?: string;
|
|
38688
|
+
id?: string;
|
|
38689
|
+
mutation_log_id?: string | null;
|
|
38690
|
+
order_id?: string | null;
|
|
38691
|
+
order_item_id?: string | null;
|
|
38692
|
+
qty?: number;
|
|
38693
|
+
reason?: string | null;
|
|
38694
|
+
resolved_at?: string | null;
|
|
38695
|
+
updated_at?: string;
|
|
38696
|
+
};
|
|
38697
|
+
Relationships: [];
|
|
38698
|
+
};
|
|
38607
38699
|
stock_import_runs: {
|
|
38608
38700
|
Row: {
|
|
38609
38701
|
connection_id: string | null;
|
|
@@ -44276,6 +44368,14 @@ export declare const tickRecalcJob: import("@tanstack/start-client-core").Option
|
|
|
44276
44368
|
};
|
|
44277
44369
|
Returns: import("../integrations/supabase/types").Json;
|
|
44278
44370
|
};
|
|
44371
|
+
logistics_plan_block: {
|
|
44372
|
+
Args: {
|
|
44373
|
+
p_details?: import("../integrations/supabase/types").Json;
|
|
44374
|
+
p_order_ids: string[];
|
|
44375
|
+
p_reason: string;
|
|
44376
|
+
};
|
|
44377
|
+
Returns: number;
|
|
44378
|
+
};
|
|
44279
44379
|
logistics_plan_buffer_sql: {
|
|
44280
44380
|
Args: {
|
|
44281
44381
|
p_buffer_id: string;
|
|
@@ -44644,6 +44744,12 @@ export declare const tickRecalcJob: import("@tanstack/start-client-core").Option
|
|
|
44644
44744
|
verwijderd: number;
|
|
44645
44745
|
}[];
|
|
44646
44746
|
};
|
|
44747
|
+
order_departure_proof: {
|
|
44748
|
+
Args: {
|
|
44749
|
+
p_order_id: string;
|
|
44750
|
+
};
|
|
44751
|
+
Returns: import("../integrations/supabase/types").Json;
|
|
44752
|
+
};
|
|
44647
44753
|
order_number_prefix: {
|
|
44648
44754
|
Args: {
|
|
44649
44755
|
p_custom_fields?: import("../integrations/supabase/types").Json;
|