@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.
- package/dist-lib/components/afhandeling/ControlePage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/ControlePage.js +86 -10
- package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/DagDetailDialog.js +4 -3
- package/dist-lib/components/afhandeling/VervoerdersPage.js +1 -1
- package/dist-lib/components/afhandeling/VerzetOrderDialog.js +1 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.js +38 -45
- package/dist-lib/hooks/useCartIndex.d.ts +11 -0
- package/dist-lib/hooks/useCartIndex.d.ts.map +1 -0
- package/dist-lib/hooks/useCartIndex.js +56 -0
- package/dist-lib/hooks/useConsistencyCheck.d.ts +8 -0
- package/dist-lib/hooks/useConsistencyCheck.d.ts.map +1 -0
- package/dist-lib/hooks/useConsistencyCheck.js +22 -0
- package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
- package/dist-lib/hooks/useOrderPlanning.js +6 -2
- package/dist-lib/hooks/usePickAgenda.d.ts +32 -0
- package/dist-lib/hooks/usePickAgenda.d.ts.map +1 -0
- package/dist-lib/hooks/usePickAgenda.js +75 -0
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +1342 -747
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +1342 -747
- package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.server.js +1 -1
- package/dist-lib/integrations/supabase/moduleClient.d.ts +1342 -747
- package/dist-lib/integrations/supabase/moduleClient.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +1354 -775
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/afhandelingSettings.functions.d.ts +2501 -1307
- package/dist-lib/lib/afhandelingSettings.functions.d.ts.map +1 -1
- package/dist-lib/lib/carriers.functions.d.ts +6356 -3381
- package/dist-lib/lib/carriers.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartAssignments.functions.d.ts +6356 -3381
- package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.d.ts +12821 -6902
- package/dist-lib/lib/cartControl.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartControl.functions.js +92 -127
- package/dist-lib/lib/floriday.functions.d.ts +1345 -748
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ControlePage.d.ts","sourceRoot":"","sources":["../../../src/components/afhandeling/ControlePage.tsx"],"names":[],"mappings":"
|
|
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 "
|
|
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
|
|
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
|
-
|
|
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:
|
|
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,
|
|
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 "
|
|
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
|
-
|
|
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.
|
|
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 "
|
|
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 "
|
|
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;
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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.
|
|
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;
|
|
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
|
-
|
|
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"}
|