@flowselections/mailbox-orders 1.0.3 → 1.0.4

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 (30) hide show
  1. package/dist-lib/components/CartAssignmentWizard.d.ts +6 -0
  2. package/dist-lib/components/CartAssignmentWizard.d.ts.map +1 -0
  3. package/dist-lib/components/CartAssignmentWizard.js +199 -0
  4. package/dist-lib/components/MailboxProcessedPage.d.ts.map +1 -1
  5. package/dist-lib/components/MailboxProcessedPage.js +25 -4
  6. package/dist-lib/components/MailboxProposalsPage.d.ts.map +1 -1
  7. package/dist-lib/components/MailboxProposalsPage.js +272 -34
  8. package/dist-lib/components/SearchableSelect.d.ts +2 -1
  9. package/dist-lib/components/SearchableSelect.d.ts.map +1 -1
  10. package/dist-lib/components/SearchableSelect.js +4 -3
  11. package/dist-lib/index.d.ts +0 -1
  12. package/dist-lib/index.d.ts.map +1 -1
  13. package/dist-lib/index.js +2 -4
  14. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -1
  15. package/dist-lib/integrations/supabase/auth-attacher.js +37 -3
  16. package/dist-lib/integrations/supabase/auth-middleware.d.ts +2762 -145
  17. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  18. package/dist-lib/integrations/supabase/client.d.ts +2762 -145
  19. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  20. package/dist-lib/integrations/supabase/client.server.d.ts +2762 -145
  21. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  22. package/dist-lib/integrations/supabase/types.d.ts +2849 -149
  23. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  24. package/dist-lib/integrations/supabase/types.js +2 -0
  25. package/dist-lib/lib/mailbox-template.functions.d.ts +34602 -18900
  26. package/dist-lib/lib/mailbox-template.functions.d.ts.map +1 -1
  27. package/dist-lib/lib/mailbox.functions.d.ts +130480 -33017
  28. package/dist-lib/lib/mailbox.functions.d.ts.map +1 -1
  29. package/dist-lib/lib/mailbox.functions.js +392 -18
  30. package/package.json +2 -7
