@flowselections/floriday-afhandeling-module 1.0.40 → 1.0.42

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 (40) hide show
  1. package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -1
  2. package/dist-lib/components/afhandeling/ControlePage.js +86 -10
  3. package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -1
  4. package/dist-lib/components/afhandeling/DagDetailDialog.js +4 -3
  5. package/dist-lib/components/afhandeling/VervoerdersPage.js +1 -1
  6. package/dist-lib/components/afhandeling/VerzetOrderDialog.js +1 -1
  7. package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
  8. package/dist-lib/components/afhandeling/WeekAgendaView.js +38 -45
  9. package/dist-lib/hooks/useCartIndex.d.ts +11 -0
  10. package/dist-lib/hooks/useCartIndex.d.ts.map +1 -0
  11. package/dist-lib/hooks/useCartIndex.js +56 -0
  12. package/dist-lib/hooks/useConsistencyCheck.d.ts +8 -0
  13. package/dist-lib/hooks/useConsistencyCheck.d.ts.map +1 -0
  14. package/dist-lib/hooks/useConsistencyCheck.js +22 -0
  15. package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
  16. package/dist-lib/hooks/useOrderPlanning.js +6 -2
  17. package/dist-lib/hooks/usePickAgenda.d.ts +32 -0
  18. package/dist-lib/hooks/usePickAgenda.d.ts.map +1 -0
  19. package/dist-lib/hooks/usePickAgenda.js +75 -0
  20. package/dist-lib/integrations/supabase/auth-middleware.d.ts +1342 -747
  21. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  22. package/dist-lib/integrations/supabase/client.server.d.ts +1342 -747
  23. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  24. package/dist-lib/integrations/supabase/client.server.js +1 -1
  25. package/dist-lib/integrations/supabase/moduleClient.d.ts +1342 -747
  26. package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
  27. package/dist-lib/integrations/supabase/types.d.ts +1354 -775
  28. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  29. package/dist-lib/lib/afhandelingSettings.functions.d.ts +2501 -1307
  30. package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
  31. package/dist-lib/lib/carriers.functions.d.ts +6356 -3381
  32. package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
  33. package/dist-lib/lib/cartAssignments.functions.d.ts +6356 -3381
  34. package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
  35. package/dist-lib/lib/cartControl.functions.d.ts +12821 -6902
  36. package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
  37. package/dist-lib/lib/cartControl.functions.js +92 -127
  38. package/dist-lib/lib/floriday.functions.d.ts +1345 -748
  39. package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
  40. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"AAmHA,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,+BAyzBpE"}
