@flowselections/floriday-afhandeling-module 1.0.58 → 1.0.63
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/AutoSendStatus.d.ts +12 -0
- package/dist-lib/components/afhandeling/AutoSendStatus.d.ts.map +1 -0
- package/dist-lib/components/afhandeling/AutoSendStatus.js +86 -0
- package/dist-lib/components/afhandeling/DagDetailDialog.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/DagDetailDialog.js +40 -24
- package/dist-lib/components/afhandeling/LeveringenPage.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/LeveringenPage.js +3 -1
- package/dist-lib/components/afhandeling/NuVersturenButton.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/NuVersturenButton.js +4 -4
- package/dist-lib/components/afhandeling/PickAgendaView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/PickAgendaView.js +9 -7
- package/dist-lib/components/afhandeling/ScanControleView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/ScanControleView.js +2 -2
- package/dist-lib/components/afhandeling/VerzetOrderDialog.js +3 -3
- package/dist-lib/components/afhandeling/WeekAgendaView.d.ts.map +1 -1
- package/dist-lib/components/afhandeling/WeekAgendaView.js +60 -10
- package/dist-lib/components/settings/AfhandelingSettingsCard.js +1 -1
- package/dist-lib/hooks/useConsistencyCheck.d.ts +1 -1
- package/dist-lib/hooks/useConsistencyCheck.d.ts.map +1 -1
- package/dist-lib/hooks/useConsistencyCheck.js +5 -3
- package/dist-lib/hooks/useOrderPlanning.d.ts.map +1 -1
- package/dist-lib/hooks/useOrderPlanning.js +7 -6
- package/dist-lib/hooks/usePickAgenda.d.ts +4 -0
- package/dist-lib/hooks/usePickAgenda.d.ts.map +1 -1
- package/dist-lib/hooks/usePickAgenda.js +36 -5
- package/dist-lib/integrations/supabase/previewAuthStorage.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/previewAuthStorage.js +22 -4
- package/dist-lib/integrations/supabase/types.d.ts +609 -2
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +1 -0
- package/dist-lib/lib/cartAssignments.functions.d.ts +4 -0
- package/dist-lib/lib/cartAssignments.functions.d.ts.map +1 -1
- package/dist-lib/lib/cartAssignments.functions.js +5 -1
- package/dist-lib/lib/floriday.functions.d.ts +2 -0
- package/dist-lib/lib/floriday.functions.d.ts.map +1 -1
- package/dist-lib/lib/floriday.functions.js +1 -0
- package/dist-lib/lib/floridayLoading.functions.d.ts +18 -0
- package/dist-lib/lib/floridayLoading.functions.d.ts.map +1 -1
- package/dist-lib/lib/floridayLoading.functions.js +28 -9
- package/dist-lib/lib/printing.js +1 -1
- package/dist-lib/lib/serverRoutes/autoSend.handler.d.ts +3 -0
- package/dist-lib/lib/serverRoutes/autoSend.handler.d.ts.map +1 -0
- package/dist-lib/lib/serverRoutes/autoSend.handler.js +123 -0
- package/dist-lib/lib/serverRoutes/index.d.ts +1 -0
- package/dist-lib/lib/serverRoutes/index.d.ts.map +1 -1
- package/dist-lib/lib/serverRoutes/index.js +1 -0
- package/dist-lib/moduleDefinition.d.ts.map +1 -1
- package/dist-lib/moduleDefinition.js +5 -2
- package/package.json +13 -2
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { supabase } from "../../integrations/supabase/moduleClient";
|
|
5
|
+
import { isExcludedOrderStatus, normalizeOrderStatus } from "../../lib/orderStatus";
|
|
3
6
|
import { Button, Badge } from "@flowselections/core";
|
|
4
7
|
import { ChevronLeft, ChevronRight, Package, Truck, Sprout, Clock, CheckCircle2, Scale } from "lucide-react";
|
|
5
8
|
import { isSameDay, startOfWeek, addDays } from "../../lib/pickDate";
|
|
@@ -8,6 +11,20 @@ import { usePickAgenda } from "../../hooks/usePickAgenda";
|
|
|
8
11
|
import { useConsistencyCheck } from "../../hooks/useConsistencyCheck";
|
|
9
12
|
import { CapacityWarning } from "./CapacityWarning";
|
|
10
13
|
import { toDayKey } from "../../lib/logisticsEngine/capacity";
|
|
14
|
+
const SHIPPED = new Set(["verzonden", "gereed voor facturatie", "gefactureerd", "geleverd", "afgeleverd", "afgerond", "afgehandeld", "afgemeld", "shipped", "delivered", "completed", "invoiced"]);
|
|
15
|
+
/** Eén indeling per order. Floriday herkend op Floriday-id; afmeldstand uit de order. */
|
|
16
|
+
function classify(o, scanned) {
|
|
17
|
+
if (/annul|cancel/i.test(o.status ?? "") || isExcludedOrderStatus(o.status))
|
|
18
|
+
return "uit";
|
|
19
|
+
if (SHIPPED.has(normalizeOrderStatus(o.status))) {
|
|
20
|
+
if (o.salesOrderId && o.floridayFulfilled !== true)
|
|
21
|
+
return "niet_afgemeld";
|
|
22
|
+
return "afgehandeld";
|
|
23
|
+
}
|
|
24
|
+
if (scanned || isOrderReady(o))
|
|
25
|
+
return "verzendklaar";
|
|
26
|
+
return "voorbereiding";
|
|
27
|
+
}
|
|
11
28
|
const WEEKDAY_LABELS = ["MAANDAG", "DINSDAG", "WOENSDAG", "DONDERDAG", "VRIJDAG", "ZATERDAG", "ZONDAG"];
|
|
12
29
|
const MONTHS_NL_SHORT = ["jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"];
|
|
13
30
|
function isOrderReady(o) {
|
|
@@ -87,6 +104,30 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
87
104
|
// Pickagenda komt volledig uit de Engine-RPC; deze module rekent zelf geen
|
|
88
105
|
// pickdagen meer uit en doet geen datum-, werkdagen- of tijdzonelogica.
|
|
89
106
|
const pickAgenda = usePickAgenda(mode === "pick" ? toDayKey(weekStart) : null, mode === "pick" ? toDayKey(weekEnd) : null);
|
|
107
|
+
// Orders waarvan alle karren goedgekeurd gescand zijn (op order-id).
|
|
108
|
+
const orderIds = useMemo(() => (mode === "pick" ? [] : planned.map((p) => p.order.id).sort()), [planned, mode]);
|
|
109
|
+
const scanQuery = useQuery({
|
|
110
|
+
queryKey: ["afhandeling-scanned-orders", orderIds],
|
|
111
|
+
enabled: orderIds.length > 0,
|
|
112
|
+
staleTime: 30000,
|
|
113
|
+
refetchInterval: 60000,
|
|
114
|
+
queryFn: async () => {
|
|
115
|
+
const all = new Map();
|
|
116
|
+
for (let i = 0; i < orderIds.length; i += 100) {
|
|
117
|
+
const { data, error } = await supabase
|
|
118
|
+
.from("logistics_control_scan").select("order_id, approved_at").in("order_id", orderIds.slice(i, i + 100));
|
|
119
|
+
if (error)
|
|
120
|
+
throw new Error(error.message);
|
|
121
|
+
for (const r of Array.isArray(data) ? data : []) {
|
|
122
|
+
if (!r.order_id)
|
|
123
|
+
continue;
|
|
124
|
+
all.set(r.order_id, (all.get(r.order_id) ?? true) && !!r.approved_at);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return [...all].filter(([, ok]) => ok).map(([id]) => id);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
const scannedIds = useMemo(() => new Set(scanQuery.data ?? []), [scanQuery.data]);
|
|
90
131
|
const days = useMemo(() => {
|
|
91
132
|
return Array.from({ length: 7 }, (_, i) => {
|
|
92
133
|
const date = addDays(weekStart, i);
|
|
@@ -104,6 +145,7 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
104
145
|
voorbereiding: row?.inPreparation ?? 0,
|
|
105
146
|
// Uitsluitend uit de Engine (taak afgemeld); geannuleerde orders zijn al weggelaten.
|
|
106
147
|
afgehandeld: row?.doneCount ?? 0,
|
|
148
|
+
nietAfgemeld: 0,
|
|
107
149
|
};
|
|
108
150
|
}
|
|
109
151
|
const entries = [];
|
|
@@ -113,7 +155,7 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
113
155
|
const d = dateForPlanned(p, mode);
|
|
114
156
|
if (!d || !isSameDay(d, date))
|
|
115
157
|
continue;
|
|
116
|
-
if (
|
|
158
|
+
if (classify(p.order, false) === "uit")
|
|
117
159
|
continue;
|
|
118
160
|
entries.push({
|
|
119
161
|
order: p.order,
|
|
@@ -126,8 +168,10 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
126
168
|
}
|
|
127
169
|
const karren = entries.reduce((s, e) => s + e.cartCount, 0);
|
|
128
170
|
const planten = entries.reduce((s, e) => s + e.plantCount, 0);
|
|
129
|
-
const
|
|
130
|
-
const
|
|
171
|
+
const cls = entries.map((e) => classify(e.order, scannedIds.has(e.order.id)));
|
|
172
|
+
const cnt = (b) => cls.filter((c) => c === b).length;
|
|
173
|
+
const verzendklaar = cnt("verzendklaar");
|
|
174
|
+
const voorbereiding = cnt("voorbereiding");
|
|
131
175
|
return {
|
|
132
176
|
date,
|
|
133
177
|
entries,
|
|
@@ -137,16 +181,19 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
137
181
|
planten,
|
|
138
182
|
verzendklaar,
|
|
139
183
|
voorbereiding,
|
|
140
|
-
afgehandeld:
|
|
184
|
+
afgehandeld: cnt("afgehandeld"),
|
|
185
|
+
nietAfgemeld: cnt("niet_afgemeld"),
|
|
141
186
|
};
|
|
142
187
|
});
|
|
143
|
-
}, [planned, weekStart, mode, pickAgenda.byDate]);
|
|
188
|
+
}, [planned, weekStart, mode, pickAgenda.byDate, scannedIds]);
|
|
144
189
|
const totals = useMemo(() => {
|
|
145
190
|
const leveringen = days.reduce((s, d) => s + d.bestellingen, 0);
|
|
146
191
|
const verzendklaar = days.reduce((s, d) => s + d.verzendklaar, 0);
|
|
147
192
|
const voorbereiding = days.reduce((s, d) => s + d.voorbereiding, 0);
|
|
148
193
|
const totaalStuks = days.reduce((s, d) => s + d.planten, 0);
|
|
149
|
-
|
|
194
|
+
const afgehandeld = days.reduce((s, d) => s + d.afgehandeld, 0);
|
|
195
|
+
const nietAfgemeld = days.reduce((s, d) => s + d.nietAfgemeld, 0);
|
|
196
|
+
return { leveringen, verzendklaar, voorbereiding, totaalStuks, afgehandeld, nietAfgemeld };
|
|
150
197
|
}, [days]);
|
|
151
198
|
const { misaligned } = useConsistencyCheck();
|
|
152
199
|
const capacity = useCapacityCheck(toDayKey(weekStart), toDayKey(weekEnd));
|
|
@@ -158,10 +205,10 @@ export function WeekAgendaView({ planned, mode, isLoading, onOrderClick, onDayCl
|
|
|
158
205
|
d.setHours(0, 0, 0, 0);
|
|
159
206
|
setCursor(startOfWeek(d));
|
|
160
207
|
};
|
|
161
|
-
return (_jsxs("div", { className: "flex h-full flex-col gap-4 overflow-hidden", 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: "
|
|
208
|
+
return (_jsxs("div", { className: "flex h-full flex-col gap-4 overflow-hidden", 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: "De aantallen worden bijgewerkt." })), _jsxs("div", { className: "grid shrink-0 grid-cols-1 gap-3 sm:grid-cols-3 lg:grid-cols-6", 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: "Afgehandeld", value: totals.afgehandeld, tone: "success" }), _jsx(SummaryCard, { label: "Afgehandeld, nog niet gemeld bij Floriday", value: totals.nietAfgemeld, tone: "warning" }), _jsx(SummaryCard, { label: "Totaal stuks", value: totals.totaalStuks, tone: "default" })] }), _jsx("div", { className: "min-h-0 flex-1 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())))) })] }));
|
|
162
209
|
}
|
|
163
210
|
function SummaryCard({ label, value, tone, }) {
|
|
164
|
-
const valueClass = tone === "success" ? "text-green-600" : "text-foreground";
|
|
211
|
+
const valueClass = tone === "success" ? "text-green-600" : tone === "warning" ? "text-amber-600" : "text-foreground";
|
|
165
212
|
return (_jsxs("div", { className: "rounded-lg border bg-card p-4", children: [_jsx("div", { className: `text-3xl font-bold ${valueClass}`, children: value }), _jsx("div", { className: "mt-1 text-sm text-muted-foreground", children: label })] }));
|
|
166
213
|
}
|
|
167
214
|
function DayRow({ day, onOrderClick, onDayClick, capacity, }) {
|
|
@@ -170,8 +217,11 @@ function DayRow({ day, onOrderClick, onDayClick, capacity, }) {
|
|
|
170
217
|
today.setHours(0, 0, 0, 0);
|
|
171
218
|
const isToday = isSameDay(day.date, today);
|
|
172
219
|
// Alles van deze dag is afgehandeld: toon een groene bevestiging.
|
|
173
|
-
const dagAfgesloten = day.bestellingen > 0 && day.afgehandeld >= day.bestellingen;
|
|
174
|
-
return (_jsxs("div", { className: `rounded-lg border bg-card ${isToday ? "ring-1 ring-primary" : ""}
|
|
220
|
+
const dagAfgesloten = day.bestellingen > 0 && day.afgehandeld >= day.bestellingen && day.nietAfgemeld === 0;
|
|
221
|
+
return (_jsxs("div", { className: `rounded-lg border bg-card ${isToday ? "ring-1 ring-primary" : ""} ${onDayClick && day.bestellingen > 0 ? "cursor-pointer transition-colors hover:bg-muted/40" : ""}`, role: onDayClick && day.bestellingen > 0 ? "button" : undefined, tabIndex: onDayClick && day.bestellingen > 0 ? 0 : undefined, onClick: onDayClick && day.bestellingen > 0 ? () => onDayClick(day.date) : undefined, onKeyDown: onDayClick && day.bestellingen > 0 ? (e) => { if (e.key === "Enter" || e.key === " ") {
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
onDayClick(day.date);
|
|
224
|
+
} } : undefined, 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: [dagAfgesloten && (_jsxs("div", { className: "flex items-center gap-2 rounded-md border border-green-600/30 bg-green-500/15 px-3 py-1.5 text-sm font-medium text-green-700", children: [_jsx(CheckCircle2, { className: "h-4 w-4" }), "Dag is afgesloten"] })), 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 planning is verschoven.", children: [_jsx(Scale, { className: "mr-1 h-3.5 w-3.5" }), day.shiftedCount, " te laat aangemeld"] })), day.nietAfgemeld > 0 && (_jsxs(Badge, { className: "bg-amber-500/15 text-amber-700 hover:bg-amber-500/15", variant: "secondary", children: [day.nietAfgemeld, " nog niet afgemeld bij Floriday"] })), 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: (e) => { e.stopPropagation(); onDayClick(day.date); }, children: "Dagdetails" }))] })] }), _jsx(CapacityWarning, { day: capacity, className: "mx-4 mb-4 sm:mx-6" })] }));
|
|
175
225
|
}
|
|
176
226
|
function Stat({ icon, bg, value, label, }) {
|
|
177
227
|
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 })] })] }));
|
|
@@ -47,5 +47,5 @@ export function AfhandelingSettingsCard() {
|
|
|
47
47
|
toast.error(e instanceof Error ? e.message : "Opslaan mislukt");
|
|
48
48
|
},
|
|
49
49
|
});
|
|
50
|
-
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CalendarClock, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Afhandeling \u2014 Planning" })] }), _jsx(CardDescription, { children: "De pickplanning (pickdagen, picktijd en verdeling over de week) wordt volledig bepaald door de
|
|
50
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CalendarClock, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Afhandeling \u2014 Planning" })] }), _jsx(CardDescription, { children: "De pickplanning (pickdagen, picktijd en verdeling over de week) wordt volledig bepaald door de planning. Hieronder staan alleen de instellingen van deze module zelf." })] }), _jsxs(CardContent, { className: "space-y-5", children: [_jsxs("div", { className: "rounded-md border p-4 space-y-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Mail, { className: "h-4 w-4 text-primary" }), _jsx(Label, { htmlFor: "mailEnabled", className: "font-semibold", children: "Aanmeldmail vervoerders" })] }), _jsx("p", { className: "text-xs text-muted-foreground max-w-prose", children: "Verstuurt automatisch een mail met de te verwachten karren naar de vervoerder v\u00F3\u00F3r de aanmelddeadline van het ophaalmoment. E\u00E9n mail per ophaaldag/-moment per vervoerder." })] }), _jsx(Switch, { id: "mailEnabled", checked: mailEnabled, onCheckedChange: setMailEnabled })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "mailLead", children: "Minuten v\u00F3\u00F3r aanmelddeadline versturen" }), _jsx(Input, { id: "mailLead", type: "number", min: 0, max: 1440, value: mailLead, disabled: !mailEnabled, onChange: (e) => setMailLead(Math.max(0, Math.min(1440, Math.floor(Number(e.target.value)) || 0))) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "mailPurpose", children: "E-mail-doeleinde (purpose)" }), _jsx(Input, { id: "mailPurpose", value: mailPurpose, disabled: !mailEnabled, onChange: (e) => setMailPurpose(e.target.value) }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Koppel dit doeleinde in de module ", _jsx("strong", { children: "E-mailkoppeling" }), " aan een actief send-adres. Standaard: ", _jsx("code", { children: "afhandeling_carrier_signup" }), "."] })] })] }), _jsxs("div", { className: "rounded-md bg-muted/50 p-3 text-xs text-muted-foreground", children: [_jsx("strong", { children: "Ophaaltijd" }), " en ", _jsx("strong", { children: "Aanmeldtijd" }), " stel je per vervoerder in (werkdagen v\u00F3\u00F3r bezorgdag). De vervoerder die als ", _jsx("strong", { children: "standaard" }), " is gemarkeerd, wordt gebruikt voor orders zonder eigen vervoerder."] }), _jsx(Button, { onClick: () => mutation.mutate(), disabled: mutation.isPending, children: mutation.isPending ? "Opslaan…" : "Opslaan" })] })] }));
|
|
51
51
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Controle van de Engine, elke 2 minuten opnieuw opgevraagd. Deze module corrigeert niets
|
|
3
3
|
* zelf: bij een afwijking tonen we alleen een discrete melding.
|
|
4
4
|
*/
|
|
5
5
|
export declare function useConsistencyCheck(): {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useConsistencyCheck.d.ts","sourceRoot":"","sources":["../../src/hooks/useConsistencyCheck.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,mBAAmB;;
|
|
1
|
+
{"version":3,"file":"useConsistencyCheck.d.ts","sourceRoot":"","sources":["../../src/hooks/useConsistencyCheck.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,wBAAgB,mBAAmB;;EAiBlC"}
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { useQuery } from "@tanstack/react-query";
|
|
2
2
|
import { supabase } from "../integrations/supabase/moduleClient";
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Controle van de Engine, elke 2 minuten opnieuw opgevraagd. Deze module corrigeert niets
|
|
5
5
|
* zelf: bij een afwijking tonen we alleen een discrete melding.
|
|
6
6
|
*/
|
|
7
7
|
export function useConsistencyCheck() {
|
|
8
8
|
const query = useQuery({
|
|
9
9
|
queryKey: ["logistics-consistency-check"],
|
|
10
|
-
staleTime:
|
|
10
|
+
staleTime: 60000,
|
|
11
|
+
refetchInterval: 120000,
|
|
12
|
+
refetchIntervalInBackground: false,
|
|
11
13
|
retry: false,
|
|
12
|
-
refetchOnWindowFocus:
|
|
14
|
+
refetchOnWindowFocus: true,
|
|
13
15
|
queryFn: async () => {
|
|
14
16
|
const { data, error } = await supabase.rpc("logistics_consistency_check");
|
|
15
17
|
if (error)
|
|
@@ -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;AAGzB,OAAO,EAAwC,KAAK,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAYtG;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,iBAAiB,GACjB,gBAAgB,GAChB,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,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,
|
|
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;AAYtG;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GACrB,SAAS,GACT,iBAAiB,GACjB,gBAAgB,GAChB,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,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,CAmTxF"}
|
|
@@ -11,7 +11,7 @@ import { useCartIndex } from "./useCartIndex";
|
|
|
11
11
|
import { emitEngineEvent, listComposeTaskRefs } from "../lib/logisticsEngine/engine";
|
|
12
12
|
import { orderRefsFor } from "../lib/orderRefs";
|
|
13
13
|
import { isOpenOrderStatus } from "../lib/orderStatus";
|
|
14
|
-
import {
|
|
14
|
+
import { emitQueueStatus, markInFlight, markResult, resetEmitQueue, selectEmitBatch, } from "../lib/logisticsEngine/emitQueue";
|
|
15
15
|
export function useOrderPlanning(range) {
|
|
16
16
|
const from = range?.from ?? null;
|
|
17
17
|
const to = range?.to ?? null;
|
|
@@ -34,7 +34,8 @@ export function useOrderPlanning(range) {
|
|
|
34
34
|
let message = "Onbekende fout";
|
|
35
35
|
if (e instanceof Response) {
|
|
36
36
|
const text = await e.clone().text().catch(() => "");
|
|
37
|
-
message =
|
|
37
|
+
message = "Floriday reageert nu niet goed. Probeer het over een paar minuten opnieuw.";
|
|
38
|
+
console.warn(`Floriday-aanvraag mislukt (${e.status}): ${text.slice(0, 200) || e.statusText}`);
|
|
38
39
|
}
|
|
39
40
|
else if (e instanceof Error) {
|
|
40
41
|
message = e.message;
|
|
@@ -182,7 +183,7 @@ export function useOrderPlanning(range) {
|
|
|
182
183
|
cartIndexes: Array.from({ length: carrierCount }, (_, i) => i),
|
|
183
184
|
},
|
|
184
185
|
],
|
|
185
|
-
reason: `Ingepland door de
|
|
186
|
+
reason: `Ingepland door de planning (taak ${(pickTask ?? task).task_type}, regelversie ${(pickTask ?? task).rule_version ?? "-"}).`,
|
|
186
187
|
carrierName: carrier?.name ?? base.carrierName,
|
|
187
188
|
};
|
|
188
189
|
return { order, planning, engineTask: task, awaitingEngine: false, state: "planned", hasComposeTask: true };
|
|
@@ -223,7 +224,7 @@ export function useOrderPlanning(range) {
|
|
|
223
224
|
if (!batch.length) {
|
|
224
225
|
const status = emitQueueStatus(refs);
|
|
225
226
|
if (status.gaveUp.length) {
|
|
226
|
-
setSignalError(
|
|
227
|
+
setSignalError(`${status.gaveUp.length} order(s) konden niet automatisch worden ingepland. Open de order en kies ‘Opnieuw inplannen’.`);
|
|
227
228
|
}
|
|
228
229
|
return;
|
|
229
230
|
}
|
|
@@ -257,8 +258,8 @@ export function useOrderPlanning(range) {
|
|
|
257
258
|
else {
|
|
258
259
|
const status = emitQueueStatus(refs);
|
|
259
260
|
setSignalError(status.gaveUp.length
|
|
260
|
-
?
|
|
261
|
-
:
|
|
261
|
+
? `${status.gaveUp.length} order(s) konden niet automatisch worden ingepland. Open de order en kies ‘Opnieuw inplannen’.`
|
|
262
|
+
: "Inplannen lukt nog niet; er volgt automatisch een nieuwe poging.");
|
|
262
263
|
}
|
|
263
264
|
}
|
|
264
265
|
})();
|
|
@@ -6,6 +6,10 @@ export interface PickAgendaOrder {
|
|
|
6
6
|
carts: number;
|
|
7
7
|
plants: number;
|
|
8
8
|
shifted: boolean;
|
|
9
|
+
/** Orders in deze (buffer)taak; leeg = alleen orderRef. */
|
|
10
|
+
bufferOrders: string[];
|
|
11
|
+
/** Unieke kar-id's van deze taak. */
|
|
12
|
+
cartIds: string[];
|
|
9
13
|
/** Verkoop-module zegt: deze order is afgehandeld (verzonden/geleverd). */
|
|
10
14
|
fulfilled?: boolean;
|
|
11
15
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"usePickAgenda.d.ts","sourceRoot":"","sources":["../../src/hooks/usePickAgenda.ts"],"names":[],"mappings":"AAQA,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;IACjB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;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,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;
|
|
1
|
+
{"version":3,"file":"usePickAgenda.d.ts","sourceRoot":"","sources":["../../src/hooks/usePickAgenda.ts"],"names":[],"mappings":"AAQA,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;IACjB,2DAA2D;IAC3D,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,qCAAqC;IACrC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;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,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AA4CD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI;;;;;EAoInE"}
|
|
@@ -16,6 +16,10 @@ function toOrders(value) {
|
|
|
16
16
|
carts: Number(r.carts ?? 0),
|
|
17
17
|
plants: Number(r.plants ?? 0),
|
|
18
18
|
shifted: Boolean(r.shifted),
|
|
19
|
+
bufferOrders: Array.isArray(r.bufferOrders) ? r.bufferOrders.map(String).filter(Boolean) : [],
|
|
20
|
+
cartIds: Array.isArray(r.cartList)
|
|
21
|
+
? r.cartList.map((c) => c?.cartId).filter((x) => typeof x === "string" && !!x)
|
|
22
|
+
: [],
|
|
19
23
|
};
|
|
20
24
|
});
|
|
21
25
|
}
|
|
@@ -110,16 +114,43 @@ export function usePickAgenda(from, to) {
|
|
|
110
114
|
if (to && pickDate > to)
|
|
111
115
|
continue;
|
|
112
116
|
// "Afgehandeld" volgt uitsluitend de Engine (taak afgemeld), niet de Verkoop-status.
|
|
113
|
-
|
|
114
|
-
|
|
117
|
+
// Tellen per losse order (ook elke order in een buffer) en per unieke kar-id,
|
|
118
|
+
// gelijk aan Dagdetails. "Afgehandeld" = taak afgemeld of Verkoop-status afgehandeld,
|
|
119
|
+
// gelijk aan Planning en leveringen.
|
|
120
|
+
const refsOf = (o) => (o.bufferOrders.length ? o.bufferOrders : [o.orderRef]);
|
|
121
|
+
const allRefs = new Set();
|
|
122
|
+
const doneRefs = new Set();
|
|
123
|
+
const readyRefs = new Set();
|
|
124
|
+
for (const o of orders) {
|
|
125
|
+
for (const r of refsOf(o)) {
|
|
126
|
+
if (isCancelledRef(r))
|
|
127
|
+
continue;
|
|
128
|
+
allRefs.add(r);
|
|
129
|
+
const engineDone = o.status === "done" || o.status === "completed";
|
|
130
|
+
if (engineDone || isFulfilledRef(r))
|
|
131
|
+
doneRefs.add(r);
|
|
132
|
+
if (o.status === "ready" || engineDone || isFulfilledRef(r))
|
|
133
|
+
readyRefs.add(r);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const cartIds = new Set();
|
|
137
|
+
let cartsWithoutId = 0;
|
|
138
|
+
for (const o of orders) {
|
|
139
|
+
if (o.cartIds.length)
|
|
140
|
+
o.cartIds.forEach((c) => cartIds.add(c));
|
|
141
|
+
else
|
|
142
|
+
cartsWithoutId += o.carts || 0;
|
|
143
|
+
}
|
|
144
|
+
const doneCount = doneRefs.size;
|
|
145
|
+
const ready = readyRefs.size;
|
|
115
146
|
out.push({
|
|
116
147
|
pickDate,
|
|
117
148
|
orders,
|
|
118
|
-
orderCount:
|
|
119
|
-
cartCount:
|
|
149
|
+
orderCount: allRefs.size,
|
|
150
|
+
cartCount: cartIds.size + cartsWithoutId,
|
|
120
151
|
plantCount: orders.reduce((s, o) => s + (o.plants || 0), 0),
|
|
121
152
|
ready,
|
|
122
|
-
inPreparation: Math.max(0,
|
|
153
|
+
inPreparation: Math.max(0, allRefs.size - ready),
|
|
123
154
|
shiftedCount: orders.filter((o) => o.shifted).length,
|
|
124
155
|
doneCount,
|
|
125
156
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"previewAuthStorage.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/previewAuthStorage.ts"],"names":[],"mappings":"AAIA,wBAAgB,sBAAsB;
|
|
1
|
+
{"version":3,"file":"previewAuthStorage.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/previewAuthStorage.ts"],"names":[],"mappings":"AAIA,wBAAgB,sBAAsB;mBA6Db,MAAM;mBAwBZ,MAAM,SAAS,MAAM;sBASlB,MAAM;cAM3B"}
|
|
@@ -61,21 +61,38 @@ export function brokeredPreviewStorage() {
|
|
|
61
61
|
// The editor may not be listening yet at the first getItem, so retry once.
|
|
62
62
|
let firstGet = true;
|
|
63
63
|
const RETRY_DELAY = 250;
|
|
64
|
+
// Blocked third-party storage throws here; seeding must not break broker reads.
|
|
65
|
+
const readLocal = (key) => { try {
|
|
66
|
+
return localStorage.getItem(key);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
} };
|
|
64
71
|
return {
|
|
65
72
|
getItem: async (key) => {
|
|
66
|
-
|
|
73
|
+
// Sent with GET and REMOVE so an editor that never cached this session can check or revoke it.
|
|
74
|
+
const local = readLocal(key);
|
|
75
|
+
let res = await request('lovable-preview-auth:get', key, local ?? undefined);
|
|
67
76
|
if (!res && firstGet) {
|
|
68
77
|
await new Promise((r) => setTimeout(r, RETRY_DELAY));
|
|
69
|
-
res = await request('lovable-preview-auth:get', key);
|
|
78
|
+
res = await request('lovable-preview-auth:get', key, local ?? undefined);
|
|
70
79
|
}
|
|
71
80
|
firstGet = false;
|
|
72
81
|
// '' is the logout tombstone: clear the local copy too so it can't resurrect if
|
|
73
82
|
// the broker later goes silent. A null reply means never-synced -> keep local.
|
|
74
83
|
if (res && res.ok && typeof res.value === 'string') {
|
|
75
84
|
if (res.value === '') {
|
|
76
|
-
|
|
85
|
+
if (local !== undefined && readLocal(key) === local)
|
|
86
|
+
localStorage.removeItem(key);
|
|
77
87
|
return null;
|
|
78
88
|
}
|
|
89
|
+
// Seed this origin so a new tab, whose editor holds no session, stays signed in.
|
|
90
|
+
if (local === null && readLocal(key) === null) {
|
|
91
|
+
try {
|
|
92
|
+
localStorage.setItem(key, res.value);
|
|
93
|
+
}
|
|
94
|
+
catch { /* storage full: still return the editor's session */ }
|
|
95
|
+
}
|
|
79
96
|
return res.value;
|
|
80
97
|
}
|
|
81
98
|
return localStorage.getItem(key);
|
|
@@ -92,8 +109,9 @@ export function brokeredPreviewStorage() {
|
|
|
92
109
|
});
|
|
93
110
|
},
|
|
94
111
|
removeItem: (key) => {
|
|
112
|
+
const local = readLocal(key);
|
|
95
113
|
localStorage.removeItem(key);
|
|
96
|
-
return request('lovable-preview-auth:remove', key).then(() => undefined);
|
|
114
|
+
return request('lovable-preview-auth:remove', key, local ?? undefined).then(() => undefined);
|
|
97
115
|
},
|
|
98
116
|
};
|
|
99
117
|
}
|