@@ -71,6 +71,7 @@ async function ingestSingleEvent(supabaseAdmin, connectionId, ev) {
71
71
  .insert({
72
72
  external_id: externalId,
73
73
  message_id: pickStr(p, "message_id", "messageId") ?? externalId,
74
+ connection_id: connectionId,
74
75
  from_email: fromEmail,
75
76
  from_name: fromName,
76
77
  subject,
@@ -88,8 +89,100 @@ async function ingestSingleEvent(supabaseAdmin, connectionId, ev) {
88
89
  }
89
90
  throw new Error(insErr.message);
90
91
  }
92
+ // Maak direct een leeg voorstel aan (AI staat uit) — match klant op afzender
93
+ await createStubProposal(supabaseAdmin, inserted.id, fromEmail);
91
94
  return { inserted: true, messageId: inserted.id };
92
95
  }
96
+ async function createStubProposal(supabaseAdmin, messageId, fromEmail) {
97
+ const { data: existing } = await supabaseAdmin
98
+ .from("mailbox_order_proposals").select("id").eq("message_id", messageId).maybeSingle();
99
+ if (existing?.id)
100
+ return;
101
+ const matchResult = await matchCustomerByEmail(supabaseAdmin, fromEmail);
102
+ await supabaseAdmin.from("mailbox_order_proposals").insert({
103
+ message_id: messageId,
104
+ matched_customer_id: matchResult.customerId,
105
+ confidence: matchResult.customerId ? 1 : 0,
106
+ status: "pending",
107
+ parsed_payload: {
108
+ stub: true,
109
+ reason: "ai_disabled",
110
+ auto_matched_customer: matchResult.customerId !== null,
111
+ auto_match_source: matchResult.source,
112
+ auto_match_email: fromEmail,
113
+ auto_match_warning: matchResult.warning,
114
+ auto_match_candidates: matchResult.candidates,
115
+ },
116
+ });
117
+ }
118
+ // Exact (case-insensitive) match op afzender-e-mailadres.
119
+ // Eerst customers.email, dan customer_contacts.email als fallback.
120
+ // Bij meerdere treffers: geen automatische koppeling, wel waarschuwing.
121
+ async function matchCustomerByEmail(supabaseAdmin, fromEmail) {
122
+ if (!fromEmail) {
123
+ return { customerId: null, source: null, warning: null, candidates: [] };
124
+ }
125
+ const email = fromEmail.trim();
126
+ if (!email) {
127
+ return { customerId: null, source: null, warning: null, candidates: [] };
128
+ }
129
+ // 1) customers.email (case-insensitive exact)
130
+ const { data: directHits } = await supabaseAdmin
131
+ .from("customers")
132
+ .select("id, company_name")
133
+ .ilike("email", email)
134
+ .limit(5);
135
+ const direct = Array.isArray(directHits) ? directHits : [];
136
+ if (direct.length === 1) {
137
+ return {
138
+ customerId: direct[0].id,
139
+ source: "customers",
140
+ warning: null,
141
+ candidates: direct.map((c) => ({ id: c.id, name: c.company_name })),
142
+ };
143
+ }
144
+ if (direct.length > 1) {
145
+ return {
146
+ customerId: null,
147
+ source: "customers",
148
+ warning: `Meerdere klanten met e-mailadres ${email} — kies handmatig.`,
149
+ candidates: direct.map((c) => ({ id: c.id, name: c.company_name })),
150
+ };
151
+ }
152
+ // 2) customer_contacts.email (case-insensitive exact)
153
+ const { data: contactHits } = await supabaseAdmin
154
+ .from("customer_contacts")
155
+ .select("customer_id")
156
+ .ilike("email", email)
157
+ .limit(5);
158
+ const contacts = Array.isArray(contactHits) ? contactHits : [];
159
+ const uniqueCustomerIds = Array.from(new Set(contacts.map((c) => c.customer_id).filter(Boolean)));
160
+ if (uniqueCustomerIds.length === 0) {
161
+ return { customerId: null, source: null, warning: null, candidates: [] };
162
+ }
163
+ const { data: custRows } = await supabaseAdmin
164
+ .from("customers")
165
+ .select("id, company_name")
166
+ .in("id", uniqueCustomerIds);
167
+ const cands = (Array.isArray(custRows) ? custRows : []).map((c) => ({
168
+ id: c.id,
169
+ name: c.company_name,
170
+ }));
171
+ if (uniqueCustomerIds.length === 1) {
172
+ return {
173
+ customerId: uniqueCustomerIds[0],
174
+ source: "customer_contacts",
175
+ warning: null,
176
+ candidates: cands,
177
+ };
178
+ }
179
+ return {
180
+ customerId: null,
181
+ source: "customer_contacts",
182
+ warning: `Meerdere klanten gevonden via contactpersoon met e-mailadres ${email} — kies handmatig.`,
183
+ candidates: cands,
184
+ };
185
+ }
93
186
  // ─── Ingest één specifiek event (vanuit realtime subscription) ───────────────
94
187
  export const ingestEmailEvent = createServerFn({ method: "POST" })
95
188
  .middleware([requireSupabaseAuth])
@@ -160,10 +253,20 @@ export const backfillRecentEvents = createServerFn({ method: "POST" })
160
253
  .update({ consumed_at: new Date().toISOString() })
161
254
  .in("id", consumedIds);
162
255
  }
