@flowselections/floriday-verkoop-module 1.0.11 → 1.0.13
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.
- package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.d.ts +4 -0
- package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.d.ts.map +1 -0
- package/dist-lib/components/verkoop/AutoFulfillmentStatusCard.js +95 -0
- package/dist-lib/components/verkoop/CartAssignmentWizard.d.ts +6 -0
- package/dist-lib/components/verkoop/CartAssignmentWizard.d.ts.map +1 -0
- package/dist-lib/components/verkoop/CartAssignmentWizard.js +194 -0
- package/dist-lib/components/verkoop/CreateOrderPage.d.ts.map +1 -1
- package/dist-lib/components/verkoop/CreateOrderPage.js +230 -192
- package/dist-lib/components/verkoop/OrderAfleverbrievenCard.d.ts +4 -0
- package/dist-lib/components/verkoop/OrderAfleverbrievenCard.d.ts.map +1 -0
- package/dist-lib/components/verkoop/OrderAfleverbrievenCard.js +44 -0
- package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts +5 -0
- package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts.map +1 -0
- package/dist-lib/components/verkoop/OrderKarindelingCard.js +160 -0
- package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrderDetailView.js +6 -3
- package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrdersTable.js +3 -4
- package/dist-lib/components/verkoop/SearchableSelect.d.ts +19 -0
- package/dist-lib/components/verkoop/SearchableSelect.d.ts.map +1 -0
- package/dist-lib/components/verkoop/SearchableSelect.js +21 -0
- package/dist-lib/components/verkoop/VerkoopOrdersPage.d.ts.map +1 -1
- package/dist-lib/components/verkoop/VerkoopOrdersPage.js +39 -3
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +1381 -86
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +1381 -86
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +1381 -86
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +1402 -79
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/cart-lock.functions.d.ts +11225 -0
- package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -0
- package/dist-lib/lib/cart-lock.functions.js +47 -0
- package/dist-lib/lib/cart-planner.d.ts +38 -0
- package/dist-lib/lib/cart-planner.d.ts.map +1 -0
- package/dist-lib/lib/cart-planner.js +217 -0
- package/dist-lib/lib/cart-replan.functions.d.ts +22467 -0
- package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -0
- package/dist-lib/lib/cart-replan.functions.js +203 -0
- package/dist-lib/lib/create-order.functions.d.ts +100967 -0
- package/dist-lib/lib/create-order.functions.d.ts.map +1 -0
- package/dist-lib/lib/create-order.functions.js +343 -0
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +33640 -0
- package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-auto-fulfillment.functions.js +65 -0
- package/dist-lib/lib/floriday-auto-fulfillment.server.d.ts +21 -0
- package/dist-lib/lib/floriday-auto-fulfillment.server.d.ts.map +1 -0
- package/dist-lib/lib/floriday-auto-fulfillment.server.js +352 -0
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +11220 -0
- package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-logistic-labels.functions.js +171 -0
- package/dist-lib/lib/floriday-public.functions.d.ts +9067 -16
- package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday-public.functions.js +45 -47
- package/dist-lib/lib/floriday-writes.functions.d.ts +6475 -0
- package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-mapping.d.ts +2 -0
- package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-mapping.js +46 -20
- package/dist-lib/lib/salesorders.functions.d.ts +4273 -388
- package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders.functions.js +8 -0
- package/dist-lib/lib/tradeitems.functions.d.ts +1381 -86
- package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
- package/dist-lib/lib/useSalesOrdersQuery.d.ts +1 -0
- package/dist-lib/lib/useSalesOrdersQuery.d.ts.map +1 -1
- package/dist-lib/lib/useSalesOrdersQuery.js +130 -41
- package/package.json +1 -1
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// Server functions voor de handmatige "Nieuwe bestelling" flow.
|
|
2
|
+
// Deelt DB-queries met de Mailbox-module zodat beide manieren van
|
|
3
|
+
// orderinvoer identieke lijsten, validaties en berekeningen gebruiken.
|
|
4
|
+
import { createServerFn } from "@tanstack/react-start";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
|
|
7
|
+
// ─── Klanten ────────────────────────────────────────────────────────────────
|
|
8
|
+
export const listCustomersForCreateOrder = createServerFn({ method: "GET" })
|
|
9
|
+
.middleware([requireSupabaseAuth])
|
|
10
|
+
.handler(async ({ context }) => {
|
|
11
|
+
const { data } = await context.supabase
|
|
12
|
+
.from("customers")
|
|
13
|
+
.select("id, company_name, email")
|
|
14
|
+
.eq("is_active", true)
|
|
15
|
+
.order("company_name")
|
|
16
|
+
.range(0, 4999);
|
|
17
|
+
return { items: data ?? [] };
|
|
18
|
+
});
|
|
19
|
+
// ─── Producten (alleen met voorraad — zelfde filter als Mailbox) ────────────
|
|
20
|
+
export const listProductsForCreateOrder = createServerFn({ method: "GET" })
|
|
21
|
+
.middleware([requireSupabaseAuth])
|
|
22
|
+
.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
|
+
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
|
+
}
|
|
32
|
+
}
|
|
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);
|
|
43
|
+
});
|
|
44
|
+
return { items };
|
|
45
|
+
});
|
|
46
|
+
// ─── Afleverlocaties van geselecteerde klant ────────────────────────────────
|
|
47
|
+
export const listDeliveryLocationsForCustomerCO = createServerFn({ method: "POST" })
|
|
48
|
+
.middleware([requireSupabaseAuth])
|
|
49
|
+
.inputValidator((d) => z.object({ customerId: z.string().uuid() }).parse(d))
|
|
50
|
+
.handler(async ({ data, context }) => {
|
|
51
|
+
if (!data.customerId)
|
|
52
|
+
return { items: [] };
|
|
53
|
+
const { data: rows } = await context.supabase
|
|
54
|
+
.from("customer_delivery_locations")
|
|
55
|
+
.select("id, name, address_line, street, house_number, postal_code, city, province, country, is_primary, carrier_id, carriers(id, name)")
|
|
56
|
+
.eq("customer_id", data.customerId)
|
|
57
|
+
.order("is_primary", { ascending: false })
|
|
58
|
+
.order("name");
|
|
59
|
+
return { items: Array.isArray(rows) ? rows : [] };
|
|
60
|
+
});
|
|
61
|
+
// ─── Afhaallocaties (eigen tuinen) — Floriday warehouses ────────────────────
|
|
62
|
+
export const listPickupLocationsCO = createServerFn({ method: "GET" })
|
|
63
|
+
.middleware([requireSupabaseAuth])
|
|
64
|
+
.handler(async ({ context }) => {
|
|
65
|
+
const { data } = await context.supabase
|
|
66
|
+
.from("floriday_warehouses_cache")
|
|
67
|
+
.select("floriday_id, name, data")
|
|
68
|
+
.eq("is_deleted", false);
|
|
69
|
+
const rows = Array.isArray(data) ? data : [];
|
|
70
|
+
const items = rows
|
|
71
|
+
.filter((r) => {
|
|
72
|
+
const d = r?.data ?? {};
|
|
73
|
+
return d?.hasAccess === true && d?.isExternal === false;
|
|
74
|
+
})
|
|
75
|
+
.map((r) => {
|
|
76
|
+
const addr = r?.data?.location?.address?.addressLine;
|
|
77
|
+
const city = r?.data?.location?.address?.city;
|
|
78
|
+
const nameRaw = typeof r.name === "string" ? r.name.trim() : "";
|
|
79
|
+
const label = nameRaw.length > 0
|
|
80
|
+
? nameRaw
|
|
81
|
+
: [addr, city].filter(Boolean).join(", ") || "(naamloos)";
|
|
82
|
+
return { id: r.floriday_id, name: label };
|
|
83
|
+
})
|
|
84
|
+
.sort((a, b) => a.name.localeCompare(b.name, "nl"));
|
|
85
|
+
return { items };
|
|
86
|
+
});
|
|
87
|
+
// ─── Vervoerders ────────────────────────────────────────────────────────────
|
|
88
|
+
export const listCarriersForCreateOrder = createServerFn({ method: "GET" })
|
|
89
|
+
.middleware([requireSupabaseAuth])
|
|
90
|
+
.handler(async ({ context }) => {
|
|
91
|
+
const { data } = await context.supabase
|
|
92
|
+
.from("carriers")
|
|
93
|
+
.select("id, name, is_default, sort_order, delivery_days")
|
|
94
|
+
.order("sort_order", { ascending: true, nullsFirst: false })
|
|
95
|
+
.order("name");
|
|
96
|
+
return { items: data ?? [] };
|
|
97
|
+
});
|
|
98
|
+
// ─── Orderpicktijd (dagen) ──────────────────────────────────────────────────
|
|
99
|
+
export const getOrderPickDaysCO = createServerFn({ method: "GET" })
|
|
100
|
+
.middleware([requireSupabaseAuth])
|
|
101
|
+
.handler(async ({ context }) => {
|
|
102
|
+
const { data } = await context.supabase
|
|
103
|
+
.from("afhandeling_settings")
|
|
104
|
+
.select("pick_days_before_transport")
|
|
105
|
+
.order("updated_at", { ascending: false })
|
|
106
|
+
.limit(1)
|
|
107
|
+
.maybeSingle();
|
|
108
|
+
return { pickDays: Math.max(0, Number(data?.pick_days_before_transport ?? 0)) };
|
|
109
|
+
});
|
|
110
|
+
// ─── Handmatige bestelling aanmaken ────────────────────────────────────────
|
|
111
|
+
const LineInput = z.object({
|
|
112
|
+
product_id: z.string().uuid(),
|
|
113
|
+
product_name: z.string().min(1).max(255),
|
|
114
|
+
quantity: z.number().int().positive(), // aantal PLANTEN (== fusten * plants_per_crate)
|
|
115
|
+
unit: z.string().max(50).nullable().optional(),
|
|
116
|
+
unit_price: z.number().nonnegative().optional(),
|
|
117
|
+
});
|
|
118
|
+
const CreateManualOrderInput = z.object({
|
|
119
|
+
customerId: z.string().uuid(),
|
|
120
|
+
customerName: z.string().min(1).max(255),
|
|
121
|
+
deliveryLocationId: z.string().uuid(),
|
|
122
|
+
pickupLocationId: z.string().min(1),
|
|
123
|
+
carrierId: z.string().uuid(),
|
|
124
|
+
deliveryDate: z.string().min(1).max(50),
|
|
125
|
+
notes: z.string().max(2000).nullable().optional(),
|
|
126
|
+
lines: z.array(LineInput).min(1),
|
|
127
|
+
});
|
|
128
|
+
function addCalendarDays(base, n) {
|
|
129
|
+
const x = new Date(base);
|
|
130
|
+
x.setDate(x.getDate() + n);
|
|
131
|
+
return x;
|
|
132
|
+
}
|
|
133
|
+
function toISODate(d) {
|
|
134
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
135
|
+
}
|
|
136
|
+
export const createManualOrder = createServerFn({ method: "POST" })
|
|
137
|
+
.middleware([requireSupabaseAuth])
|
|
138
|
+
.inputValidator((input) => CreateManualOrderInput.parse(input))
|
|
139
|
+
.handler(async ({ data, context }) => {
|
|
140
|
+
// Validatie eerst mogelijke leverdatum — identiek aan approveProposal
|
|
141
|
+
const { data: c } = await context.supabase
|
|
142
|
+
.from("carriers").select("delivery_days").eq("id", data.carrierId).maybeSingle();
|
|
143
|
+
const carrierDeliveryDays = Math.max(0, Number(c?.delivery_days ?? 0));
|
|
144
|
+
const { data: settings } = await context.supabase
|
|
145
|
+
.from("afhandeling_settings")
|
|
146
|
+
.select("pick_days_before_transport")
|
|
147
|
+
.order("updated_at", { ascending: false })
|
|
148
|
+
.limit(1).maybeSingle();
|
|
149
|
+
const pickDays = Math.max(0, Number(settings?.pick_days_before_transport ?? 0));
|
|
150
|
+
const earliestISO = toISODate(addCalendarDays(new Date(), pickDays + carrierDeliveryDays));
|
|
151
|
+
if (data.deliveryDate < earliestISO) {
|
|
152
|
+
throw new Error(`Leverdatum (${data.deliveryDate}) ligt voor de eerst mogelijke datum (${earliestISO}) o.b.v. orderpicktijd (${pickDays}d) + levertijd vervoerder (${carrierDeliveryDays}d).`);
|
|
153
|
+
}
|
|
154
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
155
|
+
const total = data.lines.reduce((s, l) => s + (Number(l.unit_price ?? 0) * Number(l.quantity)), 0);
|
|
156
|
+
const { data: order, error: orderErr } = await supabaseAdmin.from("orders").insert({
|
|
157
|
+
customer_name: data.customerName,
|
|
158
|
+
customer_id: data.customerId,
|
|
159
|
+
order_date: new Date().toISOString().slice(0, 10),
|
|
160
|
+
delivery_date: data.deliveryDate,
|
|
161
|
+
status: "nieuw",
|
|
162
|
+
total: String(total.toFixed(2)),
|
|
163
|
+
notes: data.notes ?? null,
|
|
164
|
+
custom_fields: {
|
|
165
|
+
source: "manual",
|
|
166
|
+
delivery_location: data.deliveryLocationId,
|
|
167
|
+
pickup_location: data.pickupLocationId,
|
|
168
|
+
carrier: data.carrierId,
|
|
169
|
+
},
|
|
170
|
+
}).select("id, order_number").single();
|
|
171
|
+
if (orderErr)
|
|
172
|
+
throw new Error(orderErr.message);
|
|
173
|
+
const items = data.lines.map((l) => ({
|
|
174
|
+
order_id: order.id,
|
|
175
|
+
product_id: l.product_id,
|
|
176
|
+
product_name: l.product_name,
|
|
177
|
+
quantity: Math.max(1, Math.round(l.quantity)),
|
|
178
|
+
unit: l.unit ?? "stuks",
|
|
179
|
+
unit_price: l.unit_price ?? undefined,
|
|
180
|
+
}));
|
|
181
|
+
const { error: itemsErr } = await supabaseAdmin.from("order_items").insert(items);
|
|
182
|
+
if (itemsErr)
|
|
183
|
+
throw new Error(itemsErr.message);
|
|
184
|
+
// Direct karindeling berekenen, zodat het scherm ze meteen kan tonen.
|
|
185
|
+
try {
|
|
186
|
+
const { replanOrderCartsFromCron } = await import("./cart-replan.functions");
|
|
187
|
+
await replanOrderCartsFromCron(order.id);
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
console.error("[createManualOrder] karindeling faalde:", err);
|
|
191
|
+
}
|
|
192
|
+
void context; // reserveren indien later audit-log nodig is
|
|
193
|
+
return { ok: true, orderId: order.id };
|
|
194
|
+
});
|
|
195
|
+
export const planAndSaveAutoCarts = createServerFn({ method: "POST" })
|
|
196
|
+
.middleware([requireSupabaseAuth])
|
|
197
|
+
.inputValidator((input) => z.object({ orderId: z.string().uuid() }).parse(input))
|
|
198
|
+
.handler(async ({ data, context }) => {
|
|
199
|
+
const { data: items, error: itErr } = await context.supabase
|
|
200
|
+
.from("order_items")
|
|
201
|
+
.select("id, product_id, product_name, quantity, products(plants_per_crate, crates_per_layer, layers_per_cart)")
|
|
202
|
+
.eq("order_id", data.orderId);
|
|
203
|
+
if (itErr)
|
|
204
|
+
throw new Error(itErr.message);
|
|
205
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
206
|
+
await supabaseAdmin.from("order_carts").delete().eq("order_id", data.orderId).eq("is_auto", true);
|
|
207
|
+
let cartNumber = 1;
|
|
208
|
+
const { data: existingMax } = await supabaseAdmin
|
|
209
|
+
.from("order_carts").select("cart_number").eq("order_id", data.orderId)
|
|
210
|
+
.order("cart_number", { ascending: false }).limit(1);
|
|
211
|
+
if (existingMax && existingMax[0])
|
|
212
|
+
cartNumber = existingMax[0].cart_number + 1;
|
|
213
|
+
const autoCarts = [];
|
|
214
|
+
const remainders = [];
|
|
215
|
+
for (const it of items ?? []) {
|
|
216
|
+
const prod = it.products ?? {};
|
|
217
|
+
const ppc = Math.max(1, Number(prod.plants_per_crate ?? 1));
|
|
218
|
+
const cpl = Math.max(1, Number(prod.crates_per_layer ?? 1));
|
|
219
|
+
const lpc = Math.max(1, Number(prod.layers_per_cart ?? 1));
|
|
220
|
+
const cratesPerCart = cpl * lpc;
|
|
221
|
+
const totalPlants = Math.max(0, Number(it.quantity ?? 0));
|
|
222
|
+
const totalCrates = Math.ceil(totalPlants / ppc);
|
|
223
|
+
const fullCarts = Math.floor(totalCrates / cratesPerCart);
|
|
224
|
+
const remainder = totalCrates - fullCarts * cratesPerCart;
|
|
225
|
+
for (let i = 0; i < fullCarts; i++) {
|
|
226
|
+
const { data: cart, error: cErr } = await supabaseAdmin
|
|
227
|
+
.from("order_carts").insert({
|
|
228
|
+
order_id: data.orderId,
|
|
229
|
+
cart_number: cartNumber,
|
|
230
|
+
is_auto: true,
|
|
231
|
+
is_complete: true,
|
|
232
|
+
}).select("id").single();
|
|
233
|
+
if (cErr)
|
|
234
|
+
throw new Error(cErr.message);
|
|
235
|
+
await supabaseAdmin.from("order_cart_items").insert({
|
|
236
|
+
cart_id: cart.id,
|
|
237
|
+
order_item_id: it.id,
|
|
238
|
+
product_id: it.product_id,
|
|
239
|
+
product_name: it.product_name,
|
|
240
|
+
crates: cratesPerCart,
|
|
241
|
+
plants_per_crate: ppc,
|
|
242
|
+
crates_per_layer: cpl,
|
|
243
|
+
});
|
|
244
|
+
autoCarts.push({
|
|
245
|
+
cart_number: cartNumber,
|
|
246
|
+
product_name: it.product_name,
|
|
247
|
+
crates: cratesPerCart,
|
|
248
|
+
plants: cratesPerCart * ppc,
|
|
249
|
+
capacity: cratesPerCart,
|
|
250
|
+
});
|
|
251
|
+
cartNumber++;
|
|
252
|
+
}
|
|
253
|
+
if (remainder > 0) {
|
|
254
|
+
remainders.push({
|
|
255
|
+
order_item_id: it.id,
|
|
256
|
+
product_id: it.product_id,
|
|
257
|
+
product_name: it.product_name,
|
|
258
|
+
crates_remaining: remainder,
|
|
259
|
+
plants_per_crate: ppc,
|
|
260
|
+
crates_per_layer: cpl,
|
|
261
|
+
layers_per_cart: lpc,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const { data: manualCartsRaw } = await supabaseAdmin
|
|
266
|
+
.from("order_carts")
|
|
267
|
+
.select("id, cart_number, notes, order_cart_items(order_item_id, product_id, product_name, crates, plants_per_crate, crates_per_layer)")
|
|
268
|
+
.eq("order_id", data.orderId).eq("is_auto", false)
|
|
269
|
+
.order("cart_number");
|
|
270
|
+
const existingManualCarts = (manualCartsRaw ?? []).map((c) => ({
|
|
271
|
+
cart_number: c.cart_number,
|
|
272
|
+
notes: c.notes,
|
|
273
|
+
items: (c.order_cart_items ?? []).map((i) => ({
|
|
274
|
+
order_item_id: i.order_item_id,
|
|
275
|
+
product_id: i.product_id,
|
|
276
|
+
product_name: i.product_name,
|
|
277
|
+
crates: Number(i.crates ?? 0),
|
|
278
|
+
plants_per_crate: Number(i.plants_per_crate ?? 1),
|
|
279
|
+
crates_per_layer: Number(i.crates_per_layer ?? 1),
|
|
280
|
+
})),
|
|
281
|
+
}));
|
|
282
|
+
const assignedByItem = {};
|
|
283
|
+
existingManualCarts.forEach((c) => c.items.forEach((i) => {
|
|
284
|
+
if (!i.order_item_id)
|
|
285
|
+
return;
|
|
286
|
+
assignedByItem[i.order_item_id] = (assignedByItem[i.order_item_id] ?? 0) + Number(i.crates ?? 0);
|
|
287
|
+
}));
|
|
288
|
+
const adjustedRemainders = remainders
|
|
289
|
+
.map((r) => ({ ...r, crates_remaining: Math.max(0, r.crates_remaining - (assignedByItem[r.order_item_id] ?? 0)) }))
|
|
290
|
+
.filter((r) => r.crates_remaining > 0);
|
|
291
|
+
return { ok: true, autoCarts, remainders: adjustedRemainders, existingManualCarts, nextCartNumber: cartNumber };
|
|
292
|
+
});
|
|
293
|
+
const ManualCartItemSchema = z.object({
|
|
294
|
+
order_item_id: z.string().uuid().nullable(),
|
|
295
|
+
product_id: z.string().uuid().nullable(),
|
|
296
|
+
product_name: z.string().min(1).max(255),
|
|
297
|
+
crates: z.number().int().min(1),
|
|
298
|
+
plants_per_crate: z.number().int().min(1),
|
|
299
|
+
crates_per_layer: z.number().int().min(1),
|
|
300
|
+
});
|
|
301
|
+
const ManualCartSchema = z.object({
|
|
302
|
+
is_complete: z.boolean().optional(),
|
|
303
|
+
notes: z.string().max(500).nullable().optional(),
|
|
304
|
+
items: z.array(ManualCartItemSchema).min(1),
|
|
305
|
+
});
|
|
306
|
+
export const saveManualCarts = createServerFn({ method: "POST" })
|
|
307
|
+
.middleware([requireSupabaseAuth])
|
|
308
|
+
.inputValidator((input) => z.object({ orderId: z.string().uuid(), carts: z.array(ManualCartSchema) }).parse(input))
|
|
309
|
+
.handler(async ({ data }) => {
|
|
310
|
+
const { supabaseAdmin } = await import("../integrations/supabase/client.server");
|
|
311
|
+
await supabaseAdmin.from("order_carts").delete()
|
|
312
|
+
.eq("order_id", data.orderId).eq("is_auto", false);
|
|
313
|
+
const { data: existingMax } = await supabaseAdmin
|
|
314
|
+
.from("order_carts").select("cart_number").eq("order_id", data.orderId)
|
|
315
|
+
.order("cart_number", { ascending: false }).limit(1);
|
|
316
|
+
let cartNumber = existingMax && existingMax[0] ? existingMax[0].cart_number + 1 : 1;
|
|
317
|
+
for (const cart of data.carts) {
|
|
318
|
+
const { data: inserted, error: cErr } = await supabaseAdmin
|
|
319
|
+
.from("order_carts").insert({
|
|
320
|
+
order_id: data.orderId,
|
|
321
|
+
cart_number: cartNumber,
|
|
322
|
+
is_auto: false,
|
|
323
|
+
is_complete: !!cart.is_complete,
|
|
324
|
+
notes: cart.notes ?? null,
|
|
325
|
+
}).select("id").single();
|
|
326
|
+
if (cErr)
|
|
327
|
+
throw new Error(cErr.message);
|
|
328
|
+
const rows = cart.items.map((i) => ({
|
|
329
|
+
cart_id: inserted.id,
|
|
330
|
+
order_item_id: i.order_item_id,
|
|
331
|
+
product_id: i.product_id,
|
|
332
|
+
product_name: i.product_name,
|
|
333
|
+
crates: i.crates,
|
|
334
|
+
plants_per_crate: i.plants_per_crate,
|
|
335
|
+
crates_per_layer: i.crates_per_layer,
|
|
336
|
+
}));
|
|
337
|
+
const { error: iErr } = await supabaseAdmin.from("order_cart_items").insert(rows);
|
|
338
|
+
if (iErr)
|
|
339
|
+
throw new Error(iErr.message);
|
|
340
|
+
cartNumber++;
|
|
341
|
+
}
|
|
342
|
+
return { ok: true };
|
|
343
|
+
});
|