@flowselections/floriday-verkoop-module 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.
@@ -16,30 +16,76 @@ export const listCustomersForCreateOrder = createServerFn({ method: "GET" })
16
16
  .range(0, 4999);
17
17
  return { items: data ?? [] };
18
18
  });
19
- // ─── Producten (alleen met voorraad — zelfde filter als Mailbox) ────────────
19
+ // ─── Producten (uit voorraadmodule: alle actieve Floriday trade items) ─────
20
+ // Bron = zelfde data als de Voorraad-pagina: floriday_trade_items_cache
21
+ // gejoined met trade_item_stock. Items zonder voorraad worden ook getoond
22
+ // (grijs in de UI, met melding "niet op voorraad"), zodat gebruikers ook
23
+ // vooruit kunnen bestellen op verwachte voorraad.
20
24
  export const listProductsForCreateOrder = createServerFn({ method: "GET" })
21
25
  .middleware([requireSupabaseAuth])
22
26
  .handler(async ({ context }) => {
23
- const { data: stockRows } = await context.supabase
24
- .from("trade_item_stock")
25
- .select("trade_item_key, physical_stock, reserved_stock");
26
- const availableKeys = new Set();
27
+ const [tradeItemsRes, stockRes] = await Promise.all([
28
+ context.supabase
29
+ .from("floriday_trade_items_cache")
30
+ .select("floriday_id, data")
31
+ .eq("is_deleted", false)
32
+ .range(0, 4999),
33
+ context.supabase
34
+ .from("trade_item_stock")
35
+ .select("trade_item_key, physical_stock, reserved_stock, incoming_stock"),
36
+ ]);
37
+ const tradeItems = tradeItemsRes.data;
38
+ const stockRows = stockRes.data;
39
+ const stockByKey = new Map();
27
40
  for (const s of Array.isArray(stockRows) ? stockRows : []) {
28
- const avail = Number(s.physical_stock ?? 0) - Number(s.reserved_stock ?? 0);
29
- if (avail > 0 && typeof s.trade_item_key === "string") {
30
- availableKeys.add(s.trade_item_key);
31
- }
41
+ const k = s?.trade_item_key;
42
+ if (typeof k !== "string")
43
+ continue;
44
+ stockByKey.set(k, {
45
+ physical: Number(s.physical_stock ?? 0),
46
+ reserved: Number(s.reserved_stock ?? 0),
47
+ incoming: Number(s.incoming_stock ?? 0),
48
+ });
32
49
  }
33
- if (availableKeys.size === 0)
34
- return { items: [] };
35
- const { data } = await context.supabase
36
- .from("products")
37
- .select("id, product, unit, sale_price, plants_per_crate, crates_per_layer, layers_per_cart, custom_fields")
38
- .order("product")
39
- .range(0, 4999);
40
- const items = (Array.isArray(data) ? data : []).filter((p) => {
41
- const fid = p?.custom_fields?.floriday_id;
42
- return typeof fid === "string" && availableKeys.has(fid);
50
+ const pickName = (n) => {
51
+ if (!n)
52
+ return "";
53
+ if (typeof n === "string")
54
+ return n;
55
+ return String(n.nl ?? n.en ?? n.de ?? Object.values(n)[0] ?? "");
56
+ };
57
+ const items = (Array.isArray(tradeItems) ? tradeItems : [])
58
+ .map((r) => {
59
+ const d = r?.data ?? {};
60
+ const name = pickName(d.tradeItemName) || d.tradeItemReference || "(naamloos)";
61
+ const packs = Array.isArray(d.packingConfigurations) ? d.packingConfigurations : [];
62
+ const primary = packs.find((p) => p?.primary) ?? packs[0] ?? {};
63
+ const ppc = Math.max(1, Number(primary?.piecesPerPackage ?? 1));
64
+ const cpl = Math.max(1, Number(primary?.packagesPerLayer ?? 1));
65
+ const lpc = Math.max(1, Number(primary?.layersPerLoadCarrier ?? 1));
66
+ const stock = stockByKey.get(r.floriday_id);
67
+ const physical = stock?.physical ?? 0;
68
+ const reserved = stock?.reserved ?? 0;
69
+ const available = Math.max(0, physical - reserved);
70
+ return {
71
+ id: r.floriday_id,
72
+ product: name,
73
+ article_code: d.supplierArticleCode ?? null,
74
+ unit: "stuks",
75
+ sale_price: null,
76
+ plants_per_crate: ppc,
77
+ crates_per_layer: cpl,
78
+ layers_per_cart: lpc,
79
+ physical_stock: physical,
80
+ reserved_stock: reserved,
81
+ available_stock: available,
82
+ in_stock: available > 0,
83
+ };
84
+ })
85
+ .sort((a, b) => {
86
+ if (a.in_stock !== b.in_stock)
87
+ return a.in_stock ? -1 : 1;
88
+ return a.product.localeCompare(b.product, "nl");
43
89
  });
44
90
  return { items };
45
91
  });
@@ -114,6 +160,11 @@ const LineInput = z.object({
114
160
  quantity: z.number().int().positive(), // aantal PLANTEN (== fusten * plants_per_crate)
115
161
  unit: z.string().max(50).nullable().optional(),
116
162
  unit_price: z.number().nonnegative().optional(),
163
+ // Packing-info uit voorraadmodule; nodig om ontbrekende products-rij aan te maken
164
+ plants_per_crate: z.number().int().positive().optional(),
165
+ crates_per_layer: z.number().int().positive().optional(),
166
+ layers_per_cart: z.number().int().positive().optional(),
167
+ article_code: z.string().max(100).nullable().optional(),
117
168
  });
118
169
  const CreateManualOrderInput = z.object({
119
170
  customerId: z.string().uuid(),
@@ -152,6 +203,38 @@ export const createManualOrder = createServerFn({ method: "POST" })
152
203
  throw new Error(`Leverdatum (${data.deliveryDate}) ligt voor de eerst mogelijke datum (${earliestISO}) o.b.v. orderpicktijd (${pickDays}d) + levertijd vervoerder (${carrierDeliveryDays}d).`);
153
204
  }
154
205
  const { supabaseAdmin } = await import("../integrations/supabase/client.server");
206
+ // Zorg dat elk product_id (= trade_item_key uit voorraad) een rij in
207
+ // `products` heeft — de order_items FK vereist dit én de karindeling
208
+ // (planAndSaveAutoCarts) joined op products voor de packing-waarden.
209
+ const productIds = Array.from(new Set(data.lines.map((l) => l.product_id)));
210
+ const { data: existingProducts } = await supabaseAdmin
211
+ .from("products").select("id").in("id", productIds);
212
+ const existingIds = new Set((existingProducts ?? []).map((p) => p.id));
213
+ const missingLines = data.lines.filter((l) => !existingIds.has(l.product_id));
214
+ if (missingLines.length > 0) {
215
+ const seen = new Set();
216
+ const toInsert = missingLines.filter((l) => {
217
+ if (seen.has(l.product_id))
218
+ return false;
219
+ seen.add(l.product_id);
220
+ return true;
221
+ }).map((l) => ({
222
+ id: l.product_id,
223
+ product: l.product_name,
224
+ batch: "",
225
+ location: "",
226
+ unit: l.unit ?? "stuks",
227
+ plants_per_crate: Math.max(1, Number(l.plants_per_crate ?? 1)),
228
+ crates_per_layer: Math.max(1, Number(l.crates_per_layer ?? 1)),
229
+ layers_per_cart: Math.max(1, Number(l.layers_per_cart ?? 1)),
230
+ sale_price: l.unit_price ?? null,
231
+ trade_item_key: l.product_id,
232
+ custom_fields: { floriday_id: l.product_id, source: "auto-from-voorraad" },
233
+ }));
234
+ const { error: prodErr } = await supabaseAdmin.from("products").insert(toInsert);
235
+ if (prodErr)
236
+ throw new Error(`Product aanmaken faalde: ${prodErr.message}`);
237
+ }
155
238
  const total = data.lines.reduce((s, l) => s + (Number(l.unit_price ?? 0) * Number(l.quantity)), 0);
156
239
  const { data: order, error: orderErr } = await supabaseAdmin.from("orders").insert({
157
240
  customer_name: data.customerName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowselections/floriday-verkoop-module",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "private": false,
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -29,7 +29,7 @@
29
29
  "lint": "eslint ."
30
30
  },
31
31
  "peerDependencies": {
32
- "@flowselections/core": "^1.0.11",
32
+ "@flowselections/core": "1.0.12",
33
33
  "@tanstack/react-query": "^5.0.0",
34
34
  "@tanstack/react-router": "^1.0.0",
35
35
  "@tanstack/react-start": "^1.0.0",
@@ -46,7 +46,7 @@
46
46
  "devDependencies": {
47
47
  "@cloudflare/vite-plugin": "^1.25.5",
48
48
  "@eslint/js": "^9.32.0",
49
- "@flowselections/core": "^1.0.11",
49
+ "@flowselections/core": "1.0.12",
50
50
  "@lovable.dev/vite-tanstack-config": "2.6.4",
51
51
  "@rsbuild/core": "^1.0.0",
52
52
  "@tailwindcss/vite": "^4.2.1",