163
- // AI-verwerking uitgeschakeld — wacht op externe model-koppeling
164
- const autoProcessed = 0;
165
- const autoApproved = 0;
166
- return { ok: true, inserted, autoProcessed, autoApproved };
256
+ // Maak ook stubs voor eerder ontvangen berichten zonder voorstel
257
+ const { data: orphanMsgs } = await supabaseAdmin
258
+ .from("mailbox_messages")
259
+ .select("id, from_email, mailbox_order_proposals(id)")
260
+ .order("received_at", { ascending: false })
261
+ .limit(200);
262
+ let stubbed = 0;
263
+ for (const m of (orphanMsgs ?? [])) {
264
+ if (Array.isArray(m.mailbox_order_proposals) && m.mailbox_order_proposals.length > 0)
265
+ continue;
266
+ await createStubProposal(supabaseAdmin, m.id, m.from_email ?? null);
267
+ stubbed++;
268
+ }
269
+ return { ok: true, inserted: inserted + stubbed, autoProcessed: 0, autoApproved: 0 };
167
270
  });
168
271
  // ─── Messages & Proposals ────────────────────────────────────────────────────
169
272
  export const listMessages = createServerFn({ method: "GET" })
@@ -187,27 +290,42 @@ export const parseMessage = createServerFn({ method: "POST" })
187
290
  .from("mailbox_messages").select("*").eq("id", data.messageId).single();
188
291
  if (msgErr || !msg)
189
292
  throw new Error("Bericht niet gevonden");
190
- // AI-parsing is uitgeschakeld tot de externe model-koppeling klaar is.
191
- // Het bericht blijft bewaard; status gaat naar "failed" met een duidelijke melding.
192
- const msg2 = msg;
293
+ // AI staat uit: maak een leeg voorstel zodat het bericht in "Voorstellen" verschijnt.
294
+ await createStubProposal(supabaseAdmin, msg.id, msg.from_email ?? null);
193
295
  await supabaseAdmin.from("mailbox_messages")
194
- .update({
195
- status: "failed",
196
- error_message: "AI-parsing is tijdelijk uitgeschakeld — wacht op externe model-koppeling.",
197
- })
198
- .eq("id", msg2.id);
199
- return {
200
- ok: false,
201
- error: "AI-parsing is tijdelijk uitgeschakeld — wacht op externe model-koppeling.",
202
- };
296
+ .update({ status: "parsed", error_message: null }).eq("id", msg.id);
297
+ return { ok: true };
203
298
  });
204
299
  export const listProposals = createServerFn({ method: "GET" })
205
300
  .middleware([requireSupabaseAuth])
206
301
  .inputValidator((input) => z.object({ status: z.enum(["pending", "approved", "rejected"]).optional() }).parse(input ?? {}))
