@flowselections/logistiek-engine 1.0.13 → 1.0.15

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.
Files changed (50) hide show
  1. package/README.md +24 -24
  2. package/dist-lib/components/FloridayPushCard.d.ts.map +1 -1
  3. package/dist-lib/components/FloridayPushCard.js +7 -2
  4. package/dist-lib/components/PrintjobsCard.d.ts.map +1 -1
  5. package/dist-lib/components/PrintjobsCard.js +3 -1
  6. package/dist-lib/hooks/useFloridayPushes.d.ts +7 -0
  7. package/dist-lib/hooks/useFloridayPushes.d.ts.map +1 -1
  8. package/dist-lib/hooks/useFloridayPushes.js +11 -0
  9. package/dist-lib/hooks/usePrintjobs.d.ts +1 -0
  10. package/dist-lib/hooks/usePrintjobs.d.ts.map +1 -1
  11. package/dist-lib/hooks/usePrintjobs.js +17 -1
  12. package/dist-lib/integrations/supabase/auth-middleware.d.ts +336 -0
  13. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  14. package/dist-lib/integrations/supabase/client.d.ts +1516 -159
  15. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  16. package/dist-lib/integrations/supabase/client.server.d.ts +336 -0
  17. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  18. package/dist-lib/integrations/supabase/moduleClient.d.ts +336 -0
  19. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  20. package/dist-lib/integrations/supabase/previewAuthStorage.d.ts +6 -0
  21. package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -0
  22. package/dist-lib/integrations/supabase/previewAuthStorage.js +92 -0
  23. package/dist-lib/integrations/supabase/types.d.ts +343 -0
  24. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  25. package/dist-lib/lib/bulkLoadCarrierPlanning.server.d.ts +2 -0
  26. package/dist-lib/lib/bulkLoadCarrierPlanning.server.d.ts.map +1 -1
  27. package/dist-lib/lib/bulkLoadCarrierPlanning.server.js +11 -0
  28. package/dist-lib/lib/floridayDocuments.functions.d.ts +1008 -0
  29. package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -1
  30. package/dist-lib/lib/floridayPush.functions.d.ts +336 -0
  31. package/dist-lib/lib/floridayPush.functions.d.ts.map +1 -1
  32. package/dist-lib/lib/floridayPush.server.d.ts +12 -0
  33. package/dist-lib/lib/floridayPush.server.d.ts.map +1 -1
  34. package/dist-lib/lib/floridayPush.server.js +41 -9
  35. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +1685 -30
  36. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -1
  37. package/dist-lib/lib/loadCarrierPlanning.server.d.ts +27 -2
  38. package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
  39. package/dist-lib/lib/loadCarrierPlanning.server.js +162 -30
  40. package/dist-lib/lib/loadCarrierTaskSync.server.js +1 -1
  41. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +672 -0
  42. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -1
  43. package/dist-lib/lib/logisticsRecalculate.functions.d.ts +336 -0
  44. package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -1
  45. package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
  46. package/dist-lib/lib/modulePrompts.js +1009 -970
  47. package/dist-lib/lib/planQueue.server.d.ts +2 -0
  48. package/dist-lib/lib/planQueue.server.d.ts.map +1 -1
  49. package/dist-lib/lib/planQueue.server.js +22 -4
  50. 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
- export declare function expandOrderItemsForPlanner(sb: SupabaseClient, rows: Array<Record<string, any>>): Promise<PlannerInputItem[]>;
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;
@@ -33,7 +53,11 @@ export interface ReplanResult {
33
53
  replacedManualCount: number;
34
54
  usedFloridayPacking: boolean;
35
55
  warnings: string[];
36
- skipped?: "locked";
56
+ skipped?: "locked" | "blocked";
57
+ /** Leesbare reden waarom er niets is ingedeeld. */
58
+ blockedReason?: string;
59
+ /** Gebruikte beladingbronnen in deze buffer, met aantal orderregels per bron. */
60
+ beladingSources?: Record<string, number>;
37
61
  carts?: PlannedLoadUnit[];
38
62
  }
39
63
  export interface ReplanOptions {
@@ -44,4 +68,5 @@ export interface ReplanOptions {
44
68
  }
45
69
  export declare function replanBufferInternal(sb: SupabaseClient, bufferId: string, options?: ReplanOptions): Promise<ReplanResult>;
46
70
  export declare function replanOrderInternal(sb: SupabaseClient, orderId: string, options?: ReplanOptions): Promise<ReplanResult | null>;
71
+ export {};
47
72
  //# 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,EACpB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAA0B,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAiCrF,wBAAgB,cAAc,CAC5B,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,CAAC,EAAE,GAAG,CAAA;CAAE,EACtG,IAAI,EAAE,MAAM,GACX;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAqCvD;AAID,wBAAsB,6BAA6B,CAAC,EAAE,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAmE/G;AAgBD,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,cAAc,EAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,GAC/B,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA0H7B;AAMD,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAO3F;AAUD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,OAAO,CAAC;CACtB;AAOD,wFAAwF;AACxF,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAsBzF;AAED,2FAA2F;AAC3F,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI,GAAG,cAAc,CAaxG;AAWD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,cAAc,EAClB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CA0OvB;AAED,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,cAAc,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAI9B"}
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
- function isDefaultBelading(b) {
132
- return b.plants_per_crate === 1 && b.crates_per_layer === 1 && b.layers_per_cart === 1;
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 };
133
151
  }
