@flowselections/floriday-verkoop-module 1.0.21 → 1.0.25

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/dist-lib/components/karindeling/AfmetingenTab.d.ts +6 -0
  2. package/dist-lib/components/karindeling/AfmetingenTab.d.ts.map +1 -0
  3. package/dist-lib/components/karindeling/AfmetingenTab.js +128 -0
  4. package/dist-lib/components/karindeling/KarindelingPage.d.ts +2 -0
  5. package/dist-lib/components/karindeling/KarindelingPage.d.ts.map +1 -0
  6. package/dist-lib/components/karindeling/KarindelingPage.js +102 -0
  7. package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts.map +1 -1
  8. package/dist-lib/components/verkoop/OrderKarindelingCard.js +83 -37
  9. package/dist-lib/components/verkoop/SalesOrderDetailPage.d.ts.map +1 -1
  10. package/dist-lib/components/verkoop/SalesOrderDetailPage.js +59 -8
  11. package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts +2 -1
  12. package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts.map +1 -1
  13. package/dist-lib/components/verkoop/SalesOrderDetailView.js +2 -2
  14. package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
  15. package/dist-lib/components/verkoop/SalesOrdersTable.js +36 -4
  16. package/dist-lib/components/verkoop/VerkoopOrdersPage.d.ts.map +1 -1
  17. package/dist-lib/components/verkoop/VerkoopOrdersPage.js +18 -2
  18. package/dist-lib/index.d.ts +1 -0
  19. package/dist-lib/index.d.ts.map +1 -1
  20. package/dist-lib/index.js +12 -1
  21. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2353 -374
  22. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  23. package/dist-lib/integrations/supabase/client.d.ts +2353 -374
  24. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  25. package/dist-lib/integrations/supabase/client.js +24 -9
  26. package/dist-lib/integrations/supabase/client.server.d.ts +2353 -374
  27. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  28. package/dist-lib/integrations/supabase/types.d.ts +2406 -376
  29. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  30. package/dist-lib/integrations/supabase/types.js +11 -9
  31. package/dist-lib/lib/cart-lock.functions.d.ts +2353 -374
  32. package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -1
  33. package/dist-lib/lib/cart-planner.d.ts +9 -1
  34. package/dist-lib/lib/cart-planner.d.ts.map +1 -1
  35. package/dist-lib/lib/cart-planner.js +21 -4
  36. package/dist-lib/lib/cart-planning-settings.functions.d.ts +152282 -0
  37. package/dist-lib/lib/cart-planning-settings.functions.d.ts.map +1 -0
  38. package/dist-lib/lib/cart-planning-settings.functions.js +375 -0
  39. package/dist-lib/lib/cart-replan.functions.d.ts +18468 -651
  40. package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -1
  41. package/dist-lib/lib/cart-replan.functions.js +85 -27
  42. package/dist-lib/lib/create-order.functions.d.ts +25367 -1619
  43. package/dist-lib/lib/create-order.functions.d.ts.map +1 -1
  44. package/dist-lib/lib/cron/index.d.ts +5 -0
  45. package/dist-lib/lib/cron/index.d.ts.map +1 -0
  46. package/dist-lib/lib/cron/index.js +4 -0
  47. package/dist-lib/lib/cron/mirror-floriday-orders.d.ts +4 -0
  48. package/dist-lib/lib/cron/mirror-floriday-orders.d.ts.map +1 -0
  49. package/dist-lib/lib/cron/mirror-floriday-orders.js +23 -0
  50. package/dist-lib/lib/cron/replan-carts.d.ts +4 -0
  51. package/dist-lib/lib/cron/replan-carts.d.ts.map +1 -0
  52. package/dist-lib/lib/cron/replan-carts.js +18 -0
  53. package/dist-lib/lib/cron/sync-salesorders.d.ts +4 -0
  54. package/dist-lib/lib/cron/sync-salesorders.d.ts.map +1 -0
  55. package/dist-lib/lib/cron/sync-salesorders.js +18 -0
  56. package/dist-lib/lib/cron/sync-tradeitems.d.ts +4 -0
  57. package/dist-lib/lib/cron/sync-tradeitems.d.ts.map +1 -0
  58. package/dist-lib/lib/cron/sync-tradeitems.js +18 -0
  59. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +6539 -602
  60. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -1
  61. package/dist-lib/lib/floriday-auto-fulfillment.server.js +1 -1
  62. package/dist-lib/lib/floriday-central-token.server.d.ts +12 -0
  63. package/dist-lib/lib/floriday-central-token.server.d.ts.map +1 -0
  64. package/dist-lib/lib/floriday-central-token.server.js +58 -0
  65. package/dist-lib/lib/floriday-gateway.server.d.ts +2 -2
  66. package/dist-lib/lib/floriday-gateway.server.d.ts.map +1 -1
  67. package/dist-lib/lib/floriday-gateway.server.js +10 -37
  68. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +2353 -374
  69. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -1
  70. package/dist-lib/lib/floriday-logistic-labels.functions.js +14 -4
  71. package/dist-lib/lib/floriday-mirror.functions.d.ts +13846 -0
  72. package/dist-lib/lib/floriday-mirror.functions.d.ts.map +1 -0
  73. package/dist-lib/lib/floriday-mirror.functions.js +278 -0
  74. package/dist-lib/lib/floriday-public.functions.d.ts +14907 -1054
  75. package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
  76. package/dist-lib/lib/floriday-sync.d.ts +0 -1
  77. package/dist-lib/lib/floriday-sync.d.ts.map +1 -1
  78. package/dist-lib/lib/floriday-sync.js +1 -1
  79. package/dist-lib/lib/floriday-writes.functions.d.ts +10723 -828
  80. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  81. package/dist-lib/lib/order-buffers.functions.d.ts +41540 -0
  82. package/dist-lib/lib/order-buffers.functions.d.ts.map +1 -0
  83. package/dist-lib/lib/order-buffers.functions.js +244 -0
  84. package/dist-lib/lib/order-shipments.functions.d.ts +8631 -715
  85. package/dist-lib/lib/order-shipments.functions.d.ts.map +1 -1
  86. package/dist-lib/lib/salesorders-mapping.d.ts +3 -0
  87. package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -1
  88. package/dist-lib/lib/salesorders.functions.d.ts +10725 -828
  89. package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
  90. package/dist-lib/lib/salesorders.functions.js +129 -3
  91. package/dist-lib/lib/tradeitems.functions.d.ts +2353 -374
  92. package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
  93. package/dist-lib/lib/useSalesOrdersQuery.d.ts.map +1 -1
  94. package/dist-lib/lib/useSalesOrdersQuery.js +37 -2
  95. package/package.json +27 -5