207
302
  .handler(async ({ data, context }) => {
303
+ // Beperk tot berichten die binnenkwamen via de actieve orders/receive mailbox
304
+ const { data: active } = await context.supabase.rpc("email_auth_for_purpose", {
305
+ _purpose: "orders",
306
+ _direction: "receive",
307
+ });
308
+ const activeRow = Array.isArray(active) ? active[0] : null;
309
+ const activeConnectionId = activeRow?.id ?? null;
310
+ if (!activeConnectionId)
311
+ return { items: [] };
312
+ // Stap 1: message-ids van de actieve mailbox
313
+ const { data: msgs, error: msgErr } = await context.supabase
314
+ .from("mailbox_messages")
315
+ .select("id")
316
+ .eq("connection_id", activeConnectionId)
317
+ .order("received_at", { ascending: false })
318
+ .limit(500);
319
+ if (msgErr)
320
+ throw new Error(msgErr.message);
321
+ const messageIds = (msgs ?? []).map((m) => m.id);
322
+ if (messageIds.length === 0)
323
+ return { items: [] };
324
+ // Stap 2: voorstellen op die berichten
208
325
  let q = context.supabase
209
326
  .from("mailbox_order_proposals")
210
- .select("*, mailbox_messages(subject, from_email, from_name, body_text, received_at), mailbox_proposal_lines(*), mailbox_proposal_field_values(*)")
327
+ .select("*, mailbox_messages(subject, from_email, from_name, body_text, received_at, connection_id), mailbox_proposal_lines(*), mailbox_proposal_field_values(*)")
328
+ .in("message_id", messageIds)
211
329
  .order("created_at", { ascending: false })
212
330
  .limit(100);
213
331
  if (data.status)
@@ -245,6 +363,25 @@ export const approveProposal = createServerFn({ method: "POST" })
245
363
  (data.fieldValues ?? []).forEach((fv) => {
246
364
  customFields[fv.field_key] = fv.value;
247
365
  });
366
+ // ─── Validatie eerst mogelijke leverdatum ────────────────────────────────
367
+ const carrierId = typeof customFields["carrier"] === "string" ? customFields["carrier"] : null;
368
+ let carrierDeliveryDays = 0;
369
+ if (carrierId) {
370
+ const { data: c } = await context.supabase
371
+ .from("carriers").select("delivery_days").eq("id", carrierId).maybeSingle();
372
+ carrierDeliveryDays = Math.max(0, Number(c?.delivery_days ?? 0));
373
+ }
374
+ const { data: settings } = await context.supabase
375
+ .from("afhandeling_settings")
376
+ .select("pick_days_before_transport")
377
+ .order("updated_at", { ascending: false })
378
+ .limit(1).maybeSingle();
379
+ const pickDays = Math.max(0, Number(settings?.pick_days_before_transport ?? 0));
380
+ const earliest = addCalendarDays(new Date(), pickDays + carrierDeliveryDays);
381
+ const earliestISO = toISODate(earliest);
382
+ if (data.deliveryDate < earliestISO) {
383
+ throw new Error(`Leverdatum (${data.deliveryDate}) ligt voor de eerst mogelijke datum (${earliestISO}) o.b.v. orderpicktijd (${pickDays}d) + levertijd vervoerder (${carrierDeliveryDays}d).`);
384
+ }
248
385
  const orderNumber = `MB-${Date.now()}`;
249
386
  const { data: order, error: orderErr } = await supabaseAdmin
250
387
  .from("orders").insert({
@@ -307,6 +444,19 @@ export const rejectProposal = createServerFn({ method: "POST" })
307
444
  }).eq("id", data.proposalId);
308
445
  return { ok: true };
309
446
  });
447
+ // Een afgewezen (of goedgekeurd) voorstel terugzetten naar "te controleren".
448
+ export const reopenProposal = createServerFn({ method: "POST" })
449
+ .middleware([requireSupabaseAuth])
450
+ .inputValidator((input) => z.object({ proposalId: z.string().uuid() }).parse(input))
451
+ .handler(async ({ data }) => {
452
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server");
453
+ await supabaseAdmin.from("mailbox_order_proposals").update({
454
+ status: "pending",
455
+ reviewed_by: null,
456
+ reviewed_at: null,
457
+ }).eq("id", data.proposalId);
458
+ return { ok: true };
459
+ });
310
460
  export const listCustomersForPicker = createServerFn({ method: "GET" })
311
461
  .middleware([requireSupabaseAuth])
