@flowselections/floriday-verkoop-module 1.0.73 → 1.0.76
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/verkoop/OrderKarindelingCard.d.ts.map +1 -1
- package/dist-lib/components/verkoop/OrderKarindelingCard.js +36 -114
- package/dist-lib/components/verkoop/PlanCoverageBadge.d.ts +6 -0
- package/dist-lib/components/verkoop/PlanCoverageBadge.d.ts.map +1 -0
- package/dist-lib/components/verkoop/PlanCoverageBadge.js +23 -0
- package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrderDetailView.js +2 -1
- package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrdersTable.js +2 -1
- package/dist-lib/integrations/supabase/types.d.ts +607 -3
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/cart-replan.functions.d.ts +2 -22
- package/dist-lib/lib/cart-replan.functions.d.ts.map +1 -1
- package/dist-lib/lib/cart-replan.functions.js +10 -163
- package/dist-lib/lib/create-order.functions.d.ts +0 -57
- package/dist-lib/lib/create-order.functions.d.ts.map +1 -1
- package/dist-lib/lib/create-order.functions.js +1 -205
- package/dist-lib/lib/logistics-engine.server.d.ts.map +1 -1
- package/dist-lib/lib/logistics-engine.server.js +8 -3
- package/dist-lib/lib/plan-coverage.d.ts +56 -0
- package/dist-lib/lib/plan-coverage.d.ts.map +1 -0
- package/dist-lib/lib/plan-coverage.js +120 -0
- package/dist-lib/lib/plan-coverage.server.d.ts +3 -0
- package/dist-lib/lib/plan-coverage.server.d.ts.map +1 -0
- package/dist-lib/lib/plan-coverage.server.js +97 -0
- package/dist-lib/lib/salesorders-list.server.d.ts.map +1 -1
- package/dist-lib/lib/salesorders-list.server.js +43 -82
- package/dist-lib/lib/salesorders-mapping.d.ts +5 -0
- package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -1
- package/dist-lib/lib/salesorders.functions.d.ts.map +1 -1
- package/dist-lib/lib/salesorders.functions.js +21 -18
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrderKarindelingCard.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/OrderKarindelingCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"OrderKarindelingCard.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/OrderKarindelingCard.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAkB/D,wBAAgB,oBAAoB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,aAAa,CAAA;CAAE,+BAyJvE"}
|
|
@@ -1,135 +1,57 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
// Toont de karindeling van de
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
// herberekening in de browser gaf eerder een andere indeling dan de database
|
|
8
|
-
// (één artikel per kar, verkeerde kar-telling) en is daarom verwijderd.
|
|
9
|
-
import { useEffect, useMemo, useState } from "react";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// Toont de karindeling van deze order zoals de Logistiek Engine die levert
|
|
3
|
+
// (logistics_order_plan_coverage). Deze kaart leest order_carts/
|
|
4
|
+
// order_cart_items niet zelf en berekent niets: alleen de regels van DEZE
|
|
5
|
+
// order, per kar, met of de kar gedeeld is met de buffer.
|
|
6
|
+
import { useState } from "react";
|
|
10
7
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
11
8
|
import { useServerFn } from "@tanstack/react-start";
|
|
12
|
-
import { Badge, Card, CardContent, CardHeader, CardTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@flowselections/core";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { listOrderCarts } from "../../lib/cart-replan.functions.js";
|
|
9
|
+
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@flowselections/core";
|
|
10
|
+
import { LayoutGrid, RefreshCw } from "lucide-react";
|
|
11
|
+
import { getOrderPlanCoverage } from "../../lib/cart-replan.functions.js";
|
|
12
|
+
import { summarize } from "../../lib/plan-coverage.js";
|
|
17
13
|
import { resolveProductName } from "../../lib/product-name.js";
|
|
18
14
|
import { CartLayersDialog } from "./CartLayersDialog";
|
|
15
|
+
import { PlanCoverageBadge } from "./PlanCoverageBadge";
|
|
19
16
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
i.product_id ?? "",
|
|
28
|
-
i.product_name ?? "",
|
|
29
|
-
i.plants_per_crate ?? "",
|
|
30
|
-
i.crates_per_layer ?? "",
|
|
31
|
-
i.layers_per_cart ?? "",
|
|
32
|
-
].join("|");
|
|
33
|
-
const prev = out.get(key);
|
|
34
|
-
if (prev) {
|
|
35
|
-
prev.crates = Number(prev.crates ?? 0) + Number(i.crates ?? 0);
|
|
36
|
-
prev.layer_share_pct = Number(prev.layer_share_pct ?? 0) + Number(i.layer_share_pct ?? 0);
|
|
37
|
-
prev.lines += 1;
|
|
38
|
-
}
|
|
39
|
-
else {
|
|
40
|
-
out.set(key, { ...i, crates: Number(i.crates ?? 0), layer_share_pct: Number(i.layer_share_pct ?? 0), lines: 1 });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return Array.from(out.values());
|
|
44
|
-
}
|
|
45
|
-
function belading(i) {
|
|
46
|
-
// Eén notatie in de hele module: lagen × fusten/laag × planten/fust
|
|
47
|
-
// (Floriday-UI-volgorde, bewust anders dan de API-veldvolgorde).
|
|
48
|
-
const l = i.layers_per_cart ?? 0;
|
|
49
|
-
const c = i.crates_per_layer ?? 0;
|
|
50
|
-
const p = i.plants_per_crate ?? 0;
|
|
51
|
-
if (!l || !c || !p)
|
|
52
|
-
return "—";
|
|
53
|
-
return formatBeladingNotation(l, c, p) ?? `${l}×${c}×${p}`;
|
|
54
|
-
}
|
|
17
|
+
const PICK_LABEL = {
|
|
18
|
+
geen_pickrij: "Geen pickrij",
|
|
19
|
+
open: "Open",
|
|
20
|
+
bezig: "Bezig met picken",
|
|
21
|
+
gepickt: "Gepickt",
|
|
22
|
+
};
|
|
23
|
+
const fmt = (v) => (v ? new Date(v).toLocaleString("nl-NL") : null);
|
|
55
24
|
export function OrderKarindelingCard({ order }) {
|
|
56
|
-
// Lokale
|
|
57
|
-
//
|
|
58
|
-
const rawId = order?.id ??
|
|
25
|
+
// Lokale orders hebben een prefix in hun DTO-id; Floriday-orders hun
|
|
26
|
+
// salesOrderId. De server zoekt de juiste order op.
|
|
27
|
+
const rawId = order?.id ?? null;
|
|
59
28
|
const stripped = typeof rawId === "string" ? rawId.replace(/^(local:|mailbox-proposal:)/, "") : null;
|
|
60
|
-
const orderId = stripped && UUID_RE.test(stripped) ? stripped : null;
|
|
61
|
-
const
|
|
29
|
+
const orderId = stripped && (UUID_RE.test(stripped) || stripped.length > 0) ? stripped : null;
|
|
30
|
+
const fetchCoverage = useServerFn(getOrderPlanCoverage);
|
|
62
31
|
const queryClient = useQueryClient();
|
|
63
|
-
const queryKey = ["order-
|
|
64
|
-
const { data, isLoading, error } = useQuery({
|
|
32
|
+
const queryKey = ["order-plan-coverage", orderId];
|
|
33
|
+
const { data, isLoading, error, isFetching } = useQuery({
|
|
65
34
|
queryKey,
|
|
66
35
|
enabled: !!orderId,
|
|
67
|
-
staleTime:
|
|
36
|
+
staleTime: 30000,
|
|
68
37
|
retry: (failureCount, queryError) => /authorization|token|sessie/i.test(queryError.message) && failureCount < 5,
|
|
69
38
|
retryDelay: (attempt) => Math.min(500 * 2 ** attempt, 4000),
|
|
70
39
|
queryFn: async () => {
|
|
71
40
|
if (!orderId)
|
|
72
41
|
throw new Error("Geen geldige order geselecteerd");
|
|
73
|
-
return
|
|
42
|
+
return fetchCoverage({ data: { orderId } });
|
|
74
43
|
},
|
|
75
44
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (!orderId)
|
|
80
|
-
return;
|
|
81
|
-
const invalidate = () => void queryClient.invalidateQueries({ queryKey: ["order-carts", orderId] });
|
|
82
|
-
const channel = supabase
|
|
83
|
-
.channel(`order-carts-${orderId}`)
|
|
84
|
-
.on("postgres_changes", { event: "*", schema: "public", table: "order_carts" }, invalidate)
|
|
85
|
-
.on("postgres_changes", { event: "*", schema: "public", table: "order_cart_items" }, invalidate)
|
|
86
|
-
.on("postgres_changes", { event: "UPDATE", schema: "public", table: "orders" }, invalidate)
|
|
87
|
-
.subscribe();
|
|
88
|
-
return () => {
|
|
89
|
-
void supabase.removeChannel(channel);
|
|
90
|
-
};
|
|
91
|
-
}, [orderId, queryClient]);
|
|
92
|
-
const carts = (data?.carts ?? []);
|
|
93
|
-
const orders = data?.orders ?? [];
|
|
94
|
-
const lockedAt = data?.planLockedAt ?? null;
|
|
95
|
-
const blocked = data?.blocked ?? null;
|
|
96
|
-
const planState = data?.planState ?? null;
|
|
97
|
-
// De DB-order-id (verschilt van de DTO-id bij Floriday-orders).
|
|
98
|
-
const dbOrderId = data?.orderId ?? null;
|
|
99
|
-
const orderNumberByItem = useMemo(() => {
|
|
100
|
-
const numberByOrder = new Map();
|
|
101
|
-
for (const o of orders)
|
|
102
|
-
numberByOrder.set(o.id, o.order_number);
|
|
103
|
-
const m = new Map();
|
|
104
|
-
for (const it of data?.itemOrders ?? []) {
|
|
105
|
-
m.set(it.id, { orderId: it.order_id, orderNumber: numberByOrder.get(it.order_id) ?? null });
|
|
106
|
-
}
|
|
107
|
-
return m;
|
|
108
|
-
}, [data, orders]);
|
|
109
|
-
// Bron-badge uit de data: order_carts.source + algorithm_version.
|
|
110
|
-
const sourceLabel = useMemo(() => {
|
|
111
|
-
const srcs = [...new Set(carts.map((c) => c.source ?? "onbekend"))];
|
|
112
|
-
const vers = [...new Set(carts.map((c) => c.algorithm_version).filter((v) => v != null))];
|
|
113
|
-
if (srcs.length === 0)
|
|
114
|
-
return null;
|
|
115
|
-
return `${srcs.join(", ")}${vers.length > 0 ? ` · v${vers.join("/")}` : ""}`;
|
|
116
|
-
}, [carts]);
|
|
117
|
-
const lockedDate = lockedAt ? new Date(lockedAt).toLocaleString("nl-NL") : null;
|
|
118
|
-
const multiOrder = orders.length > 1;
|
|
45
|
+
const cov = data?.coverage ?? null;
|
|
46
|
+
const carts = Array.isArray(cov?.carts) ? cov.carts : [];
|
|
47
|
+
const pickRows = Array.isArray(cov?.pickRows) ? cov.pickRows : [];
|
|
119
48
|
const [layersFor, setLayersFor] = useState(null);
|
|
120
|
-
return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
: ""] })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "Deze order staat nog niet aangemeld voor een indeling." })), blocked && (blocked.explanation || blocked.reason) && (_jsxs("p", { className: "text-sm text-destructive", children: [blocked.explanation ?? blocked.reason, blocked.at ? ` (${new Date(blocked.at).toLocaleString("nl-NL")})` : ""] }))] })) : (_jsx("div", { className: "space-y-4", children: carts.map((c) => (_jsxs("div", { className: "rounded-md border border-border", children: [_jsxs("div", { className: "flex items-center justify-between border-b border-border bg-muted/30 px-3 py-2 text-sm", children: [_jsxs("button", { type: "button", className: "flex items-center gap-3 rounded-sm text-left hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: () => setLayersFor({ id: c.id, number: c.cart_number }), title: "Bekijk de lagen van deze kar", children: [_jsx(LayoutGrid, { className: "h-4 w-4 text-muted-foreground" }), _jsxs("span", { className: "font-mono font-medium", children: ["Kar ", c.cart_number] }), _jsxs(Badge, { variant: c.is_complete ? "default" : "secondary", className: "gap-1", children: [c.is_complete && _jsx(CheckCircle2, { className: "h-3 w-3" }), c.is_complete ? "Volledig" : "Deels"] }), c.order_id === null && multiOrder && (_jsx(Badge, { variant: "outline", children: "Gedeelde kar" })), c.carrier_type && (_jsxs(Badge, { variant: "outline", className: "font-mono text-[10px]", children: [c.carrier_type, c.max_layers ? ` · ${c.max_layers} lagen` : ""] }))] }), _jsxs("div", { className: "text-muted-foreground", children: [Number(c.total_layers_used ?? 0), " lagen \u00B7 benutting", " ", Math.round(Number(c.occupancy_pct ?? 0) * 10) / 10, "%"] })] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel" }), _jsx(TableHead, { className: "text-right", children: "Planten" }), _jsx(TableHead, { className: "text-right", children: "Fusten" }), _jsx(TableHead, { children: "Belading" }), _jsx(TableHead, { className: "text-right", children: "Laag-aandeel" })] }) }), _jsx(TableBody, { children: groupCartItems(c.order_cart_items ?? []).map((i) => {
|
|
127
|
-
const own = i.order_item_id
|
|
128
|
-
? orderNumberByItem.get(i.order_item_id)
|
|
129
|
-
: undefined;
|
|
130
|
-
const crates = Number(i.crates ?? 0);
|
|
131
|
-
return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("span", { children: resolveProductName(i.product_name) ?? "—" }), multiOrder && own?.orderNumber && (_jsx(Badge, { variant: own.orderId === dbOrderId ? "default" : "outline", className: "text-[10px]", children: own.orderNumber })), i.lines > 1 && (_jsxs("span", { className: "text-xs text-muted-foreground", children: [i.lines, " lagen"] }))] }) }), _jsx(TableCell, { className: "text-right", children: crates * Number(i.plants_per_crate ?? 0) }), _jsx(TableCell, { className: "text-right", children: crates }), _jsx(TableCell, { className: "font-mono text-xs", children: belading(i) }), _jsxs(TableCell, { className: "text-right", children: [Math.round(Number(i.layer_share_pct ?? 0) * 10) / 10, "%"] })] }, i.id));
|
|
132
|
-
}) })] })] }, c.id))) })) }), _jsx(CartLayersDialog, { cartId: layersFor?.id ?? null, cartNumber: layersFor?.number ?? null, open: !!layersFor, onOpenChange: (v) => {
|
|
49
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-2 sm:flex sm:flex-row sm:justify-between", children: [_jsx(CardTitle, { className: "truncate text-base", children: "Karindeling" }), _jsxs("div", { className: "flex items-center gap-2", children: [cov && cov.state !== "onbekend" && (_jsxs(Badge, { variant: "secondary", children: [carts.length, " kar(ren)"] })), _jsx(Button, { variant: "ghost", size: "icon", "aria-label": "Vernieuwen", title: "Vernieuwen", onClick: () => void queryClient.invalidateQueries({ queryKey }), children: _jsx(RefreshCw, { className: "h-4 w-4 " + (isFetching ? "animate-spin" : "") }) })] })] }), _jsx(CardContent, { className: "space-y-4", children: isLoading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Karindeling laden\u2026" })) : error ? (_jsxs("div", { className: "space-y-1", children: [_jsx("p", { className: "text-sm font-medium text-muted-foreground", children: "Indeling onbekend" }), _jsx("p", { className: "text-sm text-muted-foreground", children: error.message })] })) : cov ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex flex-wrap items-start gap-x-6 gap-y-2 text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Indeling" }), _jsx(PlanCoverageBadge, { coverage: summarize(cov) })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Pickstatus" }), _jsxs("div", { className: "mt-1", children: [cov.pickStatus ? PICK_LABEL[cov.pickStatus] : "—", cov.pickStatus === "gepickt" && cov.pickedAt ? ` · ${fmt(cov.pickedAt)}` : ""] })] }), pickRows.length > 0 && (_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-xs text-muted-foreground", children: "Pickdocumenten" }), _jsx("div", { className: "mt-1 flex flex-wrap gap-1", children: pickRows.map((p, i) => (_jsxs(Badge, { variant: "outline", className: "text-[10px]", title: [
|
|
50
|
+
p.printedAt ? `Geprint ${fmt(p.printedAt)}` : "Nog niet geprint",
|
|
51
|
+
p.completedAt ? `Afgerond ${fmt(p.completedAt)}` : null,
|
|
52
|
+
]
|
|
53
|
+
.filter(Boolean)
|
|
54
|
+
.join(" · "), children: [p.documentNumber ?? "pickrij", p.completedAt ? " ✓" : ""] }, p.queueItemId ?? i))) })] }))] }), cov.state === "onbekend" && cov.error && (_jsx("p", { className: "text-sm text-muted-foreground", children: cov.error })), carts.map((c) => (_jsxs("div", { className: "rounded-md border border-border", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2 border-b border-border bg-muted/30 px-3 py-2 text-sm", children: [_jsxs("button", { type: "button", className: "flex items-center gap-2 rounded-sm text-left hover:underline focus:outline-none focus-visible:ring-2 focus-visible:ring-ring", onClick: () => setLayersFor({ id: c.cartId, number: c.cartNumber ?? 0 }), title: "Bekijk de lagen van deze kar", children: [_jsx(LayoutGrid, { className: "h-4 w-4 text-muted-foreground" }), _jsxs("span", { className: "font-mono font-medium", children: ["Kar ", c.cartNumber ?? "—"] })] }), c.shared && _jsx(Badge, { variant: "outline", children: "Gedeelde bufferkar" }), c.carrierType && (_jsx(Badge, { variant: "outline", className: "font-mono text-[10px]", children: c.carrierType }))] }), _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Artikel (deze order)" }), _jsx(TableHead, { className: "text-right", children: "Fusten" }), _jsx(TableHead, { className: "text-right", children: "Dozen" })] }) }), _jsx(TableBody, { children: (Array.isArray(c.lines) ? c.lines : []).map((l, i) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: resolveProductName(l.productName) ?? "—" }), _jsx(TableCell, { className: "text-right", children: l.crates }), _jsx(TableCell, { className: "text-right", children: l.boxes })] }, `${l.orderItemId ?? "x"}-${i}`))) })] })] }, c.cartId)))] })) : null }), _jsx(CartLayersDialog, { cartId: layersFor?.id ?? null, cartNumber: layersFor?.number ?? null, open: !!layersFor, onOpenChange: (v) => {
|
|
133
55
|
if (!v)
|
|
134
56
|
setLayersFor(null);
|
|
135
57
|
} })] }));
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PlanCoverageSummary } from "../../lib/plan-coverage.js";
|
|
2
|
+
export declare function PlanCoverageBadge({ coverage, showReason, }: {
|
|
3
|
+
coverage: PlanCoverageSummary | null | undefined;
|
|
4
|
+
showReason?: boolean;
|
|
5
|
+
}): import("react").JSX.Element | null;
|
|
6
|
+
//# sourceMappingURL=PlanCoverageBadge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PlanCoverageBadge.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/PlanCoverageBadge.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAG/D,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,UAAiB,GAClB,EAAE;IACD,QAAQ,EAAE,mBAAmB,GAAG,IAAI,GAAG,SAAS,CAAC;IACjD,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB,sCAkCA"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AlertTriangle } from "lucide-react";
|
|
3
|
+
// Indelingsstatus zoals de Logistiek Engine die levert. Alleen weergave.
|
|
4
|
+
export function PlanCoverageBadge({ coverage, showReason = true, }) {
|
|
5
|
+
if (!coverage)
|
|
6
|
+
return null;
|
|
7
|
+
const { state, label, progressPct, reason, shared } = coverage;
|
|
8
|
+
const base = "inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-[11px] font-medium";
|
|
9
|
+
if (state === "geen_indeling") {
|
|
10
|
+
return (_jsxs("div", { className: "mt-1 min-w-0", children: [_jsxs("span", { className: `${base} border-destructive/40 bg-destructive/10 text-destructive`, title: reason ?? undefined, children: [_jsx(AlertTriangle, { className: "h-3 w-3 shrink-0" }), label] }), showReason && reason && _jsx("div", { className: "mt-0.5 text-xs text-destructive", children: reason })] }));
|
|
11
|
+
}
|
|
12
|
+
const tone = state === "deels"
|
|
13
|
+
? "border-primary/40 bg-primary/10 text-foreground"
|
|
14
|
+
: state === "ingedeeld"
|
|
15
|
+
? "border-border bg-muted text-foreground"
|
|
16
|
+
: "border-border bg-muted/50 text-muted-foreground";
|
|
17
|
+
const text = state === "deels" && progressPct != null
|
|
18
|
+
? `${label} · ${Math.round(progressPct)}%`
|
|
19
|
+
: state === "ingedeeld" && shared
|
|
20
|
+
? `${label} · gedeeld met buffer`
|
|
21
|
+
: label;
|
|
22
|
+
return (_jsx("div", { className: "mt-1", children: _jsx("span", { className: `${base} ${tone}`, title: reason ?? undefined, children: text }) }));
|
|
23
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SalesOrderDetailView.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrderDetailView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"SalesOrderDetailView.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrderDetailView.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AA2JnE,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,cAAc,EACd,eAAuB,GACxB,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,+BA8YA"}
|
|
@@ -12,6 +12,7 @@ import { OrderShipmentsCard } from "./OrderShipmentsCard";
|
|
|
12
12
|
import { OrderControlStatusCard } from "./OrderControlStatusCard";
|
|
13
13
|
import { OrderCarrierPicker } from "./OrderCarrierPicker";
|
|
14
14
|
import { OrderPickupMomentPicker } from "./OrderPickupMomentPicker";
|
|
15
|
+
import { PlanCoverageBadge } from "./PlanCoverageBadge";
|
|
15
16
|
function AddressCard({ icon, lines, extra, }) {
|
|
16
17
|
return (_jsxs("div", { className: "inline-flex max-w-xs items-start gap-3 rounded-md border border-border bg-card p-3", children: [_jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-md bg-primary/10 text-primary", children: icon }), _jsxs("div", { className: "text-sm", children: [lines.length > 0 ? lines.map((l, i) => _jsx("div", { children: l }, i)) : _jsx("div", { children: "\u2014" }), extra && _jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: extra })] })] }));
|
|
17
18
|
}
|
|
@@ -225,7 +226,7 @@ export function SalesOrderDetailView({ order, sellerFallback, hideKarindeling =
|
|
|
225
226
|
const bestellingLabel = orderStageLabel(order.stage ?? (order.status.isCancelled ? "cancelled" : "incoming"));
|
|
226
227
|
return (_jsxs("div", { className: "space-y-6", children: [order.needsManualReview ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-foreground", children: [_jsx("div", { className: "font-semibold", children: "Handmatige beoordeling nodig" }), _jsxs("div", { className: "text-muted-foreground", children: [order.needsManualReview.reason, order.needsManualReview.at
|
|
227
228
|
? ` (geconstateerd op ${new Date(order.needsManualReview.at).toLocaleString("nl-NL")})`
|
|
228
|
-
: ""] })] })) : null, order.mixedDeliveryDates ? (_jsxs("div", { className: "rounded-md border border-border bg-muted/40 p-3 text-sm text-foreground", children: [_jsx("div", { className: "font-semibold", children: "Bestellingen met verschillende leverdatums" }), _jsx("div", { className: "text-muted-foreground", children: "Deze order bestaat uit Floriday-bestellingen met verschillende leverdatums. De vroegste leverdatum is aangehouden." })] })) : null, (order.unlinkedProducts?.length ?? 0) > 0 ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-foreground", children: [_jsx("div", { className: "font-semibold", children: "Floriday-artikel niet gekoppeld aan product" }), _jsx("ul", { className: "mt-1 space-y-0.5 text-muted-foreground", children: order.unlinkedProducts.map((u, i) => (_jsxs("li", { children: [u.name, " \u2014 trade-item-id ", u.tradeItemId ?? "onbekend"] }, i))) }), _jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: "Koppel dit trade-item aan een product; de volgende synchronisatie neemt het dan over." })] })) : null, _jsxs("div", { className: "flex items-center gap-3 rounded-md border border-border bg-muted/30 p-3", children: [_jsx("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted", children: order.imageUrl ? (_jsx("img", { src: order.imageUrl, alt: "", className: "h-full w-full object-cover" })) : (_jsx(ImageIcon, { className: "h-6 w-6 text-muted-foreground" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "truncate text-base font-semibold text-foreground", children: [order.productName, o.tradeItemVersion ? (_jsxs("span", { className: "ml-2 text-xs font-normal text-muted-foreground", children: ["\u00B7 v", o.tradeItemVersion] })) : null] }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: order.orderNumber || order.sku || "—" })] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-6 lg:grid-cols-[1fr_360px]", children: [_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsx(SectionTitle, { children: "Verkooporder" }), _jsx(Row, { label: "Ordernummer", children: order.orderNumber || "—" }), _jsx(Row, { label: "Handelsvorm", children: tradingType }), _jsx(Row, { label: "Bestelmoment", children: fmtDateTime(order.orderedAt) }), _jsx(Row, { label: "Aantal", children: _jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsxs("div", { children: [formatLoadUnits(order.fustCount, order.crateTypeName), " fusten"] }), _jsxs("div", { children: [formatLoadUnits(order.karCount, order.cartTypeName), " karren", order.karShared ? " (gedeeld met buffer)" : ""] })] }) }), _jsx(Row, { label: "Belading", children: belading }), _jsx(Row, { label: "Klant referentie", children: klantReferentie }), _jsx(Row, { label: "Floriday-referentie", children: floridayRef }), _jsx(Row, { label: "Verkoopkanaal", children: verkoopkanaal }), _jsx(Row, { label: "Financi\u00EBle afhandeling", children: financial }), _jsx(Row, { label: "Verkoper", children: sellerName ? (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsx("span", { className: "inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground", children: initials(sellerName) }), sellerName] })) : ("—") })] }), _jsxs("div", { children: [_jsx(SectionTitle, { children: "Levering" }), _jsx(Row, { label: "Afleverdatum", children: fmtDateTime(order.deliveryAt) }), _jsx(Row, { label: "Incoterm", children: incoterm }), _jsx(Row, { label: "Afleveradres", children: _jsx(AddressCard, { icon: _jsx(MapPin, { className: "h-5 w-5" }), lines: resolvedAddresses?.delivery
|
|
229
|
+
: ""] })] })) : null, order.mixedDeliveryDates ? (_jsxs("div", { className: "rounded-md border border-border bg-muted/40 p-3 text-sm text-foreground", children: [_jsx("div", { className: "font-semibold", children: "Bestellingen met verschillende leverdatums" }), _jsx("div", { className: "text-muted-foreground", children: "Deze order bestaat uit Floriday-bestellingen met verschillende leverdatums. De vroegste leverdatum is aangehouden." })] })) : null, (order.unlinkedProducts?.length ?? 0) > 0 ? (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-foreground", children: [_jsx("div", { className: "font-semibold", children: "Floriday-artikel niet gekoppeld aan product" }), _jsx("ul", { className: "mt-1 space-y-0.5 text-muted-foreground", children: order.unlinkedProducts.map((u, i) => (_jsxs("li", { children: [u.name, " \u2014 trade-item-id ", u.tradeItemId ?? "onbekend"] }, i))) }), _jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: "Koppel dit trade-item aan een product; de volgende synchronisatie neemt het dan over." })] })) : null, _jsxs("div", { className: "flex items-center gap-3 rounded-md border border-border bg-muted/30 p-3", children: [_jsx("div", { className: "flex h-12 w-12 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted", children: order.imageUrl ? (_jsx("img", { src: order.imageUrl, alt: "", className: "h-full w-full object-cover" })) : (_jsx(ImageIcon, { className: "h-6 w-6 text-muted-foreground" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "truncate text-base font-semibold text-foreground", children: [order.productName, o.tradeItemVersion ? (_jsxs("span", { className: "ml-2 text-xs font-normal text-muted-foreground", children: ["\u00B7 v", o.tradeItemVersion] })) : null] }), _jsx("div", { className: "truncate text-xs text-muted-foreground", children: order.orderNumber || order.sku || "—" })] })] }), _jsxs("div", { className: "grid grid-cols-1 gap-6 lg:grid-cols-[1fr_360px]", children: [_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { children: [_jsx(SectionTitle, { children: "Verkooporder" }), _jsx(Row, { label: "Ordernummer", children: order.orderNumber || "—" }), _jsx(Row, { label: "Handelsvorm", children: tradingType }), _jsx(Row, { label: "Bestelmoment", children: fmtDateTime(order.orderedAt) }), _jsx(Row, { label: "Aantal", children: _jsxs("div", { className: "flex flex-wrap gap-4", children: [_jsxs("div", { children: [formatLoadUnits(order.fustCount, order.crateTypeName), " fusten"] }), _jsxs("div", { children: [formatLoadUnits(order.karCount, order.cartTypeName), " karren", order.karShared ? " (gedeeld met buffer)" : "", _jsx(PlanCoverageBadge, { coverage: order.planCoverage })] })] }) }), _jsx(Row, { label: "Belading", children: belading }), _jsx(Row, { label: "Klant referentie", children: klantReferentie }), _jsx(Row, { label: "Floriday-referentie", children: floridayRef }), _jsx(Row, { label: "Verkoopkanaal", children: verkoopkanaal }), _jsx(Row, { label: "Financi\u00EBle afhandeling", children: financial }), _jsx(Row, { label: "Verkoper", children: sellerName ? (_jsxs("span", { className: "inline-flex items-center gap-2", children: [_jsx("span", { className: "inline-flex h-6 w-6 items-center justify-center rounded-full bg-muted text-[10px] font-semibold text-muted-foreground", children: initials(sellerName) }), sellerName] })) : ("—") })] }), _jsxs("div", { children: [_jsx(SectionTitle, { children: "Levering" }), _jsx(Row, { label: "Afleverdatum", children: fmtDateTime(order.deliveryAt) }), _jsx(Row, { label: "Incoterm", children: incoterm }), _jsx(Row, { label: "Afleveradres", children: _jsx(AddressCard, { icon: _jsx(MapPin, { className: "h-5 w-5" }), lines: resolvedAddresses?.delivery
|
|
229
230
|
? [
|
|
230
231
|
resolvedAddresses.delivery.name,
|
|
231
232
|
resolvedAddresses.delivery.line,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SalesOrdersTable.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrdersTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAuBjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"SalesOrdersTable.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrdersTable.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAuBjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAwGnE,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CAG1D;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,MAAM,EACN,OAAO,EACP,aAAa,EACb,aAAa,EACb,WAAW,EACX,cAAc,EACd,YAAY,EACZ,WAAW,EACX,iBAAiB,GAClB,EAAE;IACD,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAChD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;CAC7C,qBA+SA"}
|
|
@@ -6,6 +6,7 @@ import { useAllowedLoadCarriers } from "../../lib/useAllowedLoadCarriers.js";
|
|
|
6
6
|
import { orderStageBadgeClass, orderStageLabel } from "../../lib/order-status.js";
|
|
7
7
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@flowselections/core";
|
|
8
8
|
import { MoreVertical, AlertTriangle, ArrowDown } from "lucide-react";
|
|
9
|
+
import { PlanCoverageBadge } from "./PlanCoverageBadge";
|
|
9
10
|
function formatDateShort(iso) {
|
|
10
11
|
if (!iso)
|
|
11
12
|
return { date: "—", time: "" };
|
|
@@ -109,7 +110,7 @@ export function SalesOrdersTable({ orders, loading, onSelectOrder, onDeleteOrder
|
|
|
109
110
|
.map((u) => `${u.name} (trade-item ${u.tradeItemId ?? "onbekend"})`)
|
|
110
111
|
.join("\n"), children: [_jsx(AlertTriangle, { className: "mt-0.5 h-3 w-3 shrink-0" }), _jsxs("span", { children: ["Floriday-artikel niet gekoppeld aan product:", " ", row.unlinkedProducts
|
|
111
112
|
.map((u) => `${u.name} (trade-item ${u.tradeItemId ?? "onbekend"})`)
|
|
112
|
-
.join(", ")] })] }))] }),
|
|
113
|
+
.join(", ")] })] }))] }), _jsxs(TableCell, { children: [_jsx("div", { className: "font-medium text-foreground", children: row.engineCounts && row.karCount > 0 ? (_jsxs(_Fragment, { children: [formatLoadUnits(row.karCount, row.cartTypeName), row.karShared && (_jsx("span", { className: "ml-1 text-xs font-normal text-muted-foreground", children: "(gedeeld met buffer)" }))] })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) }), _jsx(PlanCoverageBadge, { coverage: row.planCoverage })] }), _jsx(TableCell, { children: _jsx("div", { className: "font-medium text-foreground", children: formatLoadUnits(row.fustCount, row.crateTypeName) }) }), _jsx(TableCell, { className: "text-sm text-foreground", children: row.carrierName || _jsx("span", { className: "text-muted-foreground", children: "\u2014" }) }), _jsx(TableCell, { children: _jsx(StatusTag, { order: row }) }), showRevenue && (_jsxs(TableCell, { className: "font-medium text-foreground", children: ["\u20AC ", Math.round(row.revenue ?? 0).toLocaleString("nl-NL")] })), _jsxs(TableCell, { children: [_jsx("div", { className: "text-sm text-foreground", children: ordered.date }), _jsx("div", { className: "text-xs text-muted-foreground", children: isBuffer
|
|
113
114
|
? `${bufferSize} orders`
|
|
114
115
|
: row.orderNumber || ordered.time })] }), _jsx(TableCell, { onClick: (e) => e.stopPropagation(), children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { className: "inline-flex h-8 w-8 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground", children: _jsx(MoreVertical, { className: "h-4 w-4" }) }), _jsxs(DropdownMenuContent, { align: "end", children: [_jsx(DropdownMenuItem, { disabled: isBuffer || !row.isLocal, onClick: () => !isBuffer && row.isLocal && onEditOrder?.(row), title: isBuffer
|
|
115
116
|
? "Buffer-orders kunnen niet gebundeld bewerkt worden."
|