@flowselections/floriday-verkoop-module 1.0.54 → 1.0.56

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 (69) hide show
  1. package/dist-lib/components/verkoop/OrderKarindelingCard.d.ts.map +1 -1
  2. package/dist-lib/components/verkoop/OrderKarindelingCard.js +7 -3
  3. package/dist-lib/integrations/supabase/auth-middleware.d.ts +45 -0
  4. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  5. package/dist-lib/integrations/supabase/client.d.ts +45 -0
  6. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  7. package/dist-lib/integrations/supabase/client.server.d.ts +45 -0
  8. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  9. package/dist-lib/integrations/supabase/types.d.ts +45 -0
  10. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  11. package/dist-lib/lib/cart-lock.functions.d.ts +45 -0
  12. package/dist-lib/lib/cart-lock.functions.d.ts.map +1 -1
  13. package/dist-lib/lib/cart-planning-settings.functions.d.ts +495 -0
  14. package/dist-lib/lib/cart-planning-settings.functions.d.ts.map +1 -1
  15. package/dist-lib/lib/cart-replan.functions.d.ts +137 -26
  16. package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -1
  17. package/dist-lib/lib/cart-replan.functions.js +93 -63
  18. package/dist-lib/lib/create-order.functions.d.ts +765 -0
  19. package/dist-lib/lib/create-order.functions.d.ts.map +1 -1
  20. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts +135 -0
  21. package/dist-lib/lib/floriday-auto-fulfillment.functions.d.ts.map +1 -1
  22. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts +45 -0
  23. package/dist-lib/lib/floriday-logistic-labels.functions.d.ts.map +1 -1
  24. package/dist-lib/lib/floriday-mirror.functions.d.ts +45 -0
  25. package/dist-lib/lib/floriday-mirror.functions.d.ts.map +1 -1
  26. package/dist-lib/lib/floriday-public.functions.d.ts +315 -0
  27. package/dist-lib/lib/floriday-public.functions.d.ts.map +1 -1
  28. package/dist-lib/lib/floriday-writes.functions.d.ts +225 -0
  29. package/dist-lib/lib/floriday-writes.functions.d.ts.map +1 -1
  30. package/dist-lib/lib/invoice-timing.functions.d.ts +180 -0
  31. package/dist-lib/lib/invoice-timing.functions.d.ts.map +1 -1
  32. package/dist-lib/lib/invoice-timing.functions.js +12 -6
  33. package/dist-lib/lib/load-carriers.functions.d.ts +45 -0
  34. package/dist-lib/lib/load-carriers.functions.d.ts.map +1 -1
  35. package/dist-lib/lib/logistics-engine.functions.d.ts +225 -0
  36. package/dist-lib/lib/logistics-engine.functions.d.ts.map +1 -1
  37. package/dist-lib/lib/logistics-engine.functions.js +10 -5
  38. package/dist-lib/lib/logistics-stock.functions.d.ts +90 -0
  39. package/dist-lib/lib/logistics-stock.functions.d.ts.map +1 -1
  40. package/dist-lib/lib/order-addresses.functions.d.ts +45 -0
  41. package/dist-lib/lib/order-addresses.functions.d.ts.map +1 -1
  42. package/dist-lib/lib/order-addresses.functions.js +15 -9
  43. package/dist-lib/lib/order-buffers.functions.d.ts +135 -0
  44. package/dist-lib/lib/order-buffers.functions.d.ts.map +1 -1
  45. package/dist-lib/lib/order-buffers.functions.js +10 -10
  46. package/dist-lib/lib/order-carrier.functions.d.ts +90 -0
  47. package/dist-lib/lib/order-carrier.functions.d.ts.map +1 -1
  48. package/dist-lib/lib/order-carrier.functions.js +20 -11
  49. package/dist-lib/lib/order-pickup.functions.d.ts +90 -0
  50. package/dist-lib/lib/order-pickup.functions.d.ts.map +1 -1
  51. package/dist-lib/lib/order-pickup.functions.js +20 -11
  52. package/dist-lib/lib/order-row.server.d.ts +24 -0
  53. package/dist-lib/lib/order-row.server.d.ts.map +1 -0
  54. package/dist-lib/lib/order-row.server.js +58 -0
  55. package/dist-lib/lib/order-shipments.functions.d.ts +180 -0
  56. package/dist-lib/lib/order-shipments.functions.d.ts.map +1 -1
  57. package/dist-lib/lib/order-shipments.functions.js +11 -6
  58. package/dist-lib/lib/product-name.d.ts +3 -0
  59. package/dist-lib/lib/product-name.d.ts.map +1 -0
  60. package/dist-lib/lib/product-name.js +43 -0
  61. package/dist-lib/lib/salesorders-list.functions.d.ts +45 -0
  62. package/dist-lib/lib/salesorders-list.functions.d.ts.map +1 -1
  63. package/dist-lib/lib/salesorders.functions.d.ts +225 -0
  64. package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
  65. package/dist-lib/lib/stock-consumption-alerts.functions.d.ts +90 -0
  66. package/dist-lib/lib/stock-consumption-alerts.functions.d.ts.map +1 -1
  67. package/dist-lib/lib/tradeitems.functions.d.ts +45 -0
  68. package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -1
  69. package/package.json +1 -1
