@flowselections/floriday-verkoop-module 1.0.51 → 1.0.52

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.
@@ -23,7 +23,7 @@ export async function replanScopeInternal(scope) {
23
23
  throw new Error("replanScopeInternal: geen scope");
24
24
  let ordersQuery = supabaseAdmin
25
25
  .from("orders")
26
- .select("id, custom_fields, plan_locked_at")
26
+ .select("id, order_number, custom_fields, plan_locked_at")
27
27
  .is("deleted_at", null);
28
28
  ordersQuery = bufferId
29
29
  ? ordersQuery.eq("buffer_id", bufferId)
@@ -31,6 +31,11 @@ export async function replanScopeInternal(scope) {
31
31
  const { data: orders } = await ordersQuery;
32
32
  const orderRows = (orders ?? []);
33
33
  const orderIds = orderRows.map((o) => o.id);
34
+ const orderNumberById = new Map();
35
+ for (const o of orderRows) {
36
+ if (o.order_number)
37
+ orderNumberById.set(o.id, String(o.order_number));
38
+ }
34
39
  if (orderRows.some((o) => o.plan_locked_at)) {
35
40
  return {
36
41
  ok: true,
@@ -53,26 +58,51 @@ export async function replanScopeInternal(scope) {
53
58
  throw new Error(itErr.message);
54
59
  // Onbekend blijft onbekend: geen ?? 1. De planner blokkeert regels
55
60
  // zonder (volledige) belading in plaats van 1×1×1 te plannen.
56
- const pick = (a, b) => {
57
- for (const v of [a, b]) {
58
- if (v === null || v === undefined)
59
- continue;
60
- const n = Number(v);
61
- if (Number.isFinite(n) && n > 0)
62
- return Math.round(n);
63
- }
64
- return null;
61
+ const pos = (v) => {
62
+ if (v === null || v === undefined)
63
+ return null;
64
+ const n = Number(v);
65
+ return Number.isFinite(n) && n > 0 ? Math.round(n) : null;
66
+ };
67
+ /** Een bron telt alleen als álle drie de velden gevuld zijn (> 0). */
68
+ const completeSource = (plants_per_crate, crates_per_layer, layers_per_cart) => {
69
+ const a = pos(plants_per_crate);
70
+ const b = pos(crates_per_layer);
71
+ const c = pos(layers_per_cart);
72
+ return a && b && c ? { plants_per_crate: a, crates_per_layer: b, layers_per_cart: c } : null;
65
73
  };
74
+ // Beladingbron per order: custom_fields.floriday_packing bevat de belading
75
+ // zoals die in Floriday op de order staat. Dat zijn de packing-VELDEN
76
+ // (pieces_per_package / packages_per_layer / layers_per_load_carrier) —
77
+ // géén voorgekauwde karrenlijst; de karindeling rekenen we zelf.
78
+ const floridayByOrder = new Map();
79
+ for (const o of orderRows) {
80
+ const fp = (o.custom_fields ?? {})?.floriday_packing ?? null;
81
+ if (!fp)
82
+ continue;
83
+ const bel = completeSource(fp.pieces_per_package, fp.packages_per_layer, fp.layers_per_load_carrier);
84
+ if (bel)
85
+ floridayByOrder.set(o.id, bel);
86
+ }
87
+ let usedFloridayPacking = false;
66
88
  const inputs = (items ?? []).map((it) => {
67
89
  const prod = it.products ?? {};
90
+ // Volgorde: floriday_packing → orderregel → product. Niet coalescen per
91
+ // veld: een half-Floriday/half-orderregel-belading is geen belading.
92
+ const fromFloriday = floridayByOrder.get(it.order_id) ?? null;
93
+ const bel = fromFloriday ??
94
+ completeSource(it.plants_per_crate, it.crates_per_layer, it.layers_per_cart) ??
95
+ completeSource(prod.plants_per_crate, prod.crates_per_layer, prod.layers_per_cart);
96
+ if (bel && fromFloriday)
97
+ usedFloridayPacking = true;
68
98
  return {
69
99
  order_item_id: it.id,
70
100
  product_id: it.product_id,
71
101
  product_name: it.product_name,
72
102
  plants: Math.max(0, Number(it.quantity ?? 0)),
73
- plants_per_crate: pick(it.plants_per_crate, prod.plants_per_crate),
74
- crates_per_layer: pick(it.crates_per_layer, prod.crates_per_layer),
75
- layers_per_cart: pick(it.layers_per_cart, prod.layers_per_cart),
103
+ plants_per_crate: bel?.plants_per_crate ?? null,
104
+ crates_per_layer: bel?.crates_per_layer ?? null,
105
+ layers_per_cart: bel?.layers_per_cart ?? null,
76
106
  };
77
107
  });
78
108
  // Item → order-map voor het toewijzen van cart.order_id bij homogene karren.
@@ -87,42 +117,14 @@ export async function replanScopeInternal(scope) {
87
117
  const { error: delErr } = await cartScope(supabaseAdmin.from("order_carts").delete());
88
118
  if (delErr)
89
119
  throw new Error(delErr.message);
90
- // Floriday-packingConfiguration-fallback: alleen als de buffer precies één
91
- // order bevat én die order een volledig Floriday-packingConfiguration heeft.
92
- let usedFloridayPacking = false;
120
+ // De karindeling wordt hier altijd zelf gerekend. custom_fields.floriday_packing
121
+ // levert alleen de belading-VELDEN (pieces_per_package / packages_per_layer /
122
+ // layers_per_load_carrier) — de mirror schrijft nooit een voorgekauwde
123
+ // carts-array, dus die tak bestaat bewust niet meer.
93
124
  let plannedCarts = [];
94
125
  const warnings = [];
95
126
  let blockedItems = [];
96
- const solo = orderRows.length === 1 ? orderRows[0] : null;
97
- const soloCustom = solo?.custom_fields ?? {};
98
- const floridayPacking = soloCustom?.floriday_packing ?? null;
99
- const isFo = solo && (soloCustom?.source === "floriday" || floridayPacking?.pieces_per_package);
100
- if (isFo && floridayPacking && Array.isArray(floridayPacking.carts)) {
101
- usedFloridayPacking = true;
102
- plannedCarts = floridayPacking.carts.map((c, idx) => {
103
- const cItems = (c.items ?? []).map((i) => ({
104
- order_item_id: i.order_item_id,
105
- product_id: i.product_id ?? null,
106
- product_name: i.product_name ?? "—",
107
- crates: Number(i.crates ?? 0),
108
- plants: Number(i.plants ?? Number(i.crates ?? 0) * Number(i.plants_per_crate ?? 1)),
109
- plants_per_crate: Math.max(1, Number(i.plants_per_crate ?? 1)),
110
- crates_per_layer: Math.max(1, Number(i.crates_per_layer ?? 1)),
111
- layers_per_cart: Math.max(1, Number(i.layers_per_cart ?? 1)),
112
- layer_fraction: Number(i.layer_fraction ?? 1),
113
- layer_share_pct: Number(i.layer_share_pct ?? 100),
114
- }));
115
- return {
116
- cart_number: idx + 1,
117
- source: "auto",
118
- is_complete: !!c.is_complete,
119
- occupancy_pct: Number(c.occupancy_pct ?? 100),
120
- total_layers_used: Number(c.total_layers_used ?? 1),
121
- items: cItems,
122
- };
123
- });
124
- }
125
- else {
127
+ {
126
128
  const [{ data: settings }, { data: overrides }] = await Promise.all([
127
129
  supabaseAdmin.from("cart_planning_settings").select("*").eq("id", 1).maybeSingle(),
128
130
  supabaseAdmin.from("cart_planning_product_overrides").select("product_id, never_mix"),
@@ -154,9 +156,25 @@ export async function replanScopeInternal(scope) {
154
156
  missing: b.missing,
155
157
  }));
156
158
  if (blockedOrderIds.length > 0) {
159
+ const FIELD_LABELS = {
160
+ plants_per_crate: "planten per fust",
161
+ crates_per_layer: "fusten per laag",
162
+ layers_per_cart: "lagen per kar",
163
+ };
164
+ const nummers = blockedOrderIds
165
+ .map((id) => orderNumberById.get(id) ?? id.slice(0, 8))
166
+ .join(", ");
167
+ const velden = [
168
+ ...new Set(blockedItems.flatMap((b) => b.missing)),
169
+ ]
170
+ .map((f) => FIELD_LABELS[f] ?? f)
171
+ .join(", ");
172
+ const reason = `Karindeling geblokkeerd voor order(s) ${nummers}: ` +
173
+ `${blockedItems.length} orderregel(s) zonder volledige belading` +
174
+ (velden ? ` (ontbreekt: ${velden})` : "") + ".";
157
175
  const { error: blockErr } = await supabaseAdmin.rpc("logistics_plan_block", {
158
176
  p_order_ids: blockedOrderIds,
159
- p_reason: "belading_onbekend",
177
+ p_reason: reason,
160
178
  p_details: { source: "floriday-verkoop-module/cart-replan", items: blockedItems },
161
179
  });
162
180
  if (blockErr) {
@@ -0,0 +1,10 @@
1
+ type MinimalClient = {
2
+ from: (table: string) => any;
3
+ };
4
+ /**
5
+ * Vertaalt Floriday sales-order-ids naar centrale ordernummers.
6
+ * Eén batch-query per 400 ids; ids zonder maprij ontbreken in de Map.
7
+ */
8
+ export declare function resolveOrderNumbers(supabase: MinimalClient, foIds: Iterable<string>): Promise<Map<string, string>>;
9
+ export {};
10
+ //# sourceMappingURL=order-numbers.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"order-numbers.server.d.ts","sourceRoot":"","sources":["../../src/lib/order-numbers.server.ts"],"names":[],"mappings":"AAKA,KAAK,aAAa,GAAG;IACnB,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,GAAG,CAAC;CAC9B,CAAC;AAIF;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,aAAa,EACvB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GACtB,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAoB9B"}
@@ -0,0 +1,30 @@
1
+ // Eén bron voor het ophalen van centrale ordernummers van Floriday-orders.
2
+ // floriday_order_number_map is leidend; deze helper wordt gebruikt door de
3
+ // lijstquery, het orderdetail én de buffer-siblings, zodat er nooit meer
4
+ // verschillende implementaties naast elkaar bestaan.
5
+ const CHUNK = 400;
6
+ /**
7
+ * Vertaalt Floriday sales-order-ids naar centrale ordernummers.
8
+ * Eén batch-query per 400 ids; ids zonder maprij ontbreken in de Map.
9
+ */
10
+ export async function resolveOrderNumbers(supabase, foIds) {
11
+ const ids = Array.from(new Set(Array.from(foIds).map((v) => String(v ?? "")).filter((v) => v.length > 0)));
12
+ const out = new Map();
13
+ if (ids.length === 0)
14
+ return out;
15
+ for (let i = 0; i < ids.length; i += CHUNK) {
16
+ const chunk = ids.slice(i, i + CHUNK);
17
+ const { data, error } = await supabase
18
+ .from("floriday_order_number_map")
19
+ .select("floriday_id, order_number")
20
+ .in("floriday_id", chunk);
21
+ if (error)
22
+ throw new Error(error.message);
23
+ for (const row of (data ?? [])) {
24
+ const num = String(row?.order_number ?? "").trim();
25
+ if (num)
26
+ out.set(String(row.floriday_id), num);
27
+ }
28
+ }
29
+ return out;
30
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"salesorders-list.server.d.ts","sourceRoot":"","sources":["../../src/lib/salesorders-list.server.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,iBAAiB,EAEvB,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EAAkC,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjF,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,UAAU,CAAC;AAE3C,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,SAAS,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC;IAChC,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB;AA8ID,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,UAAU,CAAC,CAgtBrB"}
1
+ {"version":3,"file":"salesorders-list.server.d.ts","sourceRoot":"","sources":["../../src/lib/salesorders-list.server.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,iBAAiB,EAEvB,MAAM,uBAAuB,CAAC;AAO/B,OAAO,EAAkC,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAGjF,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,UAAU,CAAC;AAE3C,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,GAAG,UAAU,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,SAAS,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,YAAY,EAAE,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,SAAS,GAAG,MAAM,CAAC;IAChC,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;CACpB;AA8ID,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,UAAU,CAAC,CAqsBrB"}
@@ -4,6 +4,7 @@ import { normalizeLoadCarrierCode } from "./load-carriers";
4
4
  import { normalizeTime, resolveDeliveryTimeForDate } from "./carrier-availability";
5
5
  import { expandOrderItemsForPlanner } from "./expand-group-order-items";
6
6
  import { planCarts } from "./cart-planner";
7
+ import { resolveOrderNumbers } from "./order-numbers.server";
7
8
  import { mergeStages, resolveOrderStage } from "./order-status";
8
9
  const FO_CAP = 20000;
9
10
  const LOCAL_CAP = 5000;
@@ -261,15 +262,7 @@ export async function buildSalesOrdersList(supabase, params) {
261
262
  }
262
263
  };
263
264
  const [orderNumberRows, customerRows, floridayCustomerRows, tradeItemRows, carrierRows, crateDescriptions, groupPacking,] = await Promise.all([
264
- safe("Ordernummers", () => chunked(foIdsForNumbers, 400, async (chunk) => {
265
- const { data, error } = await supabase
266
- .from("floriday_order_number_map")
267
- .select("floriday_id, order_number")
268
- .in("floriday_id", chunk);
269
- if (error)
270
- throw new Error(error.message);
271
- return (data ?? []);
272
- }), []),
265
+ safe("Ordernummers", () => resolveOrderNumbers(supabase, foIdsForNumbers), new Map()),
273
266
  safe("Klanten", async () => {
274
267
  const orFilter = [
275
268
  neededOrgIds.length ? `floriday_organization_id.in.(${neededOrgIds.join(",")})` : null,
@@ -318,10 +311,7 @@ export async function buildSalesOrdersList(supabase, params) {
318
311
  loadCrateDescriptions(supabase, warnings),
319
312
  safe("Productgroepen", () => loadGroupPacking(supabase), new Map()),
320
313
  ]);
321
- const orderNumberByFoId = new Map();
322
- for (const m of orderNumberRows) {
323
- orderNumberByFoId.set(String(m.floriday_id), String(m.order_number ?? ""));
324
- }
314
+ const orderNumberByFoId = orderNumberRows;
325
315
  const carrierById = new Map();
326
316
  for (const c of carrierRows)
327
317
  carrierById.set(String(c.id), c);