134
- export async function expandOrderItemsForPlanner(sb, rows) {
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" };
176
+ }
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 base = {
146
- plants_per_crate: Math.max(1, Number(row.plants_per_crate ?? row.products?.plants_per_crate ?? 1)),
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
- const belading = p
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: Math.max(1, Number(p.plants_per_crate ?? 1)),
201
- crates_per_layer: Math.max(1, Number(p.crates_per_layer ?? 1)),
202
- layers_per_cart: Math.max(1, Number(p.layers_per_cart ?? 1)),
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
- : { plants_per_crate: 1, crates_per_layer: 1, layers_per_cart: 1 };
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,
@@ -335,11 +378,23 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
335
378
  ...it,
336
379
  customer_id: customerByOrder.get(it.order_id) ?? null,
337
380
  }));
338
- const inputs = await expandOrderItemsForPlanner(sb, rawRows);
381
+ // Floriday-verpakking per order is de eerste beladingbron.
382
+ const packingByOrder = new Map();
383
+ for (const o of orderRows) {
384
+ const belading = beladingFromFloridayPacking(o.custom_fields?.floriday_packing);
385
+ if (belading)
386
+ packingByOrder.set(String(o.id), belading);
387
+ }
388
+ const sources = new Map();
389
+ const inputs = await expandOrderItemsForPlanner(sb, rawRows, { packingByOrder, sources });
339
390
  const itemToOrder = new Map();
340
391
  for (const i of inputs)
341
392
  if (i.order_id)
342
393
  itemToOrder.set(i.order_item_id, i.order_id);
394
+ const beladingSources = {};
395
+ for (const s of sources.values())
396
+ beladingSources[s] = (beladingSources[s] ?? 0) + 1;
397
+ console.log(`[logistiek-engine] buffer ${bufferId}: beladingbronnen ${JSON.stringify(beladingSources)}`);
343
398
  // Bestaande beladingen tellen en wissen.
344
399
  const { data: existingCarts } = await sb
345
400
  .from("order_carts")
@@ -355,6 +410,41 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
355
410
  const profiles = await loadCarrierProfiles(sb);
356
411
  const rules = await loadBeladingRules(sb);
357
412
  const defaultProfile = profiles.find((p) => p.is_default) ?? profileFor(profiles, "trolley");
413
+ // Guard: zonder beladingbron mag er niet gegokt worden. Ladingdragers zonder
414
+ // lagenmodel (los product / enkele eenheid) hebben de belading niet nodig.
415
+ const guardProfile = profileFor(profiles, options.carrierType ?? defaultProfile.code);
416
+ const needsBelading = guardProfile.capacity_model !== "single" && guardProfile.capacity_model !== "none";
417
+ const unknownItems = inputs.filter((i) => sources.get(i.order_item_id) === "geen" && i.plants > 0);
418
+ if (needsBelading && unknownItems.length > 0 && !options.dryRun) {
419
+ const reason = `Belading onbekend voor ${unknownItems.length} orderregel(s): geen Floriday-verpakking, ` +
420
+ `geen waarden op de orderregel en geen productinstelling. Er is niets ingedeeld.`;
421
+ console.error(`[logistiek-engine] buffer ${bufferId} geblokkeerd: ${reason}`);
422
+ const { error: blockError } = await sb.rpc("logistics_plan_block", {
423
+ p_order_ids: orderIds,
424
+ p_reason: reason,
425
+ p_details: {
426
+ bufferId,
427
+ path: "typescript",
428
+ carrier: guardProfile.code,
429
+ beladingSources,
430
+ items: unknownItems.slice(0, 20).map((i) => ({ id: i.order_item_id, product: i.product_name })),
431
+ },
432
+ });
433
+ if (blockError)
434
+ warnings.push(`Blokkade kon niet worden vastgelegd: ${blockError.message}`);
435
+ return {
436
+ ok: false,
437
+ bufferId,
438
+ orderIds,
439
+ cartsCreated: 0,
440
+ replacedManualCount: 0,
441
+ usedFloridayPacking: false,
442
+ warnings: [...warnings, reason],
443
+ skipped: "blocked",
444
+ blockedReason: reason,
445
+ beladingSources,
446
+ };
447
+ }
358
448
  let plannedCarts = [];