@@ -6,6 +6,7 @@
6
6
  import { createServerFn } from "@tanstack/react-start";
7
7
  import { z } from "zod";
8
8
  import { requireSupabaseAuth } from "../integrations/supabase/auth-middleware";
9
+ import { resolveOrderRow } from "./order-row.server";
9
10
  import { CART_PLANNER_VERSION, planCarts, } from "./cart-planner";
10
11
  import { resolveBufferForOrderInternal } from "./order-buffers.functions";
11
12
  export async function replanBufferInternal(bufferId) {
@@ -159,18 +160,14 @@ export async function replanScopeInternal(scope) {
159
160
  for (const it of items ?? []) {
160
161
  itemToOrder.set(it.id, it.order_id);
161
162
  }
162
- // Bestaande karren in de scope tellen (voor waarschuwing) en wissen.
163
163
  // Nummering én delete-scope liggen altijd op bufferniveau als de order een
164
- // buffer heeft; order-scope is uitsluitend terugval.
164
+ // buffer heeft; order-scope is uitsluitend terugval. Wissen én wegschrijven
165
+ // gebeuren verderop in ÉÉN transactie via public.replace_order_carts, zodat
166
+ // een mislukte replan de bestaande indeling laat staan.
165
167
  if (!bufferId) {
166
168
  console.warn("[cart-replan] geen buffer voor order %s — nummering en delete vallen terug op order-scope", soloOrderId);
167
169
  }
168
- const cartScope = (q) => bufferId ? q.eq("buffer_id", bufferId) : q.eq("order_id", soloOrderId);
169
- const { data: existingCarts } = await cartScope(supabaseAdmin.from("order_carts").select("id, source"));
170
- const replacedManualCount = (existingCarts ?? []).filter((c) => c.source === "manual").length;
171
- const { error: delErr } = await cartScope(supabaseAdmin.from("order_carts").delete());
172
- if (delErr)
173
- throw new Error(delErr.message);
170
+ let replacedManualCount = 0;
174
171
  // De karindeling wordt hier altijd zelf gerekend. custom_fields.floriday_packing
175
172
  // levert alleen de belading-VELDEN (pieces_per_package / packages_per_layer /
176
173
  // layers_per_load_carrier) — de mirror schrijft nooit een voorgekauwde
@@ -277,8 +274,10 @@ export async function replanScopeInternal(scope) {
277
274
  }
278
275
  }
279
276
  }