@@ -0,0 +1,375 @@
1
+ // Server-fns voor de Karindeling-instellingen module.
2
+ import { createServerFn } from "@tanstack/react-start";
3
+ import { z } from "zod";
4
+ import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
5
+ export const getCartPlanningBundle = createServerFn({ method: "GET" })
6
+ .middleware([requireSupabaseAuth])
7
+ .handler(async ({ context }) => {
8
+ const supabase = context.supabase;
9
+ const [{ data: s }, { data: pt }, { data: ct }, { data: cr }] = await Promise.all([
10
+ supabase.from("cart_planning_settings").select("*").eq("id", 1).maybeSingle(),
11
+ supabase.from("cart_planning_pick_times").select("*").order("pot_size"),
12
+ supabase.from("cart_types").select("*").order("name"),
13
+ supabase.from("crate_types").select("*").order("name"),
14
+ ]);
15
+ return {
16
+ settings: (s ?? {
17
+ id: 1,
18
+ max_party_share_per_layer_pct: 50,
19
+ max_beladingen_per_layer: 2,
20
+ optimization_strategy: "min_carts",
21
+ allow_editing_after_pickstation_send: false,
22
+ combine_floriday_orders: false,
23
+ combine_floriday_orders_window: "same_day",
24
+ combine_orders_scope: "same_customer",
25
+ own_cart_from_layers: 3,
26
+ max_customers_per_cart: 5,
27
+ max_one_customer_per_layer: false,
28
+ require_same_carrier: false,
29
+ require_same_delivery_location: true,
30
+ require_same_crate_type: false,
31
+ require_same_delivery_date: true,
32
+ default_workers: 1,
33
+ pick_time_mode: "per_pot_size",
34
+ plants_per_hour: 0,
35
+ plants_per_day: 0,
36
+ buffer_grouping_mode: "same_pickup_and_location",
37
+ }),
38
+ pickTimes: (pt ?? []),
39
+ cartTypes: (ct ?? []),
40
+ crateTypes: (cr ?? []),
41
+ };
42
+ });
43
+ const settingsSchema = z.object({
44
+ max_party_share_per_layer_pct: z.number().int().min(1).max(100),
45
+ max_beladingen_per_layer: z.number().int().min(1).max(999),
46
+ optimization_strategy: z.enum(["min_carts", "min_mixing"]),
47
+ allow_editing_after_pickstation_send: z.boolean(),
48
+ combine_floriday_orders: z.boolean(),
49
+ combine_floriday_orders_window: z.enum(["same_day", "same_delivery_slot", "same_week"]),
50
+ combine_orders_scope: z.enum(["same_customer", "same_delivery_location", "never"]),
51
+ own_cart_from_layers: z.number().int().min(1).max(100),
52
+ max_customers_per_cart: z.number().int().min(1).max(100),
53
+ max_one_customer_per_layer: z.boolean(),
54
+ require_same_carrier: z.boolean(),
55
+ require_same_delivery_location: z.boolean(),
56
+ require_same_crate_type: z.boolean(),
57
+ require_same_delivery_date: z.boolean(),
58
+ default_workers: z.number().int().min(1).max(1000),
59
+ pick_time_mode: z.enum(["per_pot_size", "plants_per_hour", "plants_per_day"]),
60
+ plants_per_hour: z.number().int().min(0).max(1000000),
61
+ plants_per_day: z.number().int().min(0).max(10000000),
62
+ buffer_grouping_mode: z.enum([
63
+ "same_pickup_and_location",
64
+ "same_pickup",
65
+ "same_location",
66
+ "same_customer",
67
+ "never",
68
+ ]),
69
+ });
70
+ export const updateCartPlanningSettings = createServerFn({ method: "POST" })
71
+ .middleware([requireSupabaseAuth])
72
+ .inputValidator((input) => settingsSchema.parse(input))
73
+ .handler(async ({ data, context }) => {
74
+ const supabase = context.supabase;
75
+ const { error } = await supabase
76
+ .from("cart_planning_settings")
77
+ .update({ ...data, updated_by: context.userId })
78
+ .eq("id", 1);
79
+ if (error)
80
+ throw new Error(error.message);
81
+ return { ok: true };
82
+ });
83
+ export const upsertPickTime = createServerFn({ method: "POST" })
84
+ .middleware([requireSupabaseAuth])
85
+ .inputValidator((input) => z
86
+ .object({
87
+ id: z.string().uuid().optional(),
88
+ pot_size: z.string().min(1).max(50),
89
+ seconds_per_cart: z.number().int().min(0).max(100000),
90
+ })
91
+ .parse(input))
92
+ .handler(async ({ data, context }) => {
93
+ const supabase = context.supabase;
94
+ const { error } = await supabase
95
+ .from("cart_planning_pick_times")
96
+ .upsert({ id: data.id, pot_size: data.pot_size, seconds_per_cart: data.seconds_per_cart }, { onConflict: "pot_size" });
97
+ if (error)
98
+ throw new Error(error.message);
99
+ return { ok: true };
100
+ });
101
+ export const deletePickTime = createServerFn({ method: "POST" })
102
+ .middleware([requireSupabaseAuth])
103
+ .inputValidator((input) => z.object({ id: z.string().uuid() }).parse(input))
104
+ .handler(async ({ data, context }) => {
105
+ const supabase = context.supabase;
106
+ const { error } = await supabase.from("cart_planning_pick_times").delete().eq("id", data.id);
107
+ if (error)
108
+ throw new Error(error.message);
109
+ return { ok: true };
110
+ });
111
+ export const listUsedCratePackages = createServerFn({ method: "GET" })
112
+ .middleware([requireSupabaseAuth])
113
+ .handler(async ({ context }) => {
114
+ const supabase = context.supabase;
115
+ const [{ data: tradeItems, error: e1 }, { data: carriers, error: e2 }] = await Promise.all([
116
+ supabase.from("floriday_trade_items_cache").select("data").limit(5000),
117
+ supabase.from("floricode_records").select("data").eq("resource", "LoadCarrier"),
118
+ ]);
119
+ if (e1)
120
+ throw new Error(e1.message);
121
+ if (e2)
122
+ throw new Error(e2.message);
123
+ const carrierByCode = new Map();
124
+ const mm2cm = (v) => (v == null ? null : Math.round(Number(v) / 10));
125
+ for (const row of (carriers ?? [])) {
126
+ const d = row.data;
127
+ if (!d?.code)
128
+ continue;
129
+ carrierByCode.set(String(d.code), {
130
+ description: d.description ?? null,
131
+ length_cm: mm2cm(d.length),
132
+ width_cm: mm2cm(d.width),
133
+ height_cm: mm2cm(d.height),
134
+ });
135
+ }
136
+ const usage = new Map();
137
+ for (const row of (tradeItems ?? [])) {
138
+ const cfgs = row?.data?.packingConfigurations;
139
+ if (!Array.isArray(cfgs))
140
+ continue;
141
+ for (const c of cfgs) {
142
+ const code = c?.package?.vbnPackageCode ?? c?.package?.customPackageId;
143
+ const pieces = Number(c?.piecesPerPackage ?? 0);
144
+ if (!code)
145
+ continue;
146
+ const key = `${code}-${pieces}`;
147
+ const e = usage.get(key);
148
+ if (e)
149
+ e.count += 1;
150
+ else
151
+ usage.set(key, { code: String(code), pieces, count: 1 });
152
+ }
153
+ }
154
+ const seenCodes = new Set();
155
+ const result = [];
156
+ for (const [code, meta] of carrierByCode.entries()) {
157
+ const variants = Array.from(usage.values()).filter((u) => u.code === code);
158
+ const total = variants.reduce((s, v) => s + v.count, 0);
159
+ const bestPieces = variants.sort((a, b) => b.count - a.count)[0]?.pieces || null;
160
+ const dims = meta.length_cm && meta.width_cm ? `${meta.length_cm}×${meta.width_cm} cm` : null;
161
+ const parts = [
162
+ `VBN ${code}`,
163
+ meta.description,
164
+ dims,
165
+ bestPieces ? `${bestPieces} st/fust` : null,
166
+ ].filter(Boolean);
167
+ result.push({
168
+ key: `${code}-${bestPieces ?? 0}`,
169
+ vbn_package_code: code,
170
+ description: meta.description,
171
+ length_cm: meta.length_cm,
172
+ width_cm: meta.width_cm,
173
+ height_cm: meta.height_cm,
174
+ pieces_per_package: bestPieces,
175
+ usage_count: total,
176
+ label: parts.join(" · "),
177
+ });
178
+ seenCodes.add(code);
179
+ }
180
+ for (const v of usage.values()) {
181
+ if (seenCodes.has(v.code))
182
+ continue;
183
+ result.push({
184
+ key: `${v.code}-${v.pieces}`,
185
+ vbn_package_code: v.code,
186
+ description: null,
187
+ length_cm: null,
188
+ width_cm: null,
189
+ height_cm: null,
190
+ pieces_per_package: v.pieces || null,
191
+ usage_count: v.count,
192
+ label: `VBN ${v.code}${v.pieces ? ` · ${v.pieces} st/fust` : ""}`,
193
+ });
194
+ }
195
+ return result.sort((a, b) => {
196
+ if (b.usage_count !== a.usage_count)
197
+ return b.usage_count - a.usage_count;
198
+ return a.vbn_package_code.localeCompare(b.vbn_package_code);
199
+ });
200
+ });
201
+ // Standaard afmetingen voor bekende Floriday load-carrier types (cm).
202
+ const CARRIER_METADATA = {
203
+ DANISH_TROLLEY: { name: "Deense container", length: 135, width: 56, layers: 4 },
204
+ DANISH_TROLLEY_NON_CC_RFID: { name: "Deense container (non-CC RFID)", length: 135, width: 56, layers: 4 },
205
+ DANISH_TROLLEY_WATER_TRAY: { name: "Deense container met waterschaal", length: 135, width: 56, layers: 4 },
206
+ ONE_THIRD_DANISH_TROLLEY: { name: "1/3 Deense container", length: 45, width: 56, layers: 4 },
207
+ AUCTION_TROLLEY: { name: "Veilingstapelwagen", length: 125, width: 56, layers: 5 },
208
+ EURO_TROLLEY: { name: "Euro-stapelwagen", length: 135, width: 56, layers: 4 },
209
+ EURO_PALLET: { name: "Europallet", length: 120, width: 80, layers: 1 },
210
+ HT_EURO_PALLET: { name: "HT Europallet", length: 120, width: 80, layers: 1 },
211
+ PALLET: { name: "Pallet", length: 120, width: 100, layers: 1 },
212
+ BLOCK_PALLET: { name: "Blokpallet", length: 120, width: 100, layers: 1 },
213
+ CHEP_PALLET_BLUE: { name: "CHEP-pallet (blauw)", length: 120, width: 100, layers: 1 },
214
+ CHEP_PALLET_RED: { name: "CHEP-pallet (rood)", length: 120, width: 80, layers: 1 },
215
+ IFCO_PALLET: { name: "IFCO-pallet", length: 120, width: 80, layers: 1 },
216
+ WATERING_CONTAINER: { name: "Watergeef-container", length: 120, width: 80, layers: 1 },
217
+ NONE: { name: "Geen", length: null, width: null, layers: null },
218
+ };
219
+ export const listUsedCartCarriers = createServerFn({ method: "GET" })
220
+ .middleware([requireSupabaseAuth])
221
+ .handler(async ({ context }) => {
222
+ const supabase = context.supabase;
223
+ const [{ data: tradeItems, error: e1 }, { data: codeList, error: e2 }] = await Promise.all([
224
+ supabase.from("floriday_trade_items_cache").select("data").limit(5000),
225
+ supabase.from("floriday_code_lists_cache").select("code").eq("list_name", "LoadCarrierType"),
226
+ ]);
227
+ if (e1)
228
+ throw new Error(e1.message);
229
+ if (e2)
230
+ throw new Error(e2.message);
231
+ // Tel gebruik + verzamel meest voorkomende layers per carrier.
232
+ const usage = new Map();
233
+ for (const row of (tradeItems ?? [])) {
234
+ const cfgs = row?.data?.packingConfigurations;
235
+ if (!Array.isArray(cfgs))
236
+ continue;
237
+ for (const c of cfgs) {
238
+ const code = c?.loadCarrier;
239
+ if (!code)
240
+ continue;
241
+ const layers = Number(c?.layersPerLoadCarrier ?? 0);
242
+ const entry = usage.get(code) ?? { count: 0, layersHist: new Map() };
243
+ entry.count += 1;
244
+ if (layers > 0)
245
+ entry.layersHist.set(layers, (entry.layersHist.get(layers) ?? 0) + 1);
246
+ usage.set(code, entry);
247
+ }
248
+ }
249
+ // Verzamel alle bekende codes (uit code-list + gebruik).
250
+ const allCodes = new Set([
251
+ ...Object.keys(CARRIER_METADATA),
252
+ ...(codeList ?? []).map((r) => r.code),
253
+ ...usage.keys(),
254
+ ]);
255
+ const result = [];
256
+ for (const code of allCodes) {
257
+ const meta = CARRIER_METADATA[code] ?? { name: code, length: null, width: null, layers: null };
258
+ const u = usage.get(code);
259
+ const mostLayers = u
260
+ ? Array.from(u.layersHist.entries()).sort((a, b) => b[1] - a[1])[0]?.[0] ?? meta.layers
261
+ : meta.layers;
262
+ const dims = meta.length && meta.width ? `${meta.length}×${meta.width} cm` : null;
263
+ const parts = [meta.name, dims, mostLayers ? `${mostLayers} lagen` : null].filter(Boolean);
264
+ result.push({
265
+ key: code,
266
+ code,
267
+ name: meta.name,
268
+ layer_length_cm: meta.length,
269
+ layer_width_cm: meta.width,
270
+ layers_per_cart: mostLayers ?? null,
271
+ usage_count: u?.count ?? 0,
272
+ label: parts.join(" · "),
273
+ });
274
+ }
275
+ return result.sort((a, b) => {
276
+ if (b.usage_count !== a.usage_count)
277
+ return b.usage_count - a.usage_count;
278
+ return a.name.localeCompare(b.name);
279
+ });
280
+ });
281
+ // ─── Cart-types (soorten karren / containers) ────────────────────────────────
282
+ const cartTypeSchema = z.object({
283
+ id: z.string().uuid().optional(),
284
+ name: z.string().min(1).max(100),
285
+ layer_length_cm: z.number().int().min(1).max(10000),
286
+ layer_width_cm: z.number().int().min(1).max(10000),
287
+ layers_per_cart: z.number().int().min(1).max(20),
288
+ is_default: z.boolean().optional(),
289
+ });
290
+ export const upsertCartType = createServerFn({ method: "POST" })
291
+ .middleware([requireSupabaseAuth])
292
+ .inputValidator((input) => cartTypeSchema.parse(input))
293
+ .handler(async ({ data, context }) => {
294
+ const supabase = context.supabase;
295
+ const row = {
296
+ name: data.name,
297
+ layer_length_cm: data.layer_length_cm,
298
+ layer_width_cm: data.layer_width_cm,
299
+ layers_per_cart: data.layers_per_cart,
300
+ };
301
+ if (data.id)
302
+ row.id = data.id;
303
+ if (data.is_default !== undefined)
304
+ row.is_default = data.is_default;
305
+ const { error } = await supabase.from("cart_types").upsert(row, { onConflict: "id" });
306
+ if (error)
307
+ throw new Error(error.message);
308
+ return { ok: true };
309
+ });
310
+ export const deleteCartType = createServerFn({ method: "POST" })
311
+ .middleware([requireSupabaseAuth])
312
+ .inputValidator((input) => z.object({ id: z.string().uuid() }).parse(input))
313
+ .handler(async ({ data, context }) => {
314
+ const supabase = context.supabase;
315
+ const { error } = await supabase.from("cart_types").delete().eq("id", data.id);
316
+ if (error)
317
+ throw new Error(error.message);
318
+ return { ok: true };
319
+ });
320
+ export const setDefaultCartType = createServerFn({ method: "POST" })
321
+ .middleware([requireSupabaseAuth])
322
+ .inputValidator((input) => z.object({ id: z.string().uuid() }).parse(input))
323
+ .handler(async ({ data, context }) => {
324
+ const supabase = context.supabase;
325
+ // Unique partial index (is_default) enforces at most one default; unset others first.
326
+ const { error: e1 } = await supabase
327
+ .from("cart_types")
328
+ .update({ is_default: false })
329
+ .neq("id", data.id);
330
+ if (e1)
331
+ throw new Error(e1.message);
332
+ const { error: e2 } = await supabase
333
+ .from("cart_types")
334
+ .update({ is_default: true })
335
+ .eq("id", data.id);
336
+ if (e2)
337
+ throw new Error(e2.message);
338
+ return { ok: true };
339
+ });
340
+ // ─── Crate-types (soorten fusten) ────────────────────────────────────────────
341
+ const crateTypeSchema = z.object({
342
+ id: z.string().uuid().optional(),
343
+ name: z.string().min(1).max(100),
344
+ length_cm: z.number().int().min(1).max(10000),
345
+ width_cm: z.number().int().min(1).max(10000),
346
+ plants_per_crate: z.number().int().min(1).max(10000),
347
+ });
348
+ export const upsertCrateType = createServerFn({ method: "POST" })
349
+ .middleware([requireSupabaseAuth])
350
+ .inputValidator((input) => crateTypeSchema.parse(input))
351
+ .handler(async ({ data, context }) => {
352
+ const supabase = context.supabase;
353
+ const row = {
354
+ name: data.name,
355
+ length_cm: data.length_cm,
356
+ width_cm: data.width_cm,
357
+ plants_per_crate: data.plants_per_crate,
358
+ };
359
+ if (data.id)
360
+ row.id = data.id;
361
+ const { error } = await supabase.from("crate_types").upsert(row, { onConflict: "id" });
362
+ if (error)
363
+ throw new Error(error.message);
364
+ return { ok: true };
365
+ });
366
+ export const deleteCrateType = createServerFn({ method: "POST" })
367
+ .middleware([requireSupabaseAuth])
368
+ .inputValidator((input) => z.object({ id: z.string().uuid() }).parse(input))
369
+ .handler(async ({ data, context }) => {
370
+ const supabase = context.supabase;
371
+ const { error } = await supabase.from("crate_types").delete().eq("id", data.id);
372
+ if (error)
373
+ throw new Error(error.message);
374
+ return { ok: true };
375
+ });