312
462
  .handler(async ({ context }) => {
@@ -318,6 +468,230 @@ export const listProductsForPicker = createServerFn({ method: "GET" })
318
468
  .middleware([requireSupabaseAuth])
319
469
  .handler(async ({ context }) => {
320
470
  const { data } = await context.supabase
321
- .from("products").select("id, product, unit").order("product").range(0, 4999);
471
+ .from("products")
472
+ .select("id, product, unit, plants_per_crate, crates_per_layer, layers_per_cart")
473
+ .order("product").range(0, 4999);
474
+ return { items: data ?? [] };
475
+ });
476
+ export const planAndSaveAutoCarts = createServerFn({ method: "POST" })
477
+ .middleware([requireSupabaseAuth])
478
+ .inputValidator((input) => z.object({ orderId: z.string().uuid() }).parse(input))
479
+ .handler(async ({ data, context }) => {
480
+ // Items met productbelading ophalen
481
+ const { data: items, error: itErr } = await context.supabase
482
+ .from("order_items")
483
+ .select("id, product_id, product_name, quantity, products(plants_per_crate, crates_per_layer, layers_per_cart)")
484
+ .eq("order_id", data.orderId);
485
+ if (itErr)
486
+ throw new Error(itErr.message);
487
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server");
488
+ // Reset eventuele eerdere auto-karren (idempotent)
489
+ await supabaseAdmin.from("order_carts").delete().eq("order_id", data.orderId).eq("is_auto", true);
490
+ let cartNumber = 1;
491
+ // Eerst hoogste bestaande nr ophalen (voor manueel toegevoegde karren)
492
+ const { data: existingMax } = await supabaseAdmin
493
+ .from("order_carts").select("cart_number").eq("order_id", data.orderId)
494
+ .order("cart_number", { ascending: false }).limit(1);
495
+ if (existingMax && existingMax[0])
496
+ cartNumber = existingMax[0].cart_number + 1;
497
+ const autoCarts = [];
498
+ const remainders = [];
499
+ for (const it of items ?? []) {
500
+ const prod = it.products ?? {};
501
+ const ppc = Math.max(1, Number(prod.plants_per_crate ?? 1));
502
+ const cpl = Math.max(1, Number(prod.crates_per_layer ?? 1));
503
+ const lpc = Math.max(1, Number(prod.layers_per_cart ?? 1));
504
+ const cratesPerCart = cpl * lpc;
505
+ // quantity is in PLANTEN (zie approveProposal)
506
+ const totalPlants = Math.max(0, Number(it.quantity ?? 0));
507
+ const totalCrates = Math.ceil(totalPlants / ppc);
508
+ const fullCarts = Math.floor(totalCrates / cratesPerCart);
509
+ const remainder = totalCrates - fullCarts * cratesPerCart;
510
+ for (let i = 0; i < fullCarts; i++) {
511
+ const { data: cart, error: cErr } = await supabaseAdmin
512
+ .from("order_carts").insert({
513
+ order_id: data.orderId,
514
+ cart_number: cartNumber,
515
+ is_auto: true,
516
+ is_complete: true,
517
+ }).select("id").single();
518
+ if (cErr)
519
+ throw new Error(cErr.message);
520
+ await supabaseAdmin.from("order_cart_items").insert({
521
+ cart_id: cart.id,
522
+ order_item_id: it.id,
523
+ product_id: it.product_id,
524
+ product_name: it.product_name,
525
+ crates: cratesPerCart,
526
+ plants_per_crate: ppc,
527
+ crates_per_layer: cpl,
528
+ });
529
+ autoCarts.push({
530
+ cart_number: cartNumber,
531
+ product_name: it.product_name,
532
+ crates: cratesPerCart,
533
+ plants: cratesPerCart * ppc,
534
+ capacity: cratesPerCart,
535
+ });
536
+ cartNumber++;
537
+ }
538
+ if (remainder > 0) {
539
+ remainders.push({
540
+ order_item_id: it.id,
541
+ product_id: it.product_id,
542
+ product_name: it.product_name,
543
+ crates_remaining: remainder,
544
+ plants_per_crate: ppc,
545
+ crates_per_layer: cpl,
546
+ layers_per_cart: lpc,
547
+ });
548
+ }
549
+ }
550
+ // Bestaande handmatige karren ophalen + crates per order_item aftrekken van remainders,
551
+ // zodat heropenen van de wizard niet dezelfde fusten opnieuw vraagt.
552
+ const { data: manualCartsRaw } = await supabaseAdmin
553
+ .from("order_carts")
554
+ .select("id, cart_number, notes, order_cart_items(order_item_id, product_id, product_name, crates, plants_per_crate, crates_per_layer)")
555
+ .eq("order_id", data.orderId).eq("is_auto", false)
556
+ .order("cart_number");
557
+ const existingManualCarts = (manualCartsRaw ?? []).map((c) => ({
558
+ cart_number: c.cart_number,
559
+ notes: c.notes,
560
+ items: (c.order_cart_items ?? []).map((i) => ({
561
+ order_item_id: i.order_item_id,
562
+ product_id: i.product_id,
563
+ product_name: i.product_name,
564
+ crates: Number(i.crates ?? 0),
565
+ plants_per_crate: Number(i.plants_per_crate ?? 1),
566
+ crates_per_layer: Number(i.crates_per_layer ?? 1),
567
+ })),
568
+ }));
569
+ const assignedByItem = {};
570
+ existingManualCarts.forEach((c) => c.items.forEach((i) => {
571
+ if (!i.order_item_id)
572
+ return;
573
+ assignedByItem[i.order_item_id] = (assignedByItem[i.order_item_id] ?? 0) + Number(i.crates ?? 0);
574
+ }));
575
+ const adjustedRemainders = remainders
576
+ .map((r) => ({ ...r, crates_remaining: Math.max(0, r.crates_remaining - (assignedByItem[r.order_item_id] ?? 0)) }))
577
+ .filter((r) => r.crates_remaining > 0);
578
+ return { ok: true, autoCarts, remainders: adjustedRemainders, existingManualCarts, nextCartNumber: cartNumber };
579
+ });
580
+ const ManualCartItem = z.object({
581
+ order_item_id: z.string().uuid().nullable(),
582
+ product_id: z.string().uuid().nullable(),
583
+ product_name: z.string().min(1).max(255),
584
+ crates: z.number().int().min(1),
585
+ plants_per_crate: z.number().int().min(1),
586
+ crates_per_layer: z.number().int().min(1),
587
+ });
588
+ const ManualCart = z.object({
589
+ is_complete: z.boolean().optional(),
590
+ notes: z.string().max(500).nullable().optional(),
591
+ items: z.array(ManualCartItem).min(1),
592
+ });
593
+ export const saveManualCarts = createServerFn({ method: "POST" })
594
+ .middleware([requireSupabaseAuth])
595
+ .inputValidator((input) => z.object({ orderId: z.string().uuid(), carts: z.array(ManualCart) }).parse(input))
596
+ .handler(async ({ data }) => {
597
+ const { supabaseAdmin } = await import("../integrations/supabase/client.server");
598
+ // Verwijder eerdere handmatige karren (replace strategy)
599
+ await supabaseAdmin.from("order_carts").delete()
600
+ .eq("order_id", data.orderId).eq("is_auto", false);
601
+ const { data: existingMax } = await supabaseAdmin
602
+ .from("order_carts").select("cart_number").eq("order_id", data.orderId)
603
+ .order("cart_number", { ascending: false }).limit(1);
604
+ let cartNumber = existingMax && existingMax[0] ? existingMax[0].cart_number + 1 : 1;
605
+ for (const cart of data.carts) {
606
+ const { data: inserted, error: cErr } = await supabaseAdmin
607
+ .from("order_carts").insert({
608
+ order_id: data.orderId,
609
+ cart_number: cartNumber,
610
+ is_auto: false,
611
+ is_complete: !!cart.is_complete,
612
+ notes: cart.notes ?? null,
613
+ }).select("id").single();
614
+ if (cErr)
615
+ throw new Error(cErr.message);
616
+ const rows = cart.items.map((i) => ({
617
+ cart_id: inserted.id,
618
+ order_item_id: i.order_item_id,
619
+ product_id: i.product_id,
620
+ product_name: i.product_name,
621
+ crates: i.crates,
622
+ plants_per_crate: i.plants_per_crate,
623
+ crates_per_layer: i.crates_per_layer,
624
+ }));
625
+ const { error: iErr } = await supabaseAdmin.from("order_cart_items").insert(rows);
626
+ if (iErr)
627
+ throw new Error(iErr.message);
628
+ cartNumber++;
629
+ }
630
+ return { ok: true };
631
+ });
632
+ export const listOrderCarts = createServerFn({ method: "POST" })
633
+ .middleware([requireSupabaseAuth])
634
+ .inputValidator((d) => d)
635
+ .handler(async ({ data, context }) => {
636
+ const { data: carts } = await context.supabase
637
+ .from("order_carts")
638
+ .select("id, cart_number, is_auto, is_complete, notes, order_cart_items(*)")
639
+ .eq("order_id", data.orderId)
640
+ .order("cart_number");
641
+ return { items: carts ?? [] };
642
+ });
643
+ export const listDeliveryLocationsForCustomer = createServerFn({ method: "POST" })
644
+ .middleware([requireSupabaseAuth])
645
+ .inputValidator((d) => d)
646
+ .handler(async ({ data, context }) => {
647
+ if (!data.customerId)
648
+ return { items: [] };
649
+ const { data: rows } = await context.supabase
650
+ .from("customer_delivery_locations")
651
+ .select("id, name, address_line, street, house_number, postal_code, city, province, country, is_primary, carrier_id, carriers(id, name)")
652
+ .eq("customer_id", data.customerId)
653
+ .order("is_primary", { ascending: false })
654
+ .order("name");
655
+ return { items: Array.isArray(rows) ? rows : [] };
656
+ });
657
+ // Onze eigen bedrijfslocaties (tuinen / afhaallocaties). Staan los van klantlocaties.
658
+ export const listPickupLocations = createServerFn({ method: "GET" })
659
+ .middleware([requireSupabaseAuth])
660
+ .handler(async ({ context }) => {
661
+ const { data } = await context.supabase
662
+ .from("locations")
663
+ .select("id, name, sort_order")
664
+ .order("sort_order", { ascending: true })
665
+ .order("name");
666
+ return { items: Array.isArray(data) ? data : [] };
667
+ });
668
+ export const listCarriersForPicker = createServerFn({ method: "GET" })
669
+ .middleware([requireSupabaseAuth])
670
+ .handler(async ({ context }) => {
671
+ const { data } = await context.supabase
672
+ .from("carriers")
673
+ .select("id, name, is_default, sort_order, delivery_days")
674
+ .order("sort_order", { ascending: true, nullsFirst: false })
675
+ .order("name");
322
676
  return { items: data ?? [] };
323
677
  });
678
+ // Orderpicktijd — gebruikt voor het berekenen van de eerst mogelijke leverdatum
679
+ export const getOrderPickDays = createServerFn({ method: "GET" })
680
+ .middleware([requireSupabaseAuth])
681
+ .handler(async ({ context }) => {
682
+ const { data } = await context.supabase
683
+ .from("afhandeling_settings")
684
+ .select("pick_days_before_transport")
685
+ .order("updated_at", { ascending: false })
686
+ .limit(1)
687
+ .maybeSingle();
688
+ return { pickDays: Math.max(0, Number(data?.pick_days_before_transport ?? 0)) };
689
+ });
690
+ function addCalendarDays(base, n) {
691
+ const x = new Date(base);
692
+ x.setDate(x.getDate() + n);
693
+ return x;
694
+ }
695
+ function toISODate(d) {
696
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
697
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowselections/mailbox-orders",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "private": false,
5
5
  "sideEffects": false,
6
6
  "type": "module",
@@ -46,7 +46,7 @@
46
46
  "@cloudflare/vite-plugin": "^1.25.5",
47
47
  "@eslint/js": "^9.32.0",
48
48
  "@flowselections/core": "^1.0.11",
49
- "@lovable.dev/vite-tanstack-config": "2.5.3",
49
+ "@lovable.dev/vite-tanstack-config": "2.6.4",
50
50
  "@rsbuild/core": "^1.0.0",
51
51
  "@tailwindcss/vite": "^4.2.1",
52
52
  "@tanstack/react-query": "^5.83.0",
@@ -77,11 +77,6 @@
77
77
  "flowselections": {
78
78
  "moduleId": "Mailbox",
79
79
  "pages": [
80
- {
81
- "export": "MailboxInboxPage",
82
- "route": "mailbox-orders",
83
- "title": "Inbox"
84
- },
85
80
  {
86
81
  "export": "MailboxProposalsPage",
87
82
  "route": "mailbox-orders/voorstellen",