280
- let cartsCreated = 0;
281
- for (const { cart, profile } of plannedCarts) {
277
+ // Payload voor public.replace_order_carts. LET OP: de sleutels hieronder
278
+ // moeten gelijk lopen met de sleutellijsten in die SQL-functie; ontbreekt er
279
+ // één, dan gooit de database een fout in plaats van stil NULL te schrijven.
280
+ const cartPayload = plannedCarts.map(({ cart, profile }) => {
282
281
  // Bepaal cart.order_id: null bij mix van orders, anders die ene order.
283
282
  const orderIdsOnCart = new Set();
284
283
  for (const i of cart.items) {
@@ -287,9 +286,7 @@ export async function replanScopeInternal(scope) {
287
286
  orderIdsOnCart.add(oid);
288
287
  }
289
288
  const singleOrderId = orderIdsOnCart.size === 1 ? [...orderIdsOnCart][0] : null;
290
- const { data: inserted, error: cErr } = await supabaseAdmin
291
- .from("order_carts")
292
- .insert({
289
+ return {
293
290
  order_id: singleOrderId,
294
291
  buffer_id: bufferId,
295
292
  cart_number: cart.cart_number,
@@ -303,14 +300,7 @@ export async function replanScopeInternal(scope) {
303
300
  carrier_type_id: profile.id,
304
301
  max_layers: profile.layers_per_carrier ?? null,
305
302
  algorithm_version: CART_PLANNER_VERSION,
306
- })
307
- .select("id")
308
- .single();
309
- if (cErr)
310
- throw new Error(cErr.message);
311
- if (cart.items.length > 0) {
312
- const rows = cart.items.map((i) => ({
313
- cart_id: inserted.id,
303
+ items: cart.items.map((i) => ({
314
304
  order_item_id: i.order_item_id,
315
305
  product_id: i.product_id,
316
306
  product_name: i.product_name,
@@ -320,14 +310,28 @@ export async function replanScopeInternal(scope) {
320
310
  layers_per_cart: i.layers_per_cart,
321
311
  layer_fraction: i.layer_fraction,
322
312
  layer_share_pct: i.layer_share_pct,
323
- }));
324
- const { error: iErr } = await supabaseAdmin
325
- .from("order_cart_items")
326
- .insert(rows);
327
- if (iErr)
328
- throw new Error(iErr.message);
329
- }
330
- cartsCreated++;
313
+ })),
314
+ };
315
+ });
316
+ // Delete + inserts in één transactie: faalt er iets, dan blijft de oude
317
+ // karindeling staan in plaats van een lege buffer achter te laten.
318
+ const { data: replaceRes, error: replaceErr } = await supabaseAdmin.rpc("replace_order_carts", {
319
+ p_buffer_id: bufferId,
320
+ p_order_id: bufferId ? null : soloOrderId,
321
+ p_carts: cartPayload,
322
+ });
323
+ if (replaceErr)
324
+ throw new Error(replaceErr.message);
325
+ const cartsCreated = Number(replaceRes?.carts_written ?? 0);
326
+ const itemsWritten = Number(replaceRes?.items_written ?? 0);
327
+ replacedManualCount = Number(replaceRes?.replaced_manual_count ?? 0);
328
+ // Controleer dat de database daadwerkelijk heeft weggeschreven wat de
329
+ // planner opleverde. Nul karren mag, maar alleen als de planner er ook nul
330
+ // opleverde — niet als gevolg van een fout.
331
+ const expectedItems = cartPayload.reduce((n, c) => n + c.items.length, 0);
332
+ if (cartsCreated !== cartPayload.length || itemsWritten !== expectedItems) {
333
+ throw new Error(`Karindeling niet volledig weggeschreven: ${cartsCreated}/${cartPayload.length} karren, ` +
334
+ `${itemsWritten}/${expectedItems} artikelen.`);
331
335
  }
332
336
  return {
333
337
  ok: true,
@@ -360,8 +364,12 @@ export async function replanOrderCartsFromCron(orderId) {
360
364
  }
361
365
  export const replanOrderCarts = createServerFn({ method: "POST" })
362
366
  .middleware([requireSupabaseAuth])
363
- .inputValidator((input) => z.object({ orderId: z.string().uuid() }).parse(input))
364
- .handler(async ({ data }) => replanOrderInternal(data.orderId));
367
+ .inputValidator((input) => z.object({ orderId: z.string().min(1) }).parse(input))
368
+ .handler(async ({ data, context }) => {
369
+ // DTO-id kan een Floriday salesOrderId zijn.
370
+ const order = await resolveOrderRow(context.supabase, data.orderId);
371
+ return replanOrderInternal(order.id);
372
+ });
365
373
  // ─── Queue drain (aangeroepen door pg_cron elke minuut) ─────────────────────
366
374
  export async function drainCartReplanQueue(limit = 50) {
367
375
  const { supabaseAdmin } = await import("../integrations/supabase/client.server");
@@ -406,65 +414,79 @@ export async function drainCartReplanQueue(limit = 50) {
406
414
  // logistics_plan_queue). De UI berekent NIETS zelf: hij toont wat er staat.
407
415
  export const listOrderCarts = createServerFn({ method: "GET" })
408
416
  .middleware([requireSupabaseAuth])
409
- .inputValidator((input) => z.object({ orderId: z.string().uuid() }).parse(input))
417
+ .inputValidator((input) => z.object({ orderId: z.string().min(1) }).parse(input))
410
418
  .handler(async ({ data, context }) => {
411
- const { data: order } = await context.supabase
419
+ // Elke query faalt HARD. "Geen karren" en "kon niet laden" zijn twee
420
+ // verschillende dingen; stilzwijgend een lege array teruggeven verbergt
421
+ // fouten (RLS, kolomnaam, embed) als "nog geen karindeling".
422
+ const fail = (what, error) => {
423
+ throw new Error(`Karindeling laden mislukt (${what}): ${error?.message ?? "onbekende fout"}`);
424
+ };
425
+ // De DTO-id is voor Floriday-orders het salesOrderId, niet orders.id.
426
+ // resolveOrderRow doet de twee-staps-lookup + botsingswaarschuwing.
427
+ const orderRow = await resolveOrderRow(context.supabase, data.orderId);
428
+ const dbOrderId = orderRow.id;
429
+ const bufferId = orderRow.buffer_id;
430
+ const lockRes = await context.supabase
412
431
  .from("orders")
413
- .select("buffer_id, plan_locked_at")
414
- .eq("id", data.orderId)
432
+ .select("plan_locked_at")
433
+ .eq("id", dbOrderId)
415
434
  .maybeSingle();
416
- const bufferId = order?.buffer_id;
417
- const planLockedAt = order?.plan_locked_at ?? null;
435
+ if (lockRes.error)
436
+ fail("plan-lock lezen", lockRes.error);
437
+ const planLockedAt = lockRes.data?.plan_locked_at ?? null;
418
438
  const select = "id, order_id, buffer_id, cart_number, source, is_complete, occupancy_pct, total_layers_used, algorithm_version, carrier_type, max_layers, notes, order_cart_items(id, order_item_id, product_id, product_name, crates, plants_per_crate, crates_per_layer, layers_per_cart, layer_fraction, layer_share_pct)";
419
439
  const base = context.supabase.from("order_carts").select(select);
420
- const { data: carts } = await (bufferId
440
+ const cartsRes = await (bufferId
421
441
  ? base.eq("buffer_id", bufferId)
422
- : base.eq("order_id", data.orderId)).order("cart_number");
442
+ : base.eq("order_id", dbOrderId)).order("cart_number");
443
+ if (cartsRes.error)
444
+ fail("karren lezen", cartsRes.error);
445
+ const carts = (cartsRes.data ?? []);
423
446
  // Orders in de scope + ordernummer per orderregel (voor de badges).
424
447
  let orders = [];
425
448
  if (bufferId) {
426
- const { data: sibs } = await context.supabase
449
+ const sibs = await context.supabase
427
450
  .from("orders")
428
451
  .select("id, order_number")
429
452
  .eq("buffer_id", bufferId)
430
453
  .is("deleted_at", null);
431
- orders = (sibs ?? []).map((o) => ({
454
+ if (sibs.error)
455
+ fail("orders in buffer lezen", sibs.error);
456
+ orders = (sibs.data ?? []).map((o) => ({
432
457
  id: o.id,
433
458
  order_number: o.order_number ?? null,
434
459
  }));
435
460
  }
436
461
  if (orders.length === 0) {
437
- const { data: own } = await context.supabase
438
- .from("orders")
439
- .select("id, order_number")
440
- .eq("id", data.orderId)
441
- .maybeSingle();
442
- if (own) {
443
- orders = [{ id: own.id, order_number: own.order_number ?? null }];
444
- }
462
+ orders = [{ id: dbOrderId, order_number: orderRow.order_number ?? null }];
445
463
  }
446
464
  const orderIds = orders.map((o) => o.id);
447
465
  let itemOrders = [];
448
466
  if (orderIds.length > 0) {
449
- const { data: its } = await context.supabase
467
+ const its = await context.supabase
450
468
  .from("order_items")
451
469
  .select("id, order_id")
452
470
  .in("order_id", orderIds);
453
- itemOrders = (its ?? []).map((i) => ({
471
+ if (its.error)
472
+ fail("orderregels lezen", its.error);
473
+ itemOrders = (its.data ?? []).map((i) => ({
454
474
  id: i.id,
455
475
  order_id: i.order_id,
456
476
  }));
457
477
  }
458
478
  // Blokkadereden alleen ophalen als er niets gepland staat.
459
479
  let blocked = null;
460
- if ((carts ?? []).length === 0 && orderIds.length > 0) {
461
- const { data: dec } = await context.supabase
480
+ if (carts.length === 0 && orderIds.length > 0) {
481
+ const dec = await context.supabase
462
482
  .from("logistics_decisions")
463
483
  .select("subject_id, slot, explanation, result, created_at")
464
484
  .in("subject_id", orderIds)
465
485
  .order("created_at", { ascending: false })
466
486
  .limit(1);
467
- const d = (dec ?? [])[0];
487
+ if (dec.error)
488
+ fail("blokkadereden lezen", dec.error);
489
+ const d = (dec.data ?? [])[0];
468
490
  if (d) {
469
491
  blocked = {
470
492
  reason: d.result?.reason ?? d.slot ?? null,
@@ -473,28 +495,36 @@ export const listOrderCarts = createServerFn({ method: "GET" })
473
495
  };
474
496
  }
475
497
  if (!blocked) {
476
- const { data: q } = await context.supabase
498
+ const q = await context.supabase
477
499
  .from("logistics_plan_queue")
478
500
  .select("order_id, status, reason, error, updated_at")
479
501
  .in("order_id", orderIds)
480
502
  .order("updated_at", { ascending: false })
481
503
  .limit(1);
482
- const r = (q ?? [])[0];
504
+ if (q.error)
505
+ fail("planqueue lezen", q.error);
506
+ const r = (q.data ?? [])[0];
483
507
  if (r && (r.error || r.reason)) {
508
+ // `reason` in logistics_plan_queue is vaak een sleutel/veldnaam
509
+ // ("items"), geen leesbare reden. De echte fout staat in `error`.
510
+ const err = r.error ?? null;
511
+ const rawReason = r.reason ?? null;
484
512
  blocked = {
485
- reason: r.reason ?? null,
486
- explanation: r.error ?? null,
513
+ reason: rawReason,
514
+ explanation: err ?? (rawReason ? `Planning geblokkeerd (${rawReason})` : null),
487
515
  at: r.updated_at ?? null,
488
516
  };
489
517
  }
490
518
  }
491
519
  }
492
- return { carts: carts ?? [], bufferId, planLockedAt, orders, itemOrders, blocked };
520
+ return { carts, bufferId, orderId: dbOrderId, planLockedAt, orders, itemOrders, blocked };
493
521
  });
494
522
  export const replanBufferForOrder = createServerFn({ method: "POST" })
495
523
  .middleware([requireSupabaseAuth])
496
- .inputValidator((input) => z.object({ orderId: z.string().uuid() }).parse(input))
497
- .handler(async ({ data }) => {
498
- const bufferId = await resolveBufferForOrderInternal(data.orderId);
499
- return replanScopeInternal({ bufferId, orderId: data.orderId });
524
+ .inputValidator((input) => z.object({ orderId: z.string().min(1) }).parse(input))
525
+ .handler(async ({ data, context }) => {
526
+ // DTO-id kan een Floriday salesOrderId zijn.
527
+ const order = await resolveOrderRow(context.supabase, data.orderId);
528
+ const bufferId = await resolveBufferForOrderInternal(order.id);
529
+ return replanScopeInternal({ bufferId, orderId: order.id });
500
530
  });