1
+ {"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"AA+JA,wBAAgB,YAAY,CAAC,EAAE,MAAM,EAAE,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CAAO,+BAw1BpE"}
@@ -3,24 +3,64 @@ import { useEffect, useMemo, useRef, useState } from "react";
3
3
  import { supabase } from "../../integrations/supabase/moduleClient";
4
4
  import { Button, Card, Checkbox, Dialog, DialogDescription, DialogTitle, Input, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Textarea, Badge, toast, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
5
5
  import { Printer, CheckCircle2, AlertTriangle, History, ChevronLeft, ChevronRight } from "lucide-react";
6
- import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
6
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../ui/responsive-dialog";
7
7
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
8
8
  import { useServerFn } from "@tanstack/react-start";
9
9
  import { getTodaysControlCarts, getControlRescheduleProposals, submitControlSession, listControlHistory, getControlSessionDetail, getControlDraft, saveControlDraft, } from "../../lib/cartControl.functions";
10
10
  import { renderControleLijstHtml } from "./ControleLijstPrint";
11
11
  import { printHtmlDocument, printFoutMelding } from "../../lib/printing";
12
+ function dayDiffFromToday(iso) {
13
+ const d = new Date(iso);
14
+ if (Number.isNaN(d.getTime()))
15
+ return null;
16
+ const today = new Date();
17
+ today.setHours(0, 0, 0, 0);
18
+ d.setHours(0, 0, 0, 0);
19
+ return Math.round((d.getTime() - today.getTime()) / 86400000);
20
+ }
21
+ /** "vandaag", "morgen", "over 3 dagen" of een korte datum. */
22
+ function relDayLabel(iso) {
23
+ const diff = dayDiffFromToday(iso);
24
+ const d = new Date(iso);
25
+ if (diff === null)
26
+ return iso;
27
+ if (diff === 0)
28
+ return "vandaag";
29
+ if (diff === 1)
30
+ return "morgen";
31
+ if (diff === -1)
32
+ return "gisteren";
33
+ if (diff > 1 && diff <= 7)
34
+ return `over ${diff} dagen`;
35
+ if (diff < -1)
36
+ return `${Math.abs(diff)} dagen geleden`;
37
+ return d.toLocaleDateString("nl-NL", { weekday: "short", day: "2-digit", month: "2-digit" });
38
+ }
39
+ function fmtTime(time) {
40
+ if (!time)
41
+ return null;
42
+ const m = /^(\d{1,2}):(\d{2})/.exec(time.trim());
43
+ if (!m)
44
+ return null;
45
+ return `${m[1].padStart(2, "0")}:${m[2]}`;
46
+ }
47
+ /** "vandaag - 10:00" */
48
+ function fmtPickup(iso, time) {
49
+ if (!iso)
50
+ return "—";
51
+ const d = new Date(iso);
52
+ if (Number.isNaN(d.getTime()))
53
+ return iso;
54
+ const t = fmtTime(time);
55
+ return t ? `${relDayLabel(iso)} - ${t}` : relDayLabel(iso);
56
+ }
12
57
  function fmtDate(iso) {
13
58
  if (!iso)
14
59
  return "—";
15
60
  const d = new Date(iso);
16
61
  if (Number.isNaN(d.getTime()))
17
62
  return iso;
18
- return d.toLocaleDateString("nl-NL", {
19
- weekday: "short",
20
- day: "2-digit",
21
- month: "2-digit",
22
- year: "numeric",
23
- });
63
+ return relDayLabel(iso);
24
64
  }
25
65
  function isTomorrowIso(iso) {
26
66
  if (!iso)
@@ -38,6 +78,10 @@ function blockedKey(b) {
38
78
  return `${b.orderSource}#${b.orderId}#${b.cartIndex}`;
39
79
  }
40
80
  function controlCartKey(c) {
81
+ return c.cartNumberId ?? `${c.orderSource}#${c.orderId}#${c.cartIndex}`;
82
+ }
83
+ /** Oude concept-sleutels (`bron#order#index`) omzetten naar de nieuwe kar-sleutel. */
84
+ function legacyCartKey(c) {
41
85
  return `${c.orderSource}#${c.orderId}#${c.cartIndex}`;
42
86
  }
43
87
  function stableStringifyDraft(input) {
@@ -92,7 +136,9 @@ export function ControlePage({ onExit } = {}) {
92
136
  refetchOnMount: "always",
93
137
  refetchOnWindowFocus: true,
94
138
  });
95
- const carts = cartsQ.data?.carts ?? [];
139
+ // Karren die al zijn goedgekeurd (klaargezet/verzonden/geleverd) verdwijnen uit de controlelijst.
140
+ const APPROVED_CART_STATUSES = ["ready", "checked", "shipped", "delivered"];
141
+ const carts = (cartsQ.data?.carts ?? []).filter((c) => !APPROVED_CART_STATUSES.includes(String(c.status ?? "pending")));
96
142
  const currentDraftPayload = useMemo(() => {
97
143
  const nextStates = { ...cartStates };
98
144
  for (const cart of carts) {
@@ -162,6 +208,31 @@ export function ControlePage({ onExit } = {}) {
162
208
  draftLoadedRef.current = true;
163
209
  setDraftHydrated(true);
164
210
  }, [draftQ.data, draftQ.isError, draftQ.isFetching, draftQ.isLoading]);
211
+ // Migreer concept-sleutels van het oude formaat naar de nieuwe kar-sleutel.
212
+ const keysMigratedRef = useRef(false);
213
+ useEffect(() => {
214
+ if (keysMigratedRef.current)
215
+ return;
216
+ if (!draftHydrated || carts.length === 0)
217
+ return;
218
+ const mapping = new Map();
219
+ for (const cart of carts) {
220
+ const legacy = legacyCartKey(cart);
221
+ const next = controlCartKey(cart);
222
+ if (legacy !== next)
223
+ mapping.set(legacy, next);
224
+ }
225
+ keysMigratedRef.current = true;
226
+ if (mapping.size === 0)
227
+ return;
228
+ setChecked((prev) => new Set([...prev].map((k) => mapping.get(k) ?? k)));
229
+ setCartStates((prev) => {
230
+ const next = {};
231
+ for (const [k, v] of Object.entries(prev))
232
+ next[mapping.get(k) ?? k] = v;
233
+ return next;
234
+ });
235
+ }, [carts, draftHydrated]);
165
236
  // Debounced autosave of the complete draft state.
166
237
  const [savingState, setSavingState] = useState("idle");
167
238
  const autosaveTimer = useRef(null);
@@ -365,7 +436,7 @@ export function ControlePage({ onExit } = {}) {
365
436
  };
366
437
  const stats = useMemo(() => {
367
438
  const total = carts.length;
368
- const printed = carts.filter((c) => c.printedCount > 0).length;
439
+ const printed = carts.filter((c) => c.printedToday ?? c.printedCount > 0).length;
369
440
  const checkedCount = carts.filter((c) => checked.has(controlCartKey(c))).length;
370
441
  return { total, printed, checked: checkedCount };
371
442
  }, [carts, checked]);
@@ -402,6 +473,11 @@ export function ControlePage({ onExit } = {}) {
402
473
  "orders",
403
474
  "floriday_salesorders_cache",
404
475
  "dashboard_critical_alerts",
476
+ "logistics_tasks",
477
+ "pick_queue",
478
+ "print_jobs",
479
+ "order_carts",
480
+ "order_cart_items",
405
481
  ];
406
482
  const channel = supabase.channel("controle-page-realtime");
407
483
  for (const table of tables) {
@@ -437,7 +513,7 @@ export function ControlePage({ onExit } = {}) {
437
513
  ? "indeterminate"
438
514
  : false, onCheckedChange: (v) => toggleAll(!!v) }) }), _jsx(TableHead, { children: "Ordernummer" }), _jsx(TableHead, { children: "Ophaaldatum" }), _jsx(TableHead, { children: "Uitgeprint" })] }) }), _jsxs(TableBody, { children: [cartsQ.isLoading && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center text-muted-foreground py-8", children: "Laden\u2026" }) })), !cartsQ.isLoading && carts.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 4, className: "text-center text-muted-foreground py-8", children: "Geen karren om vandaag te controleren." }) })), pagedCarts.map((c) => {
439
515
  const k = controlCartKey(c);
440
- return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Checkbox, { checked: checked.has(k), onCheckedChange: (v) => toggle(k, !!v) }) }), _jsx(TableCell, { className: "font-mono font-semibold", children: (c.orderNumbers?.length ?? 0) > 0 ? (_jsx("div", { className: "flex flex-col gap-0.5", children: c.orderNumbers.map((n) => (_jsx("span", { children: n }, n))) })) : (c.orderNumber || "—") }), _jsx(TableCell, { children: fmtDate(c.pickupDate) }), _jsx(TableCell, { children: (c.printedCount ?? 0) > 0 ? (_jsx(Badge, { variant: "secondary", children: "Ja" })) : (_jsx(Badge, { variant: "outline", children: "Nee" })) })] }, k));
516
+ return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Checkbox, { checked: checked.has(k), onCheckedChange: (v) => toggle(k, !!v) }) }), _jsx(TableCell, { className: "font-mono font-semibold", children: (c.orderNumbers?.length ?? 0) > 0 ? (_jsx("div", { className: "flex flex-col gap-0.5", children: c.orderNumbers.map((n) => (_jsx("span", { children: n }, n))) })) : (c.orderNumber || "—") }), _jsx(TableCell, { children: fmtPickup(c.pickupDate, c.pickupTime) }), _jsx(TableCell, { children: (c.printedToday ?? (c.printedCount ?? 0) > 0) ? (_jsx(Badge, { variant: "secondary", children: "Ja" })) : (_jsx(Badge, { variant: "outline", children: "Nee" })) })] }, k));
441
517
  })] })] }), carts.length > 0 && (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-t bg-muted/20 px-4 py-3 text-sm", children: [_jsxs("span", { className: "text-muted-foreground", children: [carts.length, " resultaten"] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx("span", { className: "text-muted-foreground", children: "Resultaten per pagina" }), _jsxs(Select, { value: String(pageSize), onValueChange: (v) => {
442
518
  setPageSize(Number(v));
443
519
  setPage(1);
@@ -1 +1 @@
1
- {"version":3,"file":"DagDetailDialog.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/DagDetailDialog.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAsCjE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC;AAID,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,oBAAoB,sCAgF1F"}
1
+ {"version":3,"file":"DagDetailDialog.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/DagDetailDialog.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAsCjE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC;AAID,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,oBAAoB,sCAiF1F"}
@@ -4,7 +4,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
5
  import { Badge, Checkbox, Button, Dialog, DialogTitle, DialogDescription, Input, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
6
6
  import { CalendarClock, ChevronDown, ChevronRight, Printer, Users } from "lucide-react";
7
- import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
7
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../ui/responsive-dialog";
8
8
  import { isSameDay } from "../../lib/pickDate";
9
9
  import { getOrderLines, } from "../../lib/cartAssignments.functions";
10
10
  import { printLayoutDocument, printFoutMelding } from "../../lib/printing";
@@ -29,7 +29,8 @@ export function DagDetailDialog({ open, date, planned, onOpenChange }) {
29
29
  const cartRows = [];
30
30
  for (const p of ordersOnDay) {
31
31
  const assigns = p.planning.pickAssignments ?? [];
32
- const totalCarts = p.order.cartCount ?? 0;
32
+ // Karren komen uit de Engine-planning (gedeelde karindex), niet uit payloads.
33
+ const totalCarts = assigns.reduce((sum, a) => sum + a.cartCount, 0);
33
34
  if (totalCarts <= 0)
34
35
  continue;
35
36
  let runningIdx = 0;
@@ -169,7 +170,7 @@ function OrderRow({ planned: p, cartsToday }) {
169
170
  /* doorgaan met lege regels */
170
171
  }
171
172
  }
172
- const cartTotal = Math.max(1, p.order.cartCount ?? 1);
173
+ const cartTotal = Math.max(1, (p.planning?.pickAssignments ?? []).reduce((sum, a) => sum + a.cartCount, 0));
173
174
  const pickDateIso = p.planning?.pickAssignments?.[0]?.date?.toISOString?.().slice(0, 10) ??
174
175
  new Date().toISOString().slice(0, 10);
175
176
  const pakbonLines = lines.map((l) => ({
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState } from "react";
3
3
  import { Button, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogDescription, AlertDialogTitle, toast, } from "@flowselections/core";
4
4
  import { Plus, Pencil, Trash2, Truck, ArrowUp, ArrowDown } from "lucide-react";
5
- import { ResponsiveAlertDialogBody, ResponsiveAlertDialogContent, ResponsiveAlertDialogFooter, ResponsiveAlertDialogHeader, } from "../../components/ui/responsive-dialog";
5
+ import { ResponsiveAlertDialogBody, ResponsiveAlertDialogContent, ResponsiveAlertDialogFooter, ResponsiveAlertDialogHeader, } from "../ui/responsive-dialog";
6
6
  import { Link } from "@tanstack/react-router";
7
7
  import { useServerFn } from "@tanstack/react-start";
8
8
  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
@@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from "react";
3
3
  import { useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { useServerFn } from "@tanstack/react-start";
5
5
  import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Dialog, DialogTitle, DialogDescription, Input, Label, Textarea, toast, } from "@flowselections/core";
6
- import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../../components/ui/responsive-dialog";
6
+ import { ResponsiveDialogBody, ResponsiveDialogContent, ResponsiveDialogFooter, ResponsiveDialogHeader, } from "../ui/responsive-dialog";
7
7
  import { applyReschedule, listRescheduleHistory, previewReschedule, getPickMoments, setPickDay, } from "../../lib/logisticsEngine/reschedule";
8
8
  import { ENGINE_TASKS_KEY } from "../../hooks/useEngineTasks";
9
9
  import { listCarriers } from "../../lib/carriers.functions";
@@ -1 +1 @@
1
- {"version":3,"file":"WeekAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/WeekAgendaView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAKjE,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,QAAQ,GAAG,oBAAoB,GAAG,MAAM,CAAC;AAEnF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC;IACxD,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AA+DD,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,UAAU,EACV,KAAK,EACL,YAAY,EACZ,UAAU,GACX,EAAE,mBAAmB,+BAwJrB"}
1
+ {"version":3,"file":"WeekAgendaView.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/WeekAgendaView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAOjE,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,QAAQ,GAAG,oBAAoB,GAAG,MAAM,CAAC;AAEnF,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,KAAK,IAAI,CAAC;IACxD,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AA4DD,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,UAAU,EACV,KAAK,EACL,YAAY,EACZ,UAAU,GACX,EAAE,mBAAmB,+BA8JrB"}
@@ -4,6 +4,8 @@ import { Button, Badge } from "@flowselections/core";
4
4
  import { ChevronLeft, ChevronRight, Package, Truck, Sprout, Clock, CheckCircle2, Scale } from "lucide-react";
5
5
  import { isSameDay, startOfWeek, addDays } from "../../lib/pickDate";
6
6
  import { useCapacityCheck } from "../../hooks/useCapacityCheck";
7
+ import { usePickAgenda } from "../../hooks/usePickAgenda";
8
+ import { useConsistencyCheck } from "../../hooks/useConsistencyCheck";
7
9
  import { CapacityWarning } from "./CapacityWarning";
8
10
  import { toDayKey } from "../../lib/logisticsEngine/capacity";
9
11
  const WEEKDAY_LABELS = ["MAANDAG", "DINSDAG", "WOENSDAG", "DONDERDAG", "VRIJDAG", "ZATERDAG", "ZONDAG"];
@@ -29,11 +31,6 @@ function dateForPlanned(p, mode) {
29
31
  case "pick": return p.planning.scheduledPickDate ?? p.planning.pickStartDate;
30
32
  }
31
33
  }
32
- function isMovedEarlier(p, mode) {
33
- if (mode !== "pick" || !p.planning)
34
- return false;
35
- return Boolean(p.planning.movedEarlier);
36
- }
37
34
  const MODE_LABELS = {
38
35
  delivery: { summary: "Leveringen deze week" },
39
36
  pickup: { summary: "Ophalingen deze week" },
@@ -58,6 +55,8 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
58
55
  // de dichtstbijzijnde week met orders.
59
56
  const jumpedToRef = useRef(null);
60
57
  useEffect(() => {
58
+ if (mode === "pick")
59
+ return; // pickagenda komt uit de Engine-RPC
61
60
  if (isLoading || planned.length > 0 || !jumpToDate)
62
61
  return;
63
62
  if (jumpedToRef.current === jumpToDate)
@@ -67,58 +66,51 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
67
66
  return;
68
67
  jumpedToRef.current = jumpToDate;
69
68
  setCursor(startOfWeek(target));
70
- }, [jumpToDate, planned.length, isLoading]);
69
+ }, [jumpToDate, planned.length, isLoading, mode]);
70
+ // Pickagenda komt volledig uit de Engine-RPC; deze module rekent zelf geen
71
+ // pickdagen meer uit en doet geen datum-, werkdagen- of tijdzonelogica.
72
+ const pickAgenda = usePickAgenda(mode === "pick" ? toDayKey(weekStart) : null, mode === "pick" ? toDayKey(weekEnd) : null);
71
73
  const days = useMemo(() => {
72
74
  return Array.from({ length: 7 }, (_, i) => {
73
75
  const date = addDays(weekStart, i);
76
+ if (mode === "pick") {
77
+ // Dagen zonder rij tonen simpelweg nullen.
78
+ const row = pickAgenda.byDate.get(toDayKey(date));
79
+ return {
80
+ date,
81
+ entries: [],
82
+ shiftedCount: row?.shiftedCount ?? 0,
83
+ bestellingen: row?.orderCount ?? 0,
84
+ karren: row?.cartCount ?? 0,
85
+ planten: row?.plantCount ?? 0,
86
+ verzendklaar: row?.ready ?? 0,
87
+ voorbereiding: row?.inPreparation ?? 0,
88
+ };
89
+ }
74
90
  const entries = [];
75
91
  for (const p of planned) {
76
92
  if (!p.planning)
77
93
  continue;
78
- if (mode === "pick") {
79
- const assigns = p.planning.pickAssignments ?? [
80
- { date: p.planning.scheduledPickDate ?? p.planning.pickStartDate, cartCount: p.order.cartCount ?? 0 },
81
- ];
82
- const total = assigns.length;
83
- assigns.forEach((a, idx) => {
84
- if (!isSameDay(a.date, date))
85
- return;
86
- const totalCarts = p.order.cartCount ?? 0;
87
- const ratio = totalCarts > 0 ? a.cartCount / totalCarts : 1 / Math.max(total, 1);
88
- entries.push({
89
- order: p.order,
90
- cartCount: a.cartCount,
91
- plantCount: Math.round((p.order.plantCount ?? 0) * ratio),
92
- movedEarlier: a.date.getTime() !== p.planning.pickStartDate.getTime(),
93
- splitPart: total > 1 ? { index: idx + 1, total } : undefined,
94
- reason: p.planning.reason,
95
- carrierName: p.planning.carrierName,
96
- });
97
- });
98
- }
99
- else {
100
- const d = dateForPlanned(p, mode);
101
- if (!d || !isSameDay(d, date))
102
- continue;
103
- entries.push({
104
- order: p.order,
105
- cartCount: p.order.cartCount ?? 0,
106
- plantCount: p.order.plantCount ?? 0,
107
- movedEarlier: false,
108
- reason: p.planning.reason,
109
- carrierName: p.planning.carrierName,
110
- });
111
- }
94
+ const d = dateForPlanned(p, mode);
95
+ if (!d || !isSameDay(d, date))
96
+ continue;
97
+ entries.push({
98
+ order: p.order,
99
+ cartCount: (p.planning.pickAssignments ?? []).reduce((sum, a) => sum + a.cartCount, 0),
100
+ plantCount: p.order.plantCount ?? 0,
101
+ movedEarlier: false,
102
+ reason: p.planning.reason,
103
+ carrierName: p.planning.carrierName,
104
+ });
112
105
  }
113
106
  const karren = entries.reduce((s, e) => s + e.cartCount, 0);
114
107
  const planten = entries.reduce((s, e) => s + e.plantCount, 0);
115
108
  const verzendklaar = entries.filter((e) => isOrderReady(e.order)).length;
116
109
  const voorbereiding = entries.length - verzendklaar;
117
- const movedEarlierOrderIds = new Set(entries.filter((e) => e.movedEarlier).map((e) => e.order.id));
118
110
  return {
119
111
  date,
120
112
  entries,
121
- movedEarlierOrderIds,
113
+ shiftedCount: 0,
122
114
  bestellingen: entries.length,
123
115
  karren,
124
116
  planten,
@@ -126,7 +118,7 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
126
118
  voorbereiding,
127
119
  };
128
120
  });
129
- }, [planned, weekStart, mode]);
121
+ }, [planned, weekStart, mode, pickAgenda.byDate]);
130
122
  const totals = useMemo(() => {
131
123
  const leveringen = days.reduce((s, d) => s + d.bestellingen, 0);
132
124
  const verzendklaar = days.reduce((s, d) => s + d.verzendklaar, 0);
@@ -134,6 +126,7 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
134
126
  const totaalStuks = days.reduce((s, d) => s + d.planten, 0);
135
127
  return { leveringen, verzendklaar, voorbereiding, totaalStuks };
136
128
  }, [days]);
129
+ const { misaligned } = useConsistencyCheck();
137
130
  const capacity = useCapacityCheck(toDayKey(weekStart), toDayKey(weekEnd));
138
131
  const headerLabel = `Week ${weekNumber} - ${weekStart.getDate()} ${MONTHS_NL_SHORT[weekStart.getMonth()]}. t/m ${weekEnd.getDate()} ${MONTHS_NL_SHORT[weekEnd.getMonth()]}. ${weekEnd.getFullYear()}`;
139
132
  const goPrev = () => setCursor((c) => addDays(c, -7));
@@ -143,7 +136,7 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
143
136
  d.setHours(0, 0, 0, 0);
144
137
  setCursor(startOfWeek(d));
145
138
  };