359
449
  let usedFloridayPacking = false;
360
450
  const solo = orderRows.length === 1 ? orderRows[0] : null;
@@ -407,11 +497,39 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
407
497
  c.cart_number = idx + 1;
408
498
  });
409
499
  }
410
- // Veiligheidsgrens: een onrealistisch grote order (bijv. een verkeerd
411
- // ingevoerd aantal planten) mag de indeling niet laten vastlopen.
500
+ // Veiligheidsgrens: een onrealistisch grote uitkomst wordt niet afgekapt maar
501
+ // geblokkeerd. Een half plan laat planten stilzwijgend uit de pickstroom vallen.
412
502
  if (plannedCarts.length > MAX_CARTS_PER_BUFFER) {
413
- warnings.push(`Deze orderbuffer levert ${plannedCarts.length} ladingdragers op. Er zijn er ${MAX_CARTS_PER_BUFFER} ingedeeld; controleer de aantallen of de belading van de producten.`);
414
- plannedCarts = plannedCarts.slice(0, MAX_CARTS_PER_BUFFER);
503
+ const reason = `Deze orderbuffer levert ${plannedCarts.length} ladingdragers op, meer dan de grens van ` +
504
+ `${MAX_CARTS_PER_BUFFER}. Er is niets ingedeeld; controleer de aantallen of de belading van de producten.`;
505
+ console.error(`[logistiek-engine] buffer ${bufferId} geblokkeerd: ${reason}`);
506
+ if (!options.dryRun) {
507
+ const { error: blockError } = await sb.rpc("logistics_plan_block", {
508
+ p_order_ids: orderIds,
509
+ p_reason: reason,
510
+ p_details: {
511
+ bufferId,
512
+ path: "typescript",
513
+ carts: plannedCarts.length,
514
+ limit: MAX_CARTS_PER_BUFFER,
515
+ beladingSources,
516
+ },
517
+ });
518
+ if (blockError)
519
+ warnings.push(`Blokkade kon niet worden vastgelegd: ${blockError.message}`);
520
+ }
521
+ return {
522
+ ok: false,
523
+ bufferId,
524
+ orderIds,
525
+ cartsCreated: 0,
526
+ replacedManualCount,
527
+ usedFloridayPacking,
528
+ warnings: [...warnings, reason],
529
+ skipped: "blocked",
530
+ blockedReason: reason,
531
+ beladingSources,
532
+ };
415
533
  }
416
534
  if (options.dryRun) {
417
535
  return {
@@ -460,8 +578,10 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
460
578
  .insert(cartRows)
461
579
  .select("id, cart_number");
462
580
  if (error || !Array.isArray(insertedCarts)) {
463
- warnings.push(`Een deel van de ladingdragers kon niet worden opgeslagen: ${error?.message ?? ""}`);
464
- continue;
581
+ const message = `Ladingdragers konden niet worden opgeslagen: ${error?.message ?? "onbekende fout"}`;
582
+ console.error(`[logistiek-engine] buffer ${bufferId}: ${message}`);
583
+ await sb.from("order_carts").delete().eq("buffer_id", bufferId);
584
+ throw new Error(message);
465
585
  }
466
586
  created += insertedCarts.length;
467
587
  const idByNumber = new Map(insertedCarts.map((c) => [Number(c.cart_number), String(c.id)]));
@@ -490,8 +610,19 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
490
610
  }
491
611
  for (let s = 0; s < itemRows.length; s += 500) {
492
612
  const slice = itemRows.slice(s, s + 500);
493
- if (slice.length > 0)
494
- await sb.from("order_cart_items").insert(slice);
613
+ if (slice.length === 0)
614
+ continue;
615
+ const { error: itemError } = await sb.from("order_cart_items").insert(slice);
616
+ if (itemError) {
617
+ // Ladingdragers zonder inhoud zijn een ongeldige uitkomst: alles terugdraaien
618
+ // en de planrun laten falen, zodat de wachtrij het opnieuw of geblokkeerd oppakt.
619
+ const message = `Inhoud van de ladingdragers kon niet worden opgeslagen: ${itemError.message}` +
620
+ (itemError.details ? ` — ${itemError.details}` : "") +
621
+ (itemError.hint ? ` (${itemError.hint})` : "");
622
+ console.error(`[logistiek-engine] buffer ${bufferId}: ${message}`);
623
+ await sb.from("order_carts").delete().eq("buffer_id", bufferId);
624
+ throw new Error(message);
625
+ }
495
626
  }
496
627
  }
497
628
  return {
@@ -502,6 +633,7 @@ export async function replanBufferInternal(sb, bufferId, options = {}) {
502
633
  replacedManualCount,
503
634
  usedFloridayPacking,
504
635
  warnings,
636
+ beladingSources,
505
637
  carts: plannedCarts,
506
638
  };
507
639
  }
@@ -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 === "locked")
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)