@flowselections/logistiek-engine 1.0.27 → 1.0.28

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 (95) hide show
  1. package/README.md +24 -24
  2. package/dist-lib/components/AfmetingenBronnenCard.d.ts +2 -0
  3. package/dist-lib/components/AfmetingenBronnenCard.d.ts.map +1 -0
  4. package/dist-lib/components/AfmetingenBronnenCard.js +46 -0
  5. package/dist-lib/components/BufferDebugCard.d.ts.map +1 -1
  6. package/dist-lib/components/BufferDebugCard.js +6 -1
  7. package/dist-lib/components/ConsistentieCard.d.ts.map +1 -1
  8. package/dist-lib/components/ConsistentieCard.js +1 -1
  9. package/dist-lib/components/KarplanStatusCard.d.ts +2 -0
  10. package/dist-lib/components/KarplanStatusCard.d.ts.map +1 -0
  11. package/dist-lib/components/KarplanStatusCard.js +31 -0
  12. package/dist-lib/components/RegiePage.d.ts.map +1 -1
  13. package/dist-lib/components/RegiePage.js +3 -1
  14. package/dist-lib/components/RuleEnginePage.d.ts.map +1 -1
  15. package/dist-lib/components/RuleEnginePage.js +7 -4
  16. package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts +2 -0
  17. package/dist-lib/components/rule-engine/FloridayConnectionBanner.d.ts.map +1 -0
  18. package/dist-lib/components/rule-engine/FloridayConnectionBanner.js +41 -0
  19. package/dist-lib/components/rule-engine/RuleFieldInput.d.ts.map +1 -1
  20. package/dist-lib/components/rule-engine/RuleFieldInput.js +9 -0
  21. package/dist-lib/hooks/useRecalcJob.d.ts.map +1 -1
  22. package/dist-lib/hooks/useRecalcJob.js +26 -1
  23. package/dist-lib/hooks/useScannersPrinters.d.ts +11 -0
  24. package/dist-lib/hooks/useScannersPrinters.d.ts.map +1 -0
  25. package/dist-lib/hooks/useScannersPrinters.js +65 -0
  26. package/dist-lib/index.d.ts +2 -0
  27. package/dist-lib/index.d.ts.map +1 -1
  28. package/dist-lib/index.js +2 -0
  29. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2063 -78
  30. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  31. package/dist-lib/integrations/supabase/client.d.ts +20293 -0
  32. package/dist-lib/integrations/supabase/client.d.ts.map +1 -0
  33. package/dist-lib/integrations/supabase/client.js +13 -0
  34. package/dist-lib/integrations/supabase/client.server.d.ts +2063 -78
  35. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  36. package/dist-lib/integrations/supabase/client.server.js +1 -1
  37. package/dist-lib/integrations/supabase/moduleClient.d.ts +2063 -78
  38. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  39. package/dist-lib/integrations/supabase/types.d.ts +2097 -68
  40. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  41. package/dist-lib/integrations/supabase/types.js +1 -0
  42. package/dist-lib/lib/attachAuthToken.d.ts +2 -0
  43. package/dist-lib/lib/attachAuthToken.d.ts.map +1 -0
  44. package/dist-lib/lib/attachAuthToken.js +50 -0
  45. package/dist-lib/lib/bufferMatching.d.ts.map +1 -1
  46. package/dist-lib/lib/bufferMatching.js +1 -0
  47. package/dist-lib/lib/cartPlanRequests.server.d.ts +13 -0
  48. package/dist-lib/lib/cartPlanRequests.server.d.ts.map +1 -0
  49. package/dist-lib/lib/cartPlanRequests.server.js +168 -0
  50. package/dist-lib/lib/controlScan.server.d.ts +12 -0
  51. package/dist-lib/lib/controlScan.server.d.ts.map +1 -0
  52. package/dist-lib/lib/controlScan.server.js +197 -0
  53. package/dist-lib/lib/dimensionData.server.d.ts +11 -1
  54. package/dist-lib/lib/dimensionData.server.d.ts.map +1 -1
  55. package/dist-lib/lib/dimensionData.server.js +58 -48
  56. package/dist-lib/lib/dimensionPlanner.d.ts +26 -1
  57. package/dist-lib/lib/dimensionPlanner.d.ts.map +1 -1
  58. package/dist-lib/lib/dimensionPlanner.js +37 -6
  59. package/dist-lib/lib/floridayDocuments.functions.d.ts +6240 -285
  60. package/dist-lib/lib/floridayDocuments.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/floridayDocuments.functions.js +4 -3
  62. package/dist-lib/lib/floridayPush.functions.d.ts +2064 -79
  63. package/dist-lib/lib/floridayPush.functions.d.ts.map +1 -1
  64. package/dist-lib/lib/floridayPush.functions.js +2 -1
  65. package/dist-lib/lib/handlers/logistics-tick.server.d.ts.map +1 -1
  66. package/dist-lib/lib/handlers/logistics-tick.server.js +57 -0
  67. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts +10302 -377
  68. package/dist-lib/lib/loadCarrierPlanning.functions.d.ts.map +1 -1
  69. package/dist-lib/lib/loadCarrierPlanning.functions.js +6 -5
  70. package/dist-lib/lib/loadCarrierPlanning.server.d.ts +42 -0
  71. package/dist-lib/lib/loadCarrierPlanning.server.d.ts.map +1 -1
  72. package/dist-lib/lib/loadCarrierPlanning.server.js +563 -31
  73. package/dist-lib/lib/logisticsPlanner.d.ts.map +1 -1
  74. package/dist-lib/lib/logisticsPlanner.js +24 -9
  75. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts +4163 -193
  76. package/dist-lib/lib/logisticsRecalcJob.functions.d.ts.map +1 -1
  77. package/dist-lib/lib/logisticsRecalcJob.functions.js +3 -2
  78. package/dist-lib/lib/logisticsRecalcJob.server.d.ts.map +1 -1
  79. package/dist-lib/lib/logisticsRecalcJob.server.js +1 -0
  80. package/dist-lib/lib/logisticsRecalculate.functions.d.ts +2064 -79
  81. package/dist-lib/lib/logisticsRecalculate.functions.d.ts.map +1 -1
  82. package/dist-lib/lib/logisticsRecalculate.functions.js +3 -1
  83. package/dist-lib/lib/modulePrompts.d.ts.map +1 -1
  84. package/dist-lib/lib/modulePrompts.js +2560 -1586
  85. package/dist-lib/lib/planQueue.server.d.ts.map +1 -1
  86. package/dist-lib/lib/planQueue.server.js +6 -0
  87. package/dist-lib/lib/readableServerErrors.d.ts +2 -0
  88. package/dist-lib/lib/readableServerErrors.d.ts.map +1 -0
  89. package/dist-lib/lib/readableServerErrors.js +36 -0
  90. package/dist-lib/lib/ruleEngineSchema.d.ts +1 -1
  91. package/dist-lib/lib/ruleEngineSchema.d.ts.map +1 -1
  92. package/dist-lib/lib/ruleEngineSchema.js +72 -51
  93. package/dist-lib/lib/ruleEngineWizard.d.ts.map +1 -1
  94. package/dist-lib/lib/ruleEngineWizard.js +5 -6
  95. package/package.json +1 -1