146
- return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("h2", { className: "text-xl font-semibold tracking-tight", children: title ?? headerLabel }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: goPrev, children: [_jsx(ChevronLeft, { className: "mr-1 h-4 w-4" }), " Vorige week"] }), _jsx(Button, { variant: "outline", size: "sm", onClick: goToday, children: "Deze week" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: goNext, children: ["Volgende week ", _jsx(ChevronRight, { className: "ml-1 h-4 w-4" })] })] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4", children: [_jsx(SummaryCard, { label: MODE_LABELS[mode].summary, value: totals.leveringen, tone: "default" }), _jsx(SummaryCard, { label: "Verzendklaar", value: totals.verzendklaar, tone: "success" }), _jsx(SummaryCard, { label: "In voorbereiding", value: totals.voorbereiding, tone: "muted" }), _jsx(SummaryCard, { label: "Totaal stuks", value: totals.totaalStuks, tone: "default" })] }), _jsx("div", { className: "max-h-[520px] space-y-2 overflow-y-auto rounded-lg border bg-muted/20 p-2", children: isLoading ? (_jsx("div", { className: "rounded-lg border bg-card p-10 text-center text-sm text-muted-foreground", children: "Laden\u2026" })) : (days.map((day) => (_jsx(DayRow, { day: day, onOrderClick: onOrderClick, onDayClick: onDayClick, capacity: mode === "delivery" ? undefined : capacity.byDate.get(toDayKey(day.date)) }, day.date.toISOString())))) })] }));
139
+ return (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3", children: [_jsx("h2", { className: "text-xl font-semibold tracking-tight", children: title ?? headerLabel }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: goPrev, children: [_jsx(ChevronLeft, { className: "mr-1 h-4 w-4" }), " Vorige week"] }), _jsx(Button, { variant: "outline", size: "sm", onClick: goToday, children: "Deze week" }), _jsxs(Button, { variant: "outline", size: "sm", onClick: goNext, children: ["Volgende week ", _jsx(ChevronRight, { className: "ml-1 h-4 w-4" })] })] })] }), misaligned && (_jsx("div", { className: "rounded-md border bg-muted/40 px-3 py-2 text-xs text-muted-foreground", children: "Aantallen worden opnieuw uitgelijnd." })), _jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4", children: [_jsx(SummaryCard, { label: MODE_LABELS[mode].summary, value: totals.leveringen, tone: "default" }), _jsx(SummaryCard, { label: "Verzendklaar", value: totals.verzendklaar, tone: "success" }), _jsx(SummaryCard, { label: "In voorbereiding", value: totals.voorbereiding, tone: "muted" }), _jsx(SummaryCard, { label: "Totaal stuks", value: totals.totaalStuks, tone: "default" })] }), _jsx("div", { className: "max-h-[520px] space-y-2 overflow-y-auto rounded-lg border bg-muted/20 p-2", children: isLoading || (mode === "pick" && pickAgenda.isLoading) ? (_jsx("div", { className: "rounded-lg border bg-card p-10 text-center text-sm text-muted-foreground", children: "Laden\u2026" })) : (days.map((day) => (_jsx(DayRow, { day: day, onOrderClick: onOrderClick, onDayClick: onDayClick, capacity: mode === "delivery" ? undefined : capacity.byDate.get(toDayKey(day.date)) }, day.date.toISOString())))) })] }));
147
140
  }
