@flowselections/logistiek-engine 1.0.14 → 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 (33) hide show
  1. package/README.md +24 -24
  2. package/dist-lib/integrations/supabase/auth-middleware.d.ts +53 -0
  3. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  4. package/dist-lib/integrations/supabase/client.d.ts +1516 -159
  5. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  6. package/dist-lib/integrations/supabase/client.server.d.ts +53 -0
  7. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  8. package/dist-lib/integrations/supabase/moduleClient.d.ts +53 -0
  9. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  10. package/dist-lib/integrations/supabase/types.d.ts +53 -0
  11. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  12. package/dist-lib/lib/bulkLoadCarrierPlanning.server.d.ts +2 -0
  13. package/dist-lib/lib/bulkLoadCarrierPlanning.server.d.ts.map +1 -1
  14. package/dist-lib/lib/bulkLoadCarrierPlanning.server.js +11 -0
  15. package/dist-lib/lib/floridayDocuments.functions.d.ts +159 -0
  16. package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -1
  17. package/dist-lib/lib/floridayPush.functions.d.ts +53 -0
  18. package/dist-lib/lib/floridayPush.functions.d.ts.map +1 -1
  19. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +270 -30
  20. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -1
  21. package/dist-lib/lib/loadCarrierPlanning.server.d.ts +27 -2
  22. package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
  23. package/dist-lib/lib/loadCarrierPlanning.server.js +162 -30
  24. package/dist-lib/lib/loadCarrierTaskSync.server.js +1 -1
  25. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +106 -0
  26. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -1
  27. package/dist-lib/lib/logisticsRecalculate.functions.d.ts +53 -0
  28. package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -1
  29. package/dist-lib/lib/modulePrompts.js +1002 -1002
  30. package/dist-lib/lib/planQueue.server.d.ts +2 -0
  31. package/dist-lib/lib/planQueue.server.d.ts.map +1 -1
  32. package/dist-lib/lib/planQueue.server.js +22 -4
  33. 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":"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;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,GAAG,kBAAkB,GAAG,aAAa,GAAG,SAAS,GAAG,MAAM,CAAC;AAOrF;;;;;GAKG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,QAAQ,GAAG,IAAI,CAQ7E;AAiCD,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,cAAc,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,iEAAiE;IACjE,OAAO,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACvC;AAED,wBAAsB,0BAA0B,CAC9C,EAAE,EAAE,cAAc,EAClB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAChC,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,gBAAgB,EAAE,CAAC,CA6H7B;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,GAAG,SAAS,CAAC;IAC/B,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iFAAiF;IACjF,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;CAC3B;AAGD,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,CAyUvB;AAGD,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,cAAc,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAI9B"}
@@ -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)
@@ -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;