@@ -57,31 +57,29 @@ function missingText(subject, name, field) {
57
57
  * Haalt fust- en planthoogtegegevens op bij de producten van de plannerinvoer.
58
58
  * Groepsleden gebruiken hun eigen product; regels zonder product_id kunnen niet
59
59
  * op afmetingen worden ingedeeld.
60
+ *
61
+ * Ontbreekt alleen de planthoogte, dan wordt die afgeleid uit de belading:
62
+ * de bruikbare hoogte van de ladingdrager gedeeld door het aantal lagen dat
63
+ * er volgens de belading op hoort, min de hoogte van het fust. Lukt dat niet
64
+ * (geen ladingdragerhoogte, geen lagen, of een uitkomst van nul of minder),
65
+ * dan blijft het een ontbrekend gegeven en wordt er niets ingedeeld.
60
66
  */
61
- export async function buildDimensionItems(sb, inputs) {
67
+ export async function buildDimensionItems(sb, inputs, carrier,
68
+ /** Aantal lagen dat volgens het beheerscherm op deze ladingdrager past. */
69
+ carrierLayers) {
62
70
  const missing = [];
71
+ const derived = [];
63
72
  const items = [];
64
73
  const productIds = [...new Set(inputs.map((i) => i.product_id).filter((v) => Boolean(v)))];
65
- const productById = new Map();
74
+ const dimById = new Map();
66
75
  if (productIds.length > 0) {
67
76
  const { data } = await sb
68
- .from("products")
69
- .select("id, product, plant_height_cm, fust_type_id, plants_per_crate, weight")
70
- .in("id", productIds);
71
- for (const p of data ?? [])
72
- productById.set(String(p.id), p);
73
- }
74
- const fustIds = [
75
- ...new Set([...productById.values()].map((p) => p.fust_type_id).filter((v) => Boolean(v))),
76
- ];
77
- const fustById = new Map();
78
- if (fustIds.length > 0) {
79
- const { data } = await sb
80
- .from("fust_types")
81
- .select("id, name, length_mm, width_mm, height_mm")
82
- .in("id", fustIds);
83
- for (const f of data ?? [])
84
- fustById.set(String(f.id), f);
77
+ .from("logistics_product_dimensions")
78
+ .select("product_id, product_name, plants_per_crate, fust_type_id, fust_name, fust_length_mm, fust_width_mm, fust_height_mm, plant_height_cm, height_source, dimension_source, missing_reason")
79
+ .in("product_id", productIds);
80
+ const safeRows = Array.isArray(data) ? data : [];
81
+ for (const d of safeRows)
82
+ dimById.set(String(d.product_id), d);
85
83
  }
86
84
  const seen = new Set();
87
85
  const addMissing = (m) => {
@@ -95,8 +93,8 @@ export async function buildDimensionItems(sb, inputs) {
95
93
  if (input.plants <= 0)
96
94
  continue;
97
95
  const label = input.product_name || "onbekend product";
98
- const product = input.product_id ? productById.get(input.product_id) : null;
99
- if (!product) {
96
+ const dim = input.product_id ? dimById.get(input.product_id) : null;
97
+ if (!dim) {
100
98
  addMissing({
101
99
  subject: "product",
102
100
  name: label,
@@ -106,37 +104,46 @@ export async function buildDimensionItems(sb, inputs) {
106
104
  });
107
105
  continue;
108
106
  }
109
- const plantHeight = posNum(product.plant_height_cm);
110
- if (plantHeight == null) {
111
- addMissing({
112
- subject: "product",
113
- name: product.product ?? label,
114
- field: "planthoogte",
115
- message: missingText("product", product.product ?? label, "planthoogte"),
116
- });
107
+ const name = dim.product_name ?? label;
108
+ const l = posNum(dim.fust_length_mm);
109
+ const w = posNum(dim.fust_width_mm);
110
+ const h = posNum(dim.fust_height_mm);
111
+ let plantHeight = posNum(dim.plant_height_cm);
112
+ // Planthoogte ontbreekt, maar fust- en dragermaten zijn er wel: afleiden
113
+ // uit het aantal lagen dat op de drager hoort. Eerst de belading van het
114
+ // product zelf; past dat fysiek niet (fust hoger dan de laaghoogte), dan
115
+ // het aantal lagen van de ladingdrager uit het beheerscherm.
116
+ if (plantHeight == null && h != null && carrier && carrier.height_cm > 0) {
117
+ const productLayers = Math.round(Number(input.layers_per_cart));
118
+ const carrierLayerCount = Math.round(Number(carrierLayers));
119
+ const kandidaten = [];
120
+ if (Number.isFinite(productLayers) && productLayers > 0)
121
+ kandidaten.push({ layers: productLayers, bron: "de belading van het product" });
122
+ if (Number.isFinite(carrierLayerCount) && carrierLayerCount > 0)
123
+ kandidaten.push({ layers: carrierLayerCount, bron: `de lagen van ${carrier.name}` });
124
+ for (const kandidaat of kandidaten) {
125
+ const perLayer = carrier.height_cm / kandidaat.layers;
126
+ const afgeleid = Number((perLayer - h / 10).toFixed(1));
127
+ if (afgeleid > 0) {
128
+ plantHeight = afgeleid;
129
+ derived.push(`De planthoogte van ${name} was niet ingevuld en is berekend uit ${kandidaat.bron}: ` +
130
+ `${kandidaat.layers} lagen op ${carrier.name} (${carrier.height_cm} cm) min de fusthoogte ` +
131
+ `(${round1(h / 10)} cm) geeft ${afgeleid} cm.`);
132
+ break;
133
+ }
134
+ }
117
135
  }
118
- const fust = product.fust_type_id ? fustById.get(String(product.fust_type_id)) : null;
119
- if (!fust) {
136
+ if (l == null || w == null || h == null || plantHeight == null) {
120
137
  addMissing({
121
- subject: "product",
122
- name: product.product ?? label,
123
- field: "fusttype",
124
- message: missingText("product", product.product ?? label, "fusttype"),
138
+ subject: l == null && w == null && h == null ? "product" : "fust",
139
+ name,
140
+ field: plantHeight == null && l != null ? "planthoogte" : "fusttype",
141
+ message: dim.missing_reason ??
142
+ `De karrenindeling kan niet worden berekend. De afmetingen van ${name} zijn onvolledig.`,
125
143
  });
126
144
  continue;
127
145
  }
128
- const l = posNum(fust.length_mm);
129
- const w = posNum(fust.width_mm);
130
- const h = posNum(fust.height_mm);
131
- if (l == null)
132
- addMissing({ subject: "fust", name: fust.name, field: "lengte", message: missingText("fust", fust.name, "lengte") });
133
- if (w == null)
134
- addMissing({ subject: "fust", name: fust.name, field: "breedte", message: missingText("fust", fust.name, "breedte") });
135
- if (h == null)
136
- addMissing({ subject: "fust", name: fust.name, field: "hoogte", message: missingText("fust", fust.name, "hoogte") });
137
- if (plantHeight == null || l == null || w == null || h == null)
138
- continue;
139
- const ppc = Math.max(1, Math.round(input.plants_per_crate || product.plants_per_crate || 1));
146
+ const ppc = Math.max(1, Math.round(input.plants_per_crate || dim.plants_per_crate || 1));
140
147
  const crates = Math.max(1, Math.ceil(input.plants / ppc));
141
148
  items.push({
142
149
  order_item_id: input.order_item_id,
@@ -153,5 +160,8 @@ export async function buildDimensionItems(sb, inputs) {
153
160
  weight_per_crate: input.weight_per_crate ?? null,
154
161
  });
155
162
  }
156
- return { items, missing };
163
+ return { items, missing, derived };
164
+ }
165
+ function round1(n) {
166
+ return Number(n.toFixed(1));
157
167
  }
@@ -1,4 +1,4 @@
1
- export declare const DIMENSION_PLANNER_VERSION = 4;
1
+ export declare const DIMENSION_PLANNER_VERSION = 5;
2
2
  export interface DimensionItem {
3
3
  order_item_id: string;
4
4
  order_id: string | null;
@@ -42,12 +42,29 @@ export interface DimensionPlacement {
42
42
  /** Rotatie waarmee de fusten op deze laag staan. */
43
43
  rotated: boolean;
44
44
  }
45
+ /** Eén fust op zijn werkelijke plek binnen een laag. Alle maten in cm. */
46
+ export interface DimensionKegPosition {
47
+ keg_id: string;
48
+ order_item_id: string;
49
+ order_id: string | null;
50
+ product_id: string | null;
51
+ label: string;
52
+ x: number;
53
+ y: number;
54
+ width: number;
55
+ height: number;
56
+ rotation: number;
57
+ }
45
58
  export interface DimensionLayer {
46
59
  layer_index: number;
47
60
  height_cm: number;
48
61
  /** Benutting van het laagoppervlak in procenten. */
49
62
  area_used_pct: number;
63
+ /** Bruikbare afmetingen van de laag in cm. */
64
+ layer_length_cm: number;
65
+ layer_width_cm: number;
50
66
  placements: DimensionPlacement[];
67
+ positions: DimensionKegPosition[];
51
68
  }
52
69
  export interface DimensionCart {
53
70
  cart_number: number;
@@ -66,5 +83,13 @@ export interface DimensionPlanResult {
66
83
  /** Gevuld als er niets ingedeeld kan worden; dan is carts leeg. */
67
84
  error?: string;
68
85
  }
86
+ export interface PlacedRect {
87
+ rotated: boolean;
88
+ /** Linkerbovenhoek en werkelijke maten in de gekozen oriëntatie (cm). */
89
+ x: number;
90
+ y: number;
91
+ width: number;
92
+ height: number;
93
+ }
69
94
  export declare function planByDimensions(items: DimensionItem[], carrier: DimensionCarrier): DimensionPlanResult;
70
95
  //# sourceMappingURL=dimensionPlanner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dimensionPlanner.d.ts","sourceRoot":"","sources":["../../src/lib/dimensionPlanner.ts"],"names":[],"mappings":"AAgBA,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,qCAAqC;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,6DAA6D;IAC7D,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,oDAAoD;IACpD,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,kBAAkB,EAAE,CAAC;CAClC;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,cAAc,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA6ED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,aAAa,EAAE,EACtB,OAAO,EAAE,gBAAgB,GACxB,mBAAmB,CAwJrB"}
1
+ {"version":3,"file":"dimensionPlanner.d.ts","sourceRoot":"","sources":["../../src/lib/dimensionPlanner.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,7 @@
13
13
  // Er wordt nooit een afmeting verzonnen: ontbrekende gegevens worden vooraf
14
14
  // gecontroleerd door de aanroeper (zie dimensionData / collectMissingDimensions).
15
15
  // ============================================================================
16
- export const DIMENSION_PLANNER_VERSION = 4;
16
+ export const DIMENSION_PLANNER_VERSION = 5;
17
17
  class LayerSpace {
18
18
  constructor(length, width) {
19
19
  this.length = length;
@@ -22,7 +22,7 @@ class LayerSpace {
22
22
  this.totalDepth = 0;
23
23
  this.usedArea = 0;
24
24
  }
25
- /** Probeert één fust te plaatsen. Geeft de gebruikte rotatie terug, of null. */
25
+ /** Probeert één fust te plaatsen. Geeft de plaatsing terug, of null. */
26
26
  place(l, w) {
27
27
  const options = [
28
28
  { a: l, b: w, rotated: false },
@@ -38,23 +38,26 @@ class LayerSpace {
38
38
  if (!fitsLength)
39
39
  continue;
40
40
  if (o.b <= shelf.depth + 1e-9) {
41
+ const rect = { rotated: o.rotated, x: shelf.usedLength, y: shelf.y, width: o.a, height: o.b };
41
42
  shelf.usedLength += o.a;
42
43
  this.usedArea += o.a * o.b;
43
- return { rotated: o.rotated };
44
+ return rect;
44
45
  }
45
46
  if (last && this.totalDepth - shelf.depth + o.b <= this.width + 1e-9) {
47
+ const rect = { rotated: o.rotated, x: shelf.usedLength, y: shelf.y, width: o.a, height: o.b };
46
48
  this.totalDepth = this.totalDepth - shelf.depth + o.b;
47
49
  shelf.depth = o.b;
48
50
  shelf.usedLength += o.a;
49
51
  this.usedArea += o.a * o.b;
50
- return { rotated: o.rotated };
52
+ return rect;
51
53
  }
52
54
  }
53
55
  if (this.totalDepth + o.b <= this.width + 1e-9) {
54
- this.shelves.push({ depth: o.b, usedLength: o.a });
56
+ const rect = { rotated: o.rotated, x: 0, y: this.totalDepth, width: o.a, height: o.b };
57
+ this.shelves.push({ y: this.totalDepth, depth: o.b, usedLength: o.a });
55
58
  this.totalDepth += o.b;
56
59
  this.usedArea += o.a * o.b;
57
- return { rotated: o.rotated };
60
+ return rect;
58
61
  }
59
62
  }
60
63
  return null;
@@ -127,6 +130,7 @@ export function planByDimensions(items, carrier) {
127
130
  break;
128
131
  const space = new LayerSpace(carrier.length_cm, carrier.width_cm);
129
132
  const perItem = new Map();
133
+ const positions = [];
130
134
  let layerHeight = 0;
131
135
  for (const b of pool) {
132
136
  while (b.left > 0) {
@@ -142,6 +146,20 @@ export function planByDimensions(items, carrier) {
142
146
  const acc = perItem.get(b) ?? { crates: 0, rotated: placed.rotated };
143
147
  acc.crates += 1;
144
148
  perItem.set(b, acc);
149
+ const seq = positions.length + 1;
150
+ positions.push({
151
+ keg_id: `L${layers.length + 1}-${seq}`,
152
+ order_item_id: b.item.order_item_id,
153
+ order_id: b.item.order_id,
154
+ product_id: b.item.product_id,
155
+ label: `${b.item.product_name} — fust ${seq} (laag ${layers.length + 1})` +
156
+ (placed.rotated ? " — gedraaid" : ""),
157
+ x: round1(placed.x),
158
+ y: round1(placed.y),
159
+ width: round1(placed.width),
160
+ height: round1(placed.height),
161
+ rotation: 0,
162
+ });
145
163
  }
146
164
  }
147
165
  if (space.isEmpty || perItem.size === 0)
@@ -169,7 +187,10 @@ export function planByDimensions(items, carrier) {
169
187
  layer_index: layers.length + 1,
170
188
  height_cm: round1(layerHeight),
171
189
  area_used_pct: space.areaPct,
190
+ layer_length_cm: carrier.length_cm,
191
+ layer_width_cm: carrier.width_cm,
172
192
  placements,
193
+ positions,
173
194
  });
174
195
  heightLeft = round1(heightLeft - layerHeight);
175
196
  }
@@ -201,6 +222,16 @@ export function planByDimensions(items, carrier) {
201
222
  carts.forEach((c, idx) => {
202
223
  c.is_complete = idx < carts.length - 1 || c.occupancy_pct >= 99.9;
203
224
  });
225
+ const expectedCrates = batches.reduce((sum, batch) => sum + Math.max(0, Math.round(batch.item.crates)), 0);
226
+ const plannedCrates = carts.reduce((cartSum, cart) => cartSum + cart.layers.reduce((layerSum, layer) => layerSum + layer.positions.length, 0), 0);
227
+ if (plannedCrates !== expectedCrates) {
228
+ return {
229
+ carts: [],
230
+ warnings,
231
+ error: `De fysieke indeling is onvolledig: ${expectedCrates} fusten verwacht, maar ${plannedCrates} ` +
232
+ `fustposities berekend. Er is niets ingedeeld.`,
233
+ };
234
+ }
204
235
  return { carts, warnings };
205
236
  }
206
237
  function allowedOnCart(batch, orders, customers, carrier) {