148
141
  function SummaryCard({ label, value, tone, }) {
149
142
  const valueClass = tone === "success" ? "text-green-600" : "text-foreground";
@@ -154,7 +147,7 @@ function DayRow({ day, onOrderClick, onDayClick, capacity, }) {
154
147
  const today = new Date();
155
148
  today.setHours(0, 0, 0, 0);
156
149
  const isToday = isSameDay(day.date, today);
157
- return (_jsxs("div", { className: `rounded-lg border bg-card ${isToday ? "ring-1 ring-primary" : ""}`, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-6 px-4 py-4 sm:px-6", children: [_jsxs("div", { className: "w-20 shrink-0", children: [_jsx("div", { className: "text-xs font-medium tracking-wider text-muted-foreground", children: WEEKDAY_LABELS[weekdayIdx] }), _jsx("div", { className: "mt-1 text-3xl font-bold leading-none", children: day.date.getDate() }), _jsxs("div", { className: "text-sm text-muted-foreground", children: [MONTHS_NL_SHORT[day.date.getMonth()], "."] })] }), _jsxs("div", { className: "flex flex-1 flex-wrap items-center gap-6", children: [_jsx(Stat, { icon: _jsx(Package, { className: "h-4 w-4 text-slate-500" }), bg: "bg-slate-100", value: day.bestellingen, label: "bestellingen" }), _jsx(Stat, { icon: _jsx(Truck, { className: "h-4 w-4 text-amber-600" }), bg: "bg-amber-100", value: day.karren, label: "karren" }), _jsx(Stat, { icon: _jsx(Sprout, { className: "h-4 w-4 text-green-600" }), bg: "bg-green-100", value: day.planten, label: "planten" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [day.movedEarlierOrderIds.size > 0 && (_jsxs(Badge, { className: "bg-purple-500/10 text-purple-700 hover:bg-purple-500/10", variant: "secondary", title: "Aantal orders dat vervroegd is ingepland door Pickverdeling.", children: [_jsx(Scale, { className: "mr-1 h-3.5 w-3.5" }), day.movedEarlierOrderIds.size, " vervroegd"] })), day.verzendklaar > 0 && (_jsxs(Badge, { className: "bg-green-500/15 text-green-700 hover:bg-green-500/15", variant: "secondary", children: [_jsx(CheckCircle2, { className: "mr-1 h-3.5 w-3.5" }), day.verzendklaar, " verzendklaar"] })), day.voorbereiding > 0 && (_jsxs(Badge, { className: "bg-blue-500/10 text-blue-700 hover:bg-blue-500/10", variant: "secondary", children: [_jsx(Clock, { className: "mr-1 h-3.5 w-3.5" }), day.voorbereiding, " in voorbereiding"] })), onDayClick && day.bestellingen > 0 && (_jsx(Button, { size: "sm", variant: "outline", onClick: () => onDayClick(day.date), children: "Dagdetails" }))] })] }), _jsx(CapacityWarning, { day: capacity, className: "mx-4 mb-4 sm:mx-6" })] }));
150
+ return (_jsxs("div", { className: `rounded-lg border bg-card ${isToday ? "ring-1 ring-primary" : ""}`, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-6 px-4 py-4 sm:px-6", children: [_jsxs("div", { className: "w-20 shrink-0", children: [_jsx("div", { className: "text-xs font-medium tracking-wider text-muted-foreground", children: WEEKDAY_LABELS[weekdayIdx] }), _jsx("div", { className: "mt-1 text-3xl font-bold leading-none", children: day.date.getDate() }), _jsxs("div", { className: "text-sm text-muted-foreground", children: [MONTHS_NL_SHORT[day.date.getMonth()], "."] })] }), _jsxs("div", { className: "flex flex-1 flex-wrap items-center gap-6", children: [_jsx(Stat, { icon: _jsx(Package, { className: "h-4 w-4 text-slate-500" }), bg: "bg-slate-100", value: day.bestellingen, label: "bestellingen" }), _jsx(Stat, { icon: _jsx(Truck, { className: "h-4 w-4 text-amber-600" }), bg: "bg-amber-100", value: day.karren, label: "karren" }), _jsx(Stat, { icon: _jsx(Sprout, { className: "h-4 w-4 text-green-600" }), bg: "bg-green-100", value: day.planten, label: "planten" })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [day.shiftedCount > 0 && (_jsxs(Badge, { className: "bg-purple-500/10 text-purple-700 hover:bg-purple-500/10", variant: "secondary", title: "Aantal orders dat te laat is aangemeld en door de Engine is verschoven.", children: [_jsx(Scale, { className: "mr-1 h-3.5 w-3.5" }), day.shiftedCount, " te laat aangemeld"] })), day.verzendklaar > 0 && (_jsxs(Badge, { className: "bg-green-500/15 text-green-700 hover:bg-green-500/15", variant: "secondary", children: [_jsx(CheckCircle2, { className: "mr-1 h-3.5 w-3.5" }), day.verzendklaar, " verzendklaar"] })), day.voorbereiding > 0 && (_jsxs(Badge, { className: "bg-blue-500/10 text-blue-700 hover:bg-blue-500/10", variant: "secondary", children: [_jsx(Clock, { className: "mr-1 h-3.5 w-3.5" }), day.voorbereiding, " in voorbereiding"] })), onDayClick && day.bestellingen > 0 && (_jsx(Button, { size: "sm", variant: "outline", onClick: () => onDayClick(day.date), children: "Dagdetails" }))] })] }), _jsx(CapacityWarning, { day: capacity, className: "mx-4 mb-4 sm:mx-6" })] }));
158
151
  }
159
152
  function Stat({ icon, bg, value, label, }) {
160
153
  return (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: `grid h-8 w-8 place-items-center rounded-full ${bg}`, children: icon }), _jsxs("div", { children: [_jsx("div", { className: "text-lg font-bold leading-none", children: value }), _jsx("div", { className: "text-xs text-muted-foreground", children: label })] })] }));
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Gedeelde karindex van de Engine. Dit is de enige bron voor karrentellingen:
3
+ * taak-payloads (cartCount) en eigen tellingen op order_carts zijn niet meer
4
+ * toegestaan. Een gedeelde kar telt bij elke order waar hij in zit.
5
+ */
6
+ export declare function useCartIndex(from: string | null, to: string | null): {
7
+ cartsForOrder: (...refs: (string | null | undefined)[]) => number | null;
8
+ isLoading: boolean;
9
+ error: string | null;
10
+ };
11
+ //# sourceMappingURL=useCartIndex.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useCartIndex.d.ts","sourceRoot":"","sources":["../../src/hooks/useCartIndex.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;6BAoBrB,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,KAAG,MAAM,GAAG,IAAI;;;EA0B1F"}
@@ -0,0 +1,56 @@
1
+ import { useCallback } from "react";
2
+ import { useQuery } from "@tanstack/react-query";
3
+ import { supabase } from "../integrations/supabase/moduleClient";
4
+ /**
5
+ * Gedeelde karindex van de Engine. Dit is de enige bron voor karrentellingen:
6
+ * taak-payloads (cartCount) en eigen tellingen op order_carts zijn niet meer
7
+ * toegestaan. Een gedeelde kar telt bij elke order waar hij in zit.
8
+ */
9
+ export function useCartIndex(from, to) {
10
+ const query = useQuery({
11
+ queryKey: ["logistics-cart-index", from, to],
12
+ enabled: !!from && !!to,
13
+ staleTime: 30000,
14
+ retry: false,
15
+ refetchInterval: 60000,
16
+ queryFn: async () => {
17
+ const { data, error } = await supabase.rpc("logistics_cart_index", {
18
+ p_from: from,
19
+ p_to: to,
20
+ });
21
+ if (error)
22
+ throw new Error(error.message);
23
+ return Array.isArray(data) ? data : [];
24
+ },
25
+ });
26
+ const rows = query.data;
27
+ // Per orderreferentie de unieke karren tellen.
28
+ const cartsForOrder = useCallback((...refs) => {
29
+ const perOrder = new Map();
30
+ for (const r of rows ?? []) {
31
+ const cart = r.cart_id ?? "";
32
+ if (!cart)
33
+ continue;
34
+ for (const ref of [r.order_number, r.order_uuid]) {
35
+ if (!ref)
36
+ continue;
37
+ const set = perOrder.get(ref) ?? new Set();
38
+ set.add(cart);
39
+ perOrder.set(ref, set);
40
+ }
41
+ }
42
+ for (const ref of refs) {
43
+ if (!ref)
44
+ continue;
45
+ const set = perOrder.get(ref);
46
+ if (set)
47
+ return set.size;
48
+ }
49
+ return null;
50
+ }, [rows]);
51
+ return {
52
+ cartsForOrder,
53
+ isLoading: query.isLoading,
54
+ error: query.error instanceof Error ? query.error.message : null,
55
+ };
56
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Eenmalige controle bij het laden van de agenda. Deze module corrigeert niets
3
+ * zelf: bij een afwijking tonen we alleen een discrete melding.
4
+ */
5
+ export declare function useConsistencyCheck(): {
6
+ misaligned: boolean;
7
+ };
8
+ //# sourceMappingURL=useConsistencyCheck.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useConsistencyCheck.d.ts","sourceRoot":"","sources":["../../src/hooks/useConsistencyCheck.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,mBAAmB;;EAelC"}
@@ -0,0 +1,22 @@
1
+ import { useQuery } from "@tanstack/react-query";
2
+ import { supabase } from "../integrations/supabase/moduleClient";
3
+ /**
4
+ * Eenmalige controle bij het laden van de agenda. Deze module corrigeert niets
5
+ * zelf: bij een afwijking tonen we alleen een discrete melding.
6
+ */
7
+ export function useConsistencyCheck() {
8
+ const query = useQuery({
9
+ queryKey: ["logistics-consistency-check"],
10
+ staleTime: Infinity,
11
+ retry: false,
12
+ refetchOnWindowFocus: false,
13
+ queryFn: async () => {
14
+ const { data, error } = await supabase.rpc("logistics_consistency_check");
15
+ if (error)
16
+ return true; // Geen melding tonen als de controle zelf faalt.
17
+ const rows = Array.isArray(data) ? data : [];
18
+ return rows.every((r) => r.ok !== false);
19
+ },
20
+ });
21
+ return { misaligned: query.data === false };
22
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"useOrderPlanning.d.ts","sourceRoot":"","sources":["../../src/hooks/useOrderPlanning.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAK/D,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAwC,KAAK,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAWtG;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,iBAAiB,GACjB,gBAAgB,GAChB,cAAc,GACd,oBAAoB,CAAC;AAEzB,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,6EAA6E;IAC7E,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B,qEAAqE;IACrE,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,gFAAgF;IAChF,cAAc,EAAE,OAAO,CAAC;IACxB,iDAAiD;IACjD,KAAK,EAAE,aAAa,CAAC;IACrB,uEAAuE;IACvE,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,wEAAwE;IACxE,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,8EAA8E;IAC9E,WAAW,EAAE,YAAY,EAAE,CAAC;IAC5B,wDAAwD;IACxD,iBAAiB,EAAE,YAAY,EAAE,CAAC;IAClC,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAGD,MAAM,WAAW,uBAAuB;IACtC,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,uBAAuB,GAAG,sBAAsB,CAiTxF"}
1
+ {"version":3,"file":"useOrderPlanning.d.ts","sourceRoot":"","sources":["../../src/hooks/useOrderPlanning.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAK/D,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAwC,KAAK,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAWtG;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,iBAAiB,GACjB,gBAAgB,GAChB,cAAc,GACd,oBAAoB,CAAC;AAEzB,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,6EAA6E;IAC7E,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC/B,qEAAqE;IACrE,UAAU,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,gFAAgF;IAChF,cAAc,EAAE,OAAO,CAAC;IACxB,iDAAiD;IACjD,KAAK,EAAE,aAAa,CAAC;IACrB,uEAAuE;IACvE,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,iFAAiF;IACjF,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,6EAA6E;IAC7E,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,wEAAwE;IACxE,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,8EAA8E;IAC9E,WAAW,EAAE,YAAY,EAAE,CAAC;IAC5B,wDAAwD;IACxD,iBAAiB,EAAE,YAAY,EAAE,CAAC;IAClC,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,mEAAmE;IACnE,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAGD,MAAM,WAAW,uBAAuB;IACtC,8EAA8E;IAC9E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,gBAAgB,CAAC,KAAK,CAAC,EAAE,uBAAuB,GAAG,sBAAsB,CAqTxF"}
@@ -7,6 +7,7 @@ import { safeServerCall } from "../lib/serverFnError";
7
7
  import { normalizePickupMoments, sanitizeWeekdays, selectCarrierForTarget } from "../lib/carrierPlanning";
8
8
  import { calculateOrderPlanning, DEFAULT_PICKUP_MOMENT, } from "../lib/planning";
9
9
  import { useEngineTasks } from "./useEngineTasks";
10
+ import { useCartIndex } from "./useCartIndex";
10
11
  import { emitEngineEvent, listComposeTaskRefs } from "../lib/logisticsEngine/engine";
11
12
  import { orderRefsFor } from "../lib/orderRefs";
12
13
  import { MAX_ATTEMPTS, emitQueueStatus, markInFlight, markResult, resetEmitQueue, selectEmitBatch, } from "../lib/logisticsEngine/emitQueue";
@@ -102,6 +103,8 @@ export function useOrderPlanning(range) {
102
103
  refetchOnWindowFocus: true,
103
104
  });
104
105
  const composeExists = composeExistsQuery.data;
106
+ // Karrentelling komt uit de gedeelde karindex van de Engine.
107
+ const { cartsForOrder } = useCartIndex(from, to);
105
108
  const planned = useMemo(() => {
106
109
  const orders = orderList;
107
110
  const carriers = carriersQuery.data?.carriers ?? [];
@@ -169,7 +172,8 @@ export function useOrderPlanning(range) {
169
172
  // Order zonder bruikbare leverdatum: taak bestaat, planning niet.
170
173
  return fallback("task_without_start");
171
174
  }
172
- const carrierCount = order.cartCount ?? task.payload?.loadCarriers?.length ?? 1;
175
+ // Karren komen uitsluitend uit de gedeelde karindex van de Engine.
176
+ const carrierCount = cartsForOrder(order.orderNumber, order.id) ?? 0;
173
177
  const planning = {
174
178
  ...base,
175
179
  pickStartDate: start,
@@ -187,7 +191,7 @@ export function useOrderPlanning(range) {
187
191
  };
188
192
  return { order, planning, engineTask: task, awaitingEngine: false, state: "planned", hasComposeTask: true };
189
193
  });
190
- }, [orderList, carriersQuery.data, composeByRef, pickByRef, composeExists]);
194
+ }, [orderList, carriersQuery.data, composeByRef, pickByRef, composeExists, cartsForOrder]);
191
195
  // Orders zonder taak melden we bij de Engine; zelf indelen mag niet.
192
196
  // De meldlus is begrensd: max MAX_PER_ROUND orders per ronde, pogingenteller
193
197
  // met backoff per order, en na MAX_ATTEMPTS definitief opgeven met melding.
@@ -0,0 +1,32 @@
1
+ /** Eén order op een pickdag volgens de Engine. */
2
+ export interface PickAgendaOrder {
3
+ taskId: string;
4
+ orderRef: string;
5
+ status: string;
6
+ carts: number;
7
+ plants: number;
8
+ shifted: boolean;
9
+ }
10
+ /** Eén dagregel uit `logistics_pick_agenda`. */
11
+ export interface PickAgendaDay {
12
+ pickDate: string;
13
+ orderCount: number;
14
+ cartCount: number;
15
+ plantCount: number;
16
+ inPreparation: number;
17
+ ready: number;
18
+ shiftedCount: number;
19
+ orders: PickAgendaOrder[];
20
+ }
21
+ /**
22
+ * Pickagenda van de Logistics Engine. De RPC is de enige bron: deze module
23
+ * berekent zelf geen pickdagen meer en doet geen datum- of tijdzonelogica.
24
+ * Dagen zonder rij tonen simpelweg nullen.
25
+ */
26
+ export declare function usePickAgenda(from: string | null, to: string | null): {
27
+ days: PickAgendaDay[];
28
+ byDate: Map<string, PickAgendaDay>;
29
+ isLoading: boolean;
30
+ error: string | null;
31
+ };
32
+ //# sourceMappingURL=usePickAgenda.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePickAgenda.d.ts","sourceRoot":"","sources":["../../src/hooks/usePickAgenda.ts"],"names":[],"mappings":"AAIA,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,gDAAgD;AAChD,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AA4BD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;;;;;EAyDnE"}