@flowselections/floriday-verkoop-module 1.0.6 → 1.0.8
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/AnalyticsCards.js +2 -2
- package/dist-lib/components/verkoop/CreateOrderPage.d.ts +2 -0
- package/dist-lib/components/verkoop/CreateOrderPage.d.ts.map +1 -0
- package/dist-lib/components/verkoop/CreateOrderPage.js +221 -0
- package/dist-lib/components/verkoop/DiagnoseConnectionDialog.d.ts +5 -0
- package/dist-lib/components/verkoop/DiagnoseConnectionDialog.d.ts.map +1 -0
- package/dist-lib/components/verkoop/DiagnoseConnectionDialog.js +40 -0
- package/dist-lib/components/verkoop/SalesOrderDetailPage.d.ts +2 -0
- package/dist-lib/components/verkoop/SalesOrderDetailPage.d.ts.map +1 -0
- package/dist-lib/components/verkoop/SalesOrderDetailPage.js +14 -0
- package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts +6 -0
- package/dist-lib/components/verkoop/SalesOrderDetailView.d.ts.map +1 -0
- package/dist-lib/components/verkoop/SalesOrderDetailView.js +213 -0
- package/dist-lib/components/verkoop/SalesOrdersFilterPanel.d.ts +9 -0
- package/dist-lib/components/verkoop/SalesOrdersFilterPanel.d.ts.map +1 -0
- package/dist-lib/components/verkoop/SalesOrdersFilterPanel.js +62 -0
- package/dist-lib/components/verkoop/SalesOrdersFilters.d.ts +13 -0
- package/dist-lib/components/verkoop/SalesOrdersFilters.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrdersFilters.js +123 -23
- package/dist-lib/components/verkoop/SalesOrdersTable.d.ts +3 -2
- package/dist-lib/components/verkoop/SalesOrdersTable.d.ts.map +1 -1
- package/dist-lib/components/verkoop/SalesOrdersTable.js +3 -3
- package/dist-lib/components/verkoop/VerkoopOrdersPage.d.ts.map +1 -1
- package/dist-lib/components/verkoop/VerkoopOrdersPage.js +124 -33
- package/dist-lib/components/verkoop/WeekCalendar.d.ts +6 -0
- package/dist-lib/components/verkoop/WeekCalendar.d.ts.map +1 -0
- package/dist-lib/components/verkoop/WeekCalendar.js +101 -0
- package/dist-lib/index.d.ts +3 -1
- package/dist-lib/index.d.ts.map +1 -1
- package/dist-lib/index.js +3 -1
- package/dist-lib/integrations/supabase/auth-middleware.d.ts +5727 -653
- package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.d.ts +5727 -653
- package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/client.js +1 -1
- package/dist-lib/integrations/supabase/client.server.d.ts +5727 -653
- 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/types.d.ts +5871 -653
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/integrations/supabase/types.js +18 -1
- package/dist-lib/lib/floriday-sync.d.ts +32 -0
- package/dist-lib/lib/floriday-sync.d.ts.map +1 -0
- package/dist-lib/lib/floriday-sync.js +177 -0
- package/dist-lib/lib/salesorders-mapping.d.ts +85 -0
- package/dist-lib/lib/salesorders-mapping.d.ts.map +1 -0
- package/dist-lib/lib/salesorders-mapping.js +198 -0
- package/dist-lib/lib/salesorders.functions.d.ts +19941 -0
- package/dist-lib/lib/salesorders.functions.d.ts.map +1 -0
- package/dist-lib/lib/salesorders.functions.js +216 -0
- package/dist-lib/lib/tradeitems.functions.d.ts +6641 -0
- package/dist-lib/lib/tradeitems.functions.d.ts.map +1 -0
- package/dist-lib/lib/tradeitems.functions.js +60 -0
- package/dist-lib/lib/useSalesOrdersQuery.d.ts +6 -0
- package/dist-lib/lib/useSalesOrdersQuery.d.ts.map +1 -0
- package/dist-lib/lib/useSalesOrdersQuery.js +143 -0
- package/package.json +18 -4
|
@@ -4,8 +4,8 @@ const eur = new Intl.NumberFormat("nl-NL", { style: "currency", currency: "EUR"
|
|
|
4
4
|
const eurPrecise = new Intl.NumberFormat("nl-NL", {
|
|
5
5
|
style: "currency",
|
|
6
6
|
currency: "EUR",
|
|
7
|
-
minimumFractionDigits:
|
|
8
|
-
maximumFractionDigits:
|
|
7
|
+
minimumFractionDigits: 2,
|
|
8
|
+
maximumFractionDigits: 2,
|
|
9
9
|
});
|
|
10
10
|
const num = new Intl.NumberFormat("nl-NL");
|
|
11
11
|
export function AnalyticsCards({ totals, loading, }) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CreateOrderPage.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/CreateOrderPage.tsx"],"names":[],"mappings":"AA6IA,wBAAgB,eAAe,4CAoX9B"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { Badge, Button, Input, Label, Popover, PopoverContent, PopoverTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, toast, } from "@flowselections/core";
|
|
4
|
+
import { Link, useNavigate } from "@tanstack/react-router";
|
|
5
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
6
|
+
import { ArrowLeft, ChevronDown, Plus, Search, Trash2, Truck, MapPin, } from "lucide-react";
|
|
7
|
+
import { supabase } from "../../integrations/supabase/client";
|
|
8
|
+
const eur = new Intl.NumberFormat("nl-NL", {
|
|
9
|
+
style: "currency",
|
|
10
|
+
currency: "EUR",
|
|
11
|
+
});
|
|
12
|
+
function pickStr(v) {
|
|
13
|
+
if (v == null)
|
|
14
|
+
return null;
|
|
15
|
+
if (typeof v === "string")
|
|
16
|
+
return v;
|
|
17
|
+
if (typeof v === "number")
|
|
18
|
+
return String(v);
|
|
19
|
+
if (Array.isArray(v)) {
|
|
20
|
+
for (const x of v) {
|
|
21
|
+
const s = pickStr(x?.text ?? x?.value ?? x);
|
|
22
|
+
if (s)
|
|
23
|
+
return s;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (typeof v === "object") {
|
|
28
|
+
return (v.nl ?? v.NL ?? v.en ?? v.EN ?? v.text ?? v.value ??
|
|
29
|
+
Object.values(v).find((x) => typeof x === "string") ??
|
|
30
|
+
null);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function useCustomersForOrder() {
|
|
35
|
+
return useQuery({
|
|
36
|
+
queryKey: ["create-order", "customers"],
|
|
37
|
+
queryFn: async () => {
|
|
38
|
+
const { data, error } = await supabase
|
|
39
|
+
.from("customers")
|
|
40
|
+
.select("id, company_name, city, is_active")
|
|
41
|
+
.or("is_active.is.null,is_active.eq.true")
|
|
42
|
+
.order("company_name", { ascending: true })
|
|
43
|
+
.limit(2000);
|
|
44
|
+
if (error)
|
|
45
|
+
throw error;
|
|
46
|
+
return (data ?? []);
|
|
47
|
+
},
|
|
48
|
+
staleTime: 60000,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function useProductsForOrder() {
|
|
52
|
+
return useQuery({
|
|
53
|
+
queryKey: ["create-order", "products"],
|
|
54
|
+
queryFn: async () => {
|
|
55
|
+
const [localRes, floridayRes] = await Promise.all([
|
|
56
|
+
supabase
|
|
57
|
+
.from("products")
|
|
58
|
+
.select("id, product, sale_price, unit")
|
|
59
|
+
.order("product", { ascending: true })
|
|
60
|
+
.limit(2000),
|
|
61
|
+
supabase
|
|
62
|
+
.from("floriday_trade_items_cache")
|
|
63
|
+
.select("floriday_id, data")
|
|
64
|
+
.eq("is_deleted", false)
|
|
65
|
+
.limit(5000),
|
|
66
|
+
]);
|
|
67
|
+
if (localRes.error)
|
|
68
|
+
throw localRes.error;
|
|
69
|
+
if (floridayRes.error)
|
|
70
|
+
throw floridayRes.error;
|
|
71
|
+
const out = [];
|
|
72
|
+
for (const r of localRes.data ?? []) {
|
|
73
|
+
out.push({
|
|
74
|
+
id: `local:${r.id}`,
|
|
75
|
+
name: r.product ?? "Onbekend product",
|
|
76
|
+
source: "local",
|
|
77
|
+
price: r.sale_price != null ? Number(r.sale_price) : null,
|
|
78
|
+
unit: r.unit ?? null,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
for (const r of floridayRes.data ?? []) {
|
|
82
|
+
const d = r.data ?? {};
|
|
83
|
+
const name = pickStr(d.name) ??
|
|
84
|
+
pickStr(d.productName) ??
|
|
85
|
+
pickStr(d.tradeItemName) ??
|
|
86
|
+
pickStr(d.tradeItemTexts) ??
|
|
87
|
+
"Onbekend artikel";
|
|
88
|
+
out.push({
|
|
89
|
+
id: `floriday:${r.floriday_id}`,
|
|
90
|
+
name,
|
|
91
|
+
source: "floriday",
|
|
92
|
+
price: null,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
},
|
|
97
|
+
staleTime: 60000,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
export function CreateOrderPage() {
|
|
101
|
+
const navigate = useNavigate();
|
|
102
|
+
const queryClient = useQueryClient();
|
|
103
|
+
const customersQ = useCustomersForOrder();
|
|
104
|
+
const productsQ = useProductsForOrder();
|
|
105
|
+
const [customerId, setCustomerId] = useState("");
|
|
106
|
+
const [deliveryDate, setDeliveryDate] = useState("");
|
|
107
|
+
const [carrier, setCarrier] = useState("");
|
|
108
|
+
const [country, setCountry] = useState("");
|
|
109
|
+
const [productPickerOpen, setProductPickerOpen] = useState(false);
|
|
110
|
+
const [productQuery, setProductQuery] = useState("");
|
|
111
|
+
const [selectedProduct, setSelectedProduct] = useState(null);
|
|
112
|
+
const [quantity, setQuantity] = useState("");
|
|
113
|
+
const [unitPrice, setUnitPrice] = useState("");
|
|
114
|
+
const [lines, setLines] = useState([]);
|
|
115
|
+
const filteredProducts = useMemo(() => {
|
|
116
|
+
const list = productsQ.data ?? [];
|
|
117
|
+
const q = productQuery.trim().toLowerCase();
|
|
118
|
+
const base = q
|
|
119
|
+
? list.filter((p) => p.name.toLowerCase().includes(q))
|
|
120
|
+
: list;
|
|
121
|
+
return base.slice(0, 100);
|
|
122
|
+
}, [productsQ.data, productQuery]);
|
|
123
|
+
const total = lines.reduce((s, l) => s + l.quantity * l.unitPrice, 0);
|
|
124
|
+
const addLine = () => {
|
|
125
|
+
if (!selectedProduct)
|
|
126
|
+
return;
|
|
127
|
+
const qty = Number(quantity);
|
|
128
|
+
const price = Number(unitPrice);
|
|
129
|
+
if (!Number.isFinite(qty) || qty <= 0) {
|
|
130
|
+
toast.error("Vul een geldig aantal in");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!Number.isFinite(price) || price < 0) {
|
|
134
|
+
toast.error("Vul een geldige prijs in");
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
setLines((prev) => [
|
|
138
|
+
...prev,
|
|
139
|
+
{
|
|
140
|
+
key: `${selectedProduct.id}-${Date.now()}`,
|
|
141
|
+
product: selectedProduct,
|
|
142
|
+
quantity: qty,
|
|
143
|
+
unitPrice: price,
|
|
144
|
+
},
|
|
145
|
+
]);
|
|
146
|
+
setSelectedProduct(null);
|
|
147
|
+
setQuantity("");
|
|
148
|
+
setUnitPrice("");
|
|
149
|
+
setProductQuery("");
|
|
150
|
+
};
|
|
151
|
+
const removeLine = (key) => setLines((prev) => prev.filter((l) => l.key !== key));
|
|
152
|
+
const createMutation = useMutation({
|
|
153
|
+
mutationFn: async () => {
|
|
154
|
+
if (!customerId)
|
|
155
|
+
throw new Error("Selecteer een klant");
|
|
156
|
+
if (!deliveryDate)
|
|
157
|
+
throw new Error("Selecteer een leverdatum");
|
|
158
|
+
if (lines.length === 0)
|
|
159
|
+
throw new Error("Voeg minimaal één product toe");
|
|
160
|
+
const customer = customersQ.data?.find((c) => c.id === customerId);
|
|
161
|
+
const orderNumber = `LOC-${new Date()
|
|
162
|
+
.toISOString()
|
|
163
|
+
.slice(0, 10)
|
|
164
|
+
.replace(/-/g, "")}-${Math.floor(Math.random() * 9000 + 1000)}`;
|
|
165
|
+
const { data: orderRow, error: orderErr } = await supabase
|
|
166
|
+
.from("orders")
|
|
167
|
+
.insert({
|
|
168
|
+
order_number: orderNumber,
|
|
169
|
+
customer_id: customerId,
|
|
170
|
+
customer_name: customer?.company_name ?? "Onbekende klant",
|
|
171
|
+
order_date: new Date().toISOString(),
|
|
172
|
+
delivery_date: deliveryDate,
|
|
173
|
+
status: "open",
|
|
174
|
+
total: String(total.toFixed(2)),
|
|
175
|
+
custom_fields: {
|
|
176
|
+
source: "local",
|
|
177
|
+
transporteur: carrier || null,
|
|
178
|
+
land: country || null,
|
|
179
|
+
},
|
|
180
|
+
})
|
|
181
|
+
.select("id")
|
|
182
|
+
.single();
|
|
183
|
+
if (orderErr)
|
|
184
|
+
throw orderErr;
|
|
185
|
+
const itemsPayload = lines.map((l) => ({
|
|
186
|
+
order_id: orderRow.id,
|
|
187
|
+
product_id: l.product.source === "local"
|
|
188
|
+
? l.product.id.replace(/^local:/, "")
|
|
189
|
+
: undefined,
|
|
190
|
+
product_name: l.product.name,
|
|
191
|
+
quantity: Math.round(l.quantity),
|
|
192
|
+
unit: l.product.unit ?? undefined,
|
|
193
|
+
unit_price: l.unitPrice,
|
|
194
|
+
}));
|
|
195
|
+
const { error: itemsErr } = await supabase
|
|
196
|
+
.from("order_items")
|
|
197
|
+
.insert(itemsPayload);
|
|
198
|
+
if (itemsErr)
|
|
199
|
+
throw itemsErr;
|
|
200
|
+
},
|
|
201
|
+
onSuccess: () => {
|
|
202
|
+
toast.success("Bestelling aangemaakt");
|
|
203
|
+
queryClient.invalidateQueries({ queryKey: ["floriday", "salesorders-cache"] });
|
|
204
|
+
navigate({ to: "/floriday-verkoop/verkooporders" });
|
|
205
|
+
},
|
|
206
|
+
onError: (e) => toast.error(e?.message ?? "Aanmaken mislukt"),
|
|
207
|
+
});
|
|
208
|
+
return (_jsxs("main", { className: "space-y-5 p-6", children: [_jsx("div", { children: _jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsxs(Link, { to: "/floriday-verkoop/verkooporders", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar verkooporders"] }) }) }), _jsxs("div", { className: "rounded-lg border border-border bg-card p-6", children: [_jsx("h1", { className: "text-xl font-semibold text-foreground", children: "Nieuwe Bestelling" }), _jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: "Maak een nieuwe bestelling aan door producten te zoeken en toe te voegen." }), _jsxs("div", { className: "mt-6 grid gap-5 md:grid-cols-2", children: [_jsxs("div", { className: "grid gap-1.5", children: [_jsxs(Label, { children: ["Klant ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsxs(Select, { value: customerId, onValueChange: setCustomerId, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Selecteer klant" }) }), _jsxs(SelectContent, { children: [(customersQ.data ?? []).map((c) => (_jsxs(SelectItem, { value: c.id, children: [c.company_name ?? "Onbekend", c.city ? ` — ${c.city}` : ""] }, c.id))), customersQ.data?.length === 0 && (_jsx("div", { className: "px-3 py-2 text-sm text-muted-foreground", children: "Geen klanten gevonden" }))] })] })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsxs(Label, { htmlFor: "delivery", children: ["Leverdatum ", _jsx("span", { className: "text-destructive", children: "*" })] }), _jsx(Input, { id: "delivery", type: "date", value: deliveryDate, onChange: (e) => setDeliveryDate(e.target.value) })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "carrier", children: "Transporteur" }), _jsxs("div", { className: "relative", children: [_jsx(Truck, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { id: "carrier", className: "pl-9", placeholder: "Bv. DHL, PostNL", value: carrier, onChange: (e) => setCarrier(e.target.value) })] })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { htmlFor: "country", children: "Land" }), _jsxs("div", { className: "relative", children: [_jsx(MapPin, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { id: "country", className: "pl-9", placeholder: "Bv. Nederland", value: country, onChange: (e) => setCountry(e.target.value) })] })] })] }), _jsxs("div", { className: "mt-8", children: [_jsx(Label, { children: "Producten toevoegen" }), _jsxs("div", { className: "mt-2 flex flex-wrap items-stretch gap-2", children: [_jsx("div", { className: "min-w-[260px] flex-1", children: _jsxs(Popover, { open: productPickerOpen, onOpenChange: setProductPickerOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { type: "button", variant: "outline", className: "w-full justify-between font-normal", children: [_jsx("span", { className: selectedProduct ? "" : "text-muted-foreground", children: selectedProduct ? selectedProduct.name : "Zoek product..." }), _jsx(ChevronDown, { className: "h-4 w-4 opacity-50" })] }) }), _jsxs(PopoverContent, { align: "start", className: "w-[--radix-popover-trigger-width] p-0", children: [_jsxs("div", { className: "flex items-center gap-2 border-b border-border px-3 py-2", children: [_jsx(Search, { className: "h-4 w-4 text-muted-foreground" }), _jsx("input", { autoFocus: true, placeholder: "Zoek product...", value: productQuery, onChange: (e) => setProductQuery(e.target.value), className: "h-8 w-full bg-transparent text-sm outline-none placeholder:text-muted-foreground" })] }), _jsxs("div", { className: "max-h-72 overflow-y-auto py-1", children: [productsQ.isLoading && (_jsx("div", { className: "px-3 py-6 text-center text-sm text-muted-foreground", children: "Laden\u2026" })), !productsQ.isLoading && filteredProducts.length === 0 && (_jsx("div", { className: "px-3 py-6 text-center text-sm text-muted-foreground", children: "Geen producten gevonden." })), filteredProducts.map((p) => (_jsxs("button", { type: "button", onClick: () => {
|
|
209
|
+
setSelectedProduct(p);
|
|
210
|
+
if (p.price != null && unitPrice === "")
|
|
211
|
+
setUnitPrice(p.price);
|
|
212
|
+
if (quantity === "")
|
|
213
|
+
setQuantity(1);
|
|
214
|
+
setProductPickerOpen(false);
|
|
215
|
+
}, className: "flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-sm hover:bg-accent", children: [_jsx("span", { className: "truncate", children: p.name }), _jsx(Badge, { variant: "outline", className: p.source === "local"
|
|
216
|
+
? "border-primary/30 text-primary"
|
|
217
|
+
: "border-emerald-500/30 text-emerald-600", children: p.source === "local" ? "Lokaal" : "Floriday" })] }, p.id)))] })] })] }) }), _jsx(Input, { type: "number", min: 1, placeholder: "Aantal", className: "w-28", value: quantity, onChange: (e) => setQuantity(e.target.value === "" ? "" : Number(e.target.value)) }), _jsx(Input, { type: "number", min: 0, step: "0.01", placeholder: "\u20AC Prijs", className: "w-32", value: unitPrice, onChange: (e) => setUnitPrice(e.target.value === "" ? "" : Number(e.target.value)) }), _jsx(Button, { type: "button", onClick: addLine, disabled: !selectedProduct, "aria-label": "Toevoegen", children: _jsx(Plus, { className: "h-4 w-4" }) })] }), _jsx("div", { className: "mt-3 rounded-lg border border-dashed border-border bg-muted/30", children: lines.length === 0 ? (_jsx("div", { className: "px-4 py-10 text-center text-sm text-muted-foreground", children: "Voeg producten toe aan de bestelling" })) : (_jsxs("div", { className: "divide-y divide-border", children: [lines.map((l) => (_jsxs("div", { className: "flex items-center gap-3 px-4 py-3 text-sm", children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("div", { className: "truncate font-medium text-foreground", children: l.product.name }), _jsx("div", { className: "text-xs text-muted-foreground", children: l.product.source === "local" ? "Lokaal" : "Floriday" })] }), _jsxs("div", { className: "w-20 text-right", children: [l.quantity, " st."] }), _jsx("div", { className: "w-24 text-right", children: eur.format(l.unitPrice) }), _jsx("div", { className: "w-28 text-right font-medium", children: eur.format(l.quantity * l.unitPrice) }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: () => removeLine(l.key), "aria-label": "Regel verwijderen", children: _jsx(Trash2, { className: "h-4 w-4 text-muted-foreground" }) })] }, l.key))), _jsxs("div", { className: "flex items-center justify-end gap-3 px-4 py-3 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: "Totaal" }), _jsx("span", { className: "text-base font-semibold text-foreground", children: eur.format(total) })] })] })) })] }), _jsxs("div", { className: "mt-8 flex items-center justify-end gap-2", children: [_jsx(Button, { variant: "outline", onClick: () => navigate({ to: "/floriday-verkoop/verkooporders" }), disabled: createMutation.isPending, children: "Annuleren" }), _jsx(Button, { onClick: () => createMutation.mutate(), disabled: createMutation.isPending ||
|
|
218
|
+
!customerId ||
|
|
219
|
+
!deliveryDate ||
|
|
220
|
+
lines.length === 0, children: createMutation.isPending ? "Aanmaken…" : "Bestelling Aanmaken" })] })] })] }));
|
|
221
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DiagnoseConnectionDialog.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/DiagnoseConnectionDialog.tsx"],"names":[],"mappings":"AAiBA,wBAAgB,wBAAwB,CAAC,EACvC,IAAI,EACJ,YAAY,GACb,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;CACpC,2CAmLA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from "react";
|
|
3
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, toast, } from "@flowselections/core";
|
|
4
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
5
|
+
import { CheckCircle2, XCircle, Loader2, RefreshCw } from "lucide-react";
|
|
6
|
+
import { diagnoseFloridayConnection } from "../../lib/salesorders.functions";
|
|
7
|
+
export function DiagnoseConnectionDialog({ open, onOpenChange, }) {
|
|
8
|
+
const run = useServerFn(diagnoseFloridayConnection);
|
|
9
|
+
const [loading, setLoading] = useState(false);
|
|
10
|
+
const [data, setData] = useState(null);
|
|
11
|
+
const fire = async () => {
|
|
12
|
+
try {
|
|
13
|
+
setLoading(true);
|
|
14
|
+
const r = await run();
|
|
15
|
+
setData(r);
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
toast.error(e?.message ?? "Diagnose mislukt");
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
setLoading(false);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (open) {
|
|
26
|
+
setData(null);
|
|
27
|
+
fire();
|
|
28
|
+
}
|
|
29
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
30
|
+
}, [open]);
|
|
31
|
+
const Row = ({ label, ok, value, }) => (_jsxs("div", { className: "flex items-start justify-between gap-4 border-b border-border py-2 last:border-0", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [ok === true ? (_jsx(CheckCircle2, { className: "h-4 w-4 text-emerald-500" })) : ok === false ? (_jsx(XCircle, { className: "h-4 w-4 text-destructive" })) : (_jsx("span", { className: "inline-block h-4 w-4" })), label] }), _jsx("div", { className: "text-right text-sm font-medium", children: value })] }));
|
|
32
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Diagnose Floriday-connectie" }), _jsx(DialogDescription, { children: "Roept GET /sales-orders/current-max-sequence aan met de actieve credentials en toont de cron-status. Bij succes wordt een verouderde sync-fout gewist." })] }), loading && (_jsxs("div", { className: "flex items-center gap-2 py-6 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Bezig met diagnose\u2026"] })), !loading && data && (_jsxs("div", { className: "space-y-1", children: [_jsx(Row, { label: "Omgeving", ok: null, value: data.environment ?? "—" }), _jsx(Row, { label: "GLN (lokaal opgeslagen)", ok: data.localTenant.gln ? true : null, value: _jsx("code", { children: data.localTenant.gln ?? "—" }) }), _jsx(Row, { label: "currentMaxSequence (sales-orders)", ok: data.maxSequence.ok, value: data.maxSequence.ok ? (_jsx("code", { children: data.maxSequence.value ?? 0 })) : (_jsxs("span", { className: "text-destructive", children: ["HTTP ", data.maxSequence.status, " \u2014 ", data.maxSequence.rawError] })) }), _jsx(Row, { label: "last_sequence_number (lokaal)", ok: null, value: _jsx("code", { children: data.cronHealth.lastSequenceNumber }) }), _jsx(Row, { label: "last_synced_at", ok: data.cronHealth.neverSynced ? false : true, value: data.cronHealth.lastSyncedAt ?? "—" }), _jsx(Row, { label: "achterstand (max \u2212 lokaal)", ok: null, value: data.cronHealth.behindBy === null ? ("—") : (_jsx("code", { children: data.cronHealth.behindBy })) }), _jsx(Row, { label: "last_error", ok: data.syncState?.last_error ? false : true, value: data.syncState?.last_error ?? "—" }), _jsxs("div", { className: "mt-3 rounded-md border border-border bg-muted/30 p-3 text-sm", children: [!data.maxSequence.ok &&
|
|
33
|
+
(data.maxSequence.status === 401 || data.maxSequence.status === 403) && (_jsxs(_Fragment, { children: [_jsx("strong", { children: "Token of credentials ongeldig:" }), " Floriday weigert de call (", data.maxSequence.status, "). De auth-module moet", " ", _jsx("code", { children: "client_id" }), "/", _jsx("code", { children: "client_secret" }), " controleren of nieuwe credentials inlezen."] })), !data.maxSequence.ok &&
|
|
34
|
+
data.maxSequence.status !== 401 &&
|
|
35
|
+
data.maxSequence.status !== 403 && (_jsxs(_Fragment, { children: [_jsx("strong", { children: "Floriday onbereikbaar:" }), " HTTP", " ", data.maxSequence.status, ". Geen credentials-probleem; controleer Floriday-status of probeer later opnieuw."] })), data.maxSequence.ok && data.cronHealth.neverSynced && (_jsxs(_Fragment, { children: [_jsx("strong", { children: "Credentials werken, maar de sync heeft nog niets weggeschreven." }), " ", _jsxs("code", { children: ["currentMaxSequence = ", data.maxSequence.value] }), ", maar lokaal staat ", _jsx("code", { children: "last_synced_at = \u2014" }), ". Klik op \"Sync nu\" en raadpleeg de server-function-logs; mogelijk geeft", " ", _jsxs("code", { children: ["/sales-orders/sync/", "{", "seq", "}"] }), " een lege array terug vanaf ", _jsx("code", { children: "fromSequence = 0" }), " (Floriday-retentie)."] })), data.maxSequence.ok &&
|
|
36
|
+
!data.cronHealth.neverSynced &&
|
|
37
|
+
(data.cronHealth.behindBy ?? 0) > 1000 && (_jsxs(_Fragment, { children: [_jsx("strong", { children: "Cron loopt achter:" }), " achterstand van", " ", _jsx("code", { children: data.cronHealth.behindBy }), " sequences. Klik \"Sync nu\" om bij te trekken."] })), data.maxSequence.ok &&
|
|
38
|
+
!data.cronHealth.neverSynced &&
|
|
39
|
+
(data.cronHealth.behindBy ?? 0) <= 1000 && (_jsxs(_Fragment, { children: [_jsx("strong", { children: "OK:" }), " Floriday is bereikbaar en de cron is bij(na) bij. Nieuwe orders verschijnen automatisch."] }))] })] })), _jsxs(DialogFooter, { children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: fire, disabled: loading, children: [_jsx(RefreshCw, { className: "mr-2 h-4 w-4 " + (loading ? "animate-spin" : "") }), "Opnieuw uitvoeren"] }), _jsx(Button, { size: "sm", onClick: () => onOpenChange(false), children: "Sluiten" })] })] }) }));
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SalesOrderDetailPage.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrderDetailPage.tsx"],"names":[],"mappings":"AAMA,wBAAgB,oBAAoB,4CAsCnC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Link, useParams } from "@tanstack/react-router";
|
|
3
|
+
import { Button } from "@flowselections/core";
|
|
4
|
+
import { ArrowLeft } from "lucide-react";
|
|
5
|
+
import { SalesOrderDetailView } from "../../components/verkoop/SalesOrderDetailView";
|
|
6
|
+
import { useSalesOrdersQuery } from "../../lib/useSalesOrdersQuery";
|
|
7
|
+
export function SalesOrderDetailPage() {
|
|
8
|
+
const { salesOrderId } = useParams({
|
|
9
|
+
from: "/_authenticated/floriday-verkoop/verkooporders/$salesOrderId",
|
|
10
|
+
});
|
|
11
|
+
const { data, isLoading, error } = useSalesOrdersQuery();
|
|
12
|
+
const order = data?.orders.find((o) => o.id === salesOrderId) ?? null;
|
|
13
|
+
return (_jsxs("main", { className: "space-y-5 p-6", children: [_jsx("div", { className: "flex items-center justify-between gap-3", children: _jsx(Button, { asChild: true, variant: "ghost", size: "sm", children: _jsxs(Link, { to: "/floriday-verkoop/verkooporders", children: [_jsx(ArrowLeft, { className: "mr-2 h-4 w-4" }), "Terug naar verkooporders"] }) }) }), isLoading && (_jsx("div", { className: "rounded-lg border border-border bg-card p-8 text-sm text-muted-foreground", children: "Laden\u2026" })), error && (_jsx("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 p-4 text-sm text-destructive", children: error.message })), !isLoading && !error && !order && (_jsx("div", { className: "rounded-lg border border-border bg-card p-8 text-sm text-muted-foreground", children: "Order niet gevonden." })), order && (_jsx(SalesOrderDetailView, { order: order, sellerFallback: data?.sellerFallback ?? null }))] }));
|
|
14
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SalesOrderDTO } from "../../lib/salesorders-mapping";
|
|
2
|
+
export declare function SalesOrderDetailView({ order, sellerFallback, }: {
|
|
3
|
+
order: SalesOrderDTO;
|
|
4
|
+
sellerFallback?: string | null;
|
|
5
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=SalesOrderDetailView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SalesOrderDetailView.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrderDetailView.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AA4HnE,wBAAgB,oBAAoB,CAAC,EACnC,KAAK,EACL,cAAc,GACf,EAAE;IACD,KAAK,EAAE,aAAa,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC,2CAiRA"}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { CheckCircle2, ImageIcon } from "lucide-react";
|
|
3
|
+
const eurFlex = new Intl.NumberFormat("nl-NL", {
|
|
4
|
+
style: "currency",
|
|
5
|
+
currency: "EUR",
|
|
6
|
+
minimumFractionDigits: 2,
|
|
7
|
+
maximumFractionDigits: 3,
|
|
8
|
+
});
|
|
9
|
+
function fmtDateTime(iso) {
|
|
10
|
+
if (!iso)
|
|
11
|
+
return "—";
|
|
12
|
+
const d = new Date(iso);
|
|
13
|
+
if (Number.isNaN(d.getTime()))
|
|
14
|
+
return iso;
|
|
15
|
+
return d.toLocaleString("nl-NL", {
|
|
16
|
+
day: "numeric",
|
|
17
|
+
month: "long",
|
|
18
|
+
year: "numeric",
|
|
19
|
+
hour: "2-digit",
|
|
20
|
+
minute: "2-digit",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
function pickStr(v) {
|
|
24
|
+
if (v == null)
|
|
25
|
+
return null;
|
|
26
|
+
if (typeof v === "string")
|
|
27
|
+
return v.length > 0 ? v : null;
|
|
28
|
+
if (typeof v === "number")
|
|
29
|
+
return String(v);
|
|
30
|
+
if (Array.isArray(v)) {
|
|
31
|
+
for (const item of v) {
|
|
32
|
+
const s = pickStr(item?.text ?? item?.value ?? item);
|
|
33
|
+
if (s)
|
|
34
|
+
return s;
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
if (typeof v === "object") {
|
|
39
|
+
return (v.nl ?? v.NL ?? v.en ?? v.EN ?? v.text ?? v.value ??
|
|
40
|
+
Object.values(v).find((x) => typeof x === "string") ?? null);
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const TRADE_INSTRUMENT_LABELS = {
|
|
45
|
+
DIRECT_SALES: "Directe verkoop",
|
|
46
|
+
DIRECT_SALE: "Directe verkoop",
|
|
47
|
+
CLOCK_PRESALES: "Klokvoorverkoop",
|
|
48
|
+
CLOCK_PRESALE: "Klokvoorverkoop",
|
|
49
|
+
CONTRACT: "Contract",
|
|
50
|
+
DAYTRADE: "Daghandel",
|
|
51
|
+
BLANKET_ORDER: "Raamcontract",
|
|
52
|
+
BUNDLED_OFFER: "Bundelaanbod",
|
|
53
|
+
};
|
|
54
|
+
const LOAD_CARRIER_SHORT = {
|
|
55
|
+
DANISH_TROLLEY: "DC",
|
|
56
|
+
DANISH_CONTAINER: "DC",
|
|
57
|
+
CC_CONTAINER: "CC",
|
|
58
|
+
CC_TROLLEY: "CC",
|
|
59
|
+
EURO_PALLET: "EP",
|
|
60
|
+
BLOCK_PALLET: "BP",
|
|
61
|
+
BOX: "BX",
|
|
62
|
+
NONE: "—",
|
|
63
|
+
};
|
|
64
|
+
const SALES_CHANNEL_LABELS = {
|
|
65
|
+
FLORIDAY: "Floriday",
|
|
66
|
+
RFH: "Royal FloraHolland",
|
|
67
|
+
PLANTION: "Plantion",
|
|
68
|
+
};
|
|
69
|
+
const SALES_INTERFACE_LABELS = {
|
|
70
|
+
UI: "user interface",
|
|
71
|
+
API: "api",
|
|
72
|
+
CONNECT: "connect",
|
|
73
|
+
EDI: "edi",
|
|
74
|
+
};
|
|
75
|
+
const PAYMENT_PROVIDER_LABELS = {
|
|
76
|
+
RFH: "Royal FloraHolland",
|
|
77
|
+
RFH_E_WALLET: "Royal FloraHolland (E-wallet)",
|
|
78
|
+
PLANTION: "Plantion",
|
|
79
|
+
DIRECT: "Direct",
|
|
80
|
+
};
|
|
81
|
+
const STATUS_LABELS = {
|
|
82
|
+
COMMITTED: "Order bevestigd",
|
|
83
|
+
CONFIRMED: "Order bevestigd",
|
|
84
|
+
SHIPPED: "Verzonden",
|
|
85
|
+
DELIVERED: "Geleverd",
|
|
86
|
+
CANCELLED: "Geannuleerd",
|
|
87
|
+
OPEN: "Open",
|
|
88
|
+
};
|
|
89
|
+
function initials(name) {
|
|
90
|
+
return name
|
|
91
|
+
.split(/\s+/)
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
.slice(0, 2)
|
|
94
|
+
.map((p) => p[0]?.toUpperCase() ?? "")
|
|
95
|
+
.join("");
|
|
96
|
+
}
|
|
97
|
+
function shortLoadCarrier(raw) {
|
|
98
|
+
if (!raw)
|
|
99
|
+
return "";
|
|
100
|
+
return LOAD_CARRIER_SHORT[raw.toUpperCase()] ?? raw.slice(0, 2).toUpperCase();
|
|
101
|
+
}
|
|
102
|
+
function Row({ label, children }) {
|
|
103
|
+
return (_jsxs("div", { className: "grid grid-cols-[200px_1fr] gap-4 py-2 text-sm", children: [_jsx("div", { className: "text-muted-foreground", children: label }), _jsx("div", { className: "text-foreground", children: children })] }));
|
|
104
|
+
}
|
|
105
|
+
function SectionTitle({ children }) {
|
|
106
|
+
return (_jsx("div", { className: "border-b border-border pb-2 text-sm font-semibold text-foreground", children: children }));
|
|
107
|
+
}
|
|
108
|
+
export function SalesOrderDetailView({ order, sellerFallback, }) {
|
|
109
|
+
const o = order.raw.order ?? {};
|
|
110
|
+
const c = order.raw.customer ?? {};
|
|
111
|
+
const cRaw = c?.raw ?? {};
|
|
112
|
+
const instr = String(o.tradeInstrument ?? o.tradingType ?? o.tradeType ?? "").toUpperCase();
|
|
113
|
+
const tradingType = TRADE_INSTRUMENT_LABELS[instr] ?? (instr ? instr.replace(/_/g, " ").toLowerCase() : "—");
|
|
114
|
+
const pack = o.packingConfiguration ?? {};
|
|
115
|
+
const piecesPerPackage = Number(pack.piecesPerPackage ?? order.quantity.count) || order.quantity.count;
|
|
116
|
+
const packagesPerLayer = Number(pack.packagesPerLayer ?? 0);
|
|
117
|
+
const layersPerLoadCarrier = Number(pack.layersPerLoadCarrier ?? 0);
|
|
118
|
+
const numberOfPieces = Number(o.numberOfPieces ?? piecesPerPackage * order.quantity.multiplier);
|
|
119
|
+
const packages = piecesPerPackage > 0 ? Math.ceil(numberOfPieces / piecesPerPackage) : order.quantity.multiplier;
|
|
120
|
+
const vbnCode = pack.package?.vbnPackageCode ?? pack.vbnPackageCode ?? null;
|
|
121
|
+
const lcShort = shortLoadCarrier(pickStr(pack.loadCarrier) ?? pickStr(o.loadCarrier));
|
|
122
|
+
const beladingParts = [];
|
|
123
|
+
if (vbnCode)
|
|
124
|
+
beladingParts.push(String(vbnCode));
|
|
125
|
+
if (layersPerLoadCarrier && packagesPerLayer && piecesPerPackage) {
|
|
126
|
+
beladingParts.push(`${layersPerLoadCarrier}×${packagesPerLayer}×${piecesPerPackage}`);
|
|
127
|
+
}
|
|
128
|
+
if (lcShort)
|
|
129
|
+
beladingParts.push(lcShort);
|
|
130
|
+
const belading = beladingParts.length > 0 ? beladingParts.join(" - ") : "—";
|
|
131
|
+
const klantReferentie = pickStr(o.customerOrderId) ?? "—";
|
|
132
|
+
const floridayRef = pickStr(o.salesChannelOrderId) ??
|
|
133
|
+
pickStr(o.batchReference) ??
|
|
134
|
+
pickStr(o.salesOrderId)?.slice(0, 16) ??
|
|
135
|
+
"—";
|
|
136
|
+
const channelKey = String(o.salesChannel ?? "").toUpperCase();
|
|
137
|
+
const interfaceKey = String(o.salesChannelInterface ?? "").toUpperCase();
|
|
138
|
+
const channelLabel = SALES_CHANNEL_LABELS[channelKey] ?? (channelKey || "—");
|
|
139
|
+
const interfaceLabel = SALES_INTERFACE_LABELS[interfaceKey] ?? (interfaceKey ? interfaceKey.toLowerCase() : "");
|
|
140
|
+
const verkoopkanaal = interfaceLabel ? `${channelLabel} (${interfaceLabel})` : channelLabel;
|
|
141
|
+
const payKey = String(o.paymentProvider ?? "").toUpperCase();
|
|
142
|
+
const financial = PAYMENT_PROVIDER_LABELS[payKey] ?? (payKey || "—");
|
|
143
|
+
const sellerName = pickStr(o.createdByUserName) ??
|
|
144
|
+
pickStr(o.sellerName) ??
|
|
145
|
+
pickStr(o.seller?.name) ??
|
|
146
|
+
pickStr(o.salesEmployeeName) ??
|
|
147
|
+
sellerFallback ??
|
|
148
|
+
null;
|
|
149
|
+
const delivLoc = o.delivery?.location ?? o.delivery?.deliveryLocation ?? o.deliveryLocation ?? {};
|
|
150
|
+
const delivAddrObj = delivLoc.address ?? null;
|
|
151
|
+
const fallbackAddr = cRaw.mailingAddress ?? cRaw.physicalAddress ?? null;
|
|
152
|
+
const addr = {
|
|
153
|
+
line: pickStr(delivAddrObj?.addressLine) ??
|
|
154
|
+
pickStr(delivLoc.addressLine) ??
|
|
155
|
+
pickStr(c.street) ??
|
|
156
|
+
pickStr(fallbackAddr?.addressLine) ??
|
|
157
|
+
null,
|
|
158
|
+
postal: pickStr(delivAddrObj?.postalCode) ??
|
|
159
|
+
pickStr(delivLoc.postalCode) ??
|
|
160
|
+
pickStr(c.postal_code) ??
|
|
161
|
+
pickStr(fallbackAddr?.postalCode) ??
|
|
162
|
+
null,
|
|
163
|
+
city: pickStr(delivAddrObj?.city) ??
|
|
164
|
+
pickStr(delivLoc.city) ??
|
|
165
|
+
pickStr(c.city) ??
|
|
166
|
+
pickStr(fallbackAddr?.city) ??
|
|
167
|
+
null,
|
|
168
|
+
country: pickStr(delivAddrObj?.countryCode) ??
|
|
169
|
+
pickStr(delivAddrObj?.country) ??
|
|
170
|
+
pickStr(delivLoc.country) ??
|
|
171
|
+
pickStr(c.country) ??
|
|
172
|
+
pickStr(fallbackAddr?.countryCode) ??
|
|
173
|
+
null,
|
|
174
|
+
gln: pickStr(delivLoc.gln) ??
|
|
175
|
+
pickStr(delivLoc.glnCode) ??
|
|
176
|
+
pickStr(delivLoc.locationGln) ??
|
|
177
|
+
null,
|
|
178
|
+
};
|
|
179
|
+
const countryLabel = (() => {
|
|
180
|
+
if (!addr.country)
|
|
181
|
+
return null;
|
|
182
|
+
const map = { NL: "Nederland", BE: "België", DE: "Duitsland", FR: "Frankrijk", ES: "Spanje", IT: "Italië", GB: "Verenigd Koninkrijk", PL: "Polen", DK: "Denemarken" };
|
|
183
|
+
return map[addr.country.toUpperCase()] ?? addr.country;
|
|
184
|
+
})();
|
|
185
|
+
const incoterm = pickStr(o.delivery?.incoterm) ?? pickStr(o.incoterm) ?? "—";
|
|
186
|
+
const productUnit = Number(o.calculatedFields?.totalPricePerPiece?.value ?? o.pricePerPiece?.value ?? order.quantity.unitPrice) || 0;
|
|
187
|
+
const purchaseUnit = Number(o.pricePerPiece?.value ?? productUnit) || 0;
|
|
188
|
+
const transportPerPiece = Number(o.deliveryPricePerPiece ?? Math.max(0, productUnit - purchaseUnit)) || 0;
|
|
189
|
+
const totalAmount = Number(o.calculatedFields?.orderAmount?.value ?? order.revenue) || 0;
|
|
190
|
+
let oldProductPrice = null;
|
|
191
|
+
const mutations = Array.isArray(o.mutations) ? o.mutations : [];
|
|
192
|
+
const lastCorrection = [...mutations]
|
|
193
|
+
.reverse()
|
|
194
|
+
.find((m) => String(m?.mutationType ?? "").toUpperCase() === "CORRECTION_REQUEST");
|
|
195
|
+
if (lastCorrection) {
|
|
196
|
+
const oldP = Number(lastCorrection.oldValues?.totalPricePerPiece ?? lastCorrection.oldValues?.pricePerPiece);
|
|
197
|
+
if (Number.isFinite(oldP) && Math.abs(oldP - productUnit) > 1e-9)
|
|
198
|
+
oldProductPrice = oldP;
|
|
199
|
+
}
|
|
200
|
+
const hasCorrection = !!lastCorrection;
|
|
201
|
+
const customerName = pickStr(cRaw.commercialName) ?? pickStr(c.commercial_name) ?? pickStr(c.company_name) ?? order.customer.name;
|
|
202
|
+
const customerGln = pickStr(c.gln) ?? pickStr(cRaw.companyGln) ?? "—";
|
|
203
|
+
const customerAdmin = pickStr(c.custom_fields?.rfh_relation_id) ??
|
|
204
|
+
pickStr(cRaw.rfhRelationId) ??
|
|
205
|
+
pickStr(cRaw.rfhCompanyAccountId) ??
|
|
206
|
+
"—";
|
|
207
|
+
const customerPhone = pickStr(c.phone) ?? pickStr(c.phone_number) ?? pickStr(c.contact_person?.phone) ?? "—";
|
|
208
|
+
const statusKey = String(o.status ?? "").toUpperCase();
|
|
209
|
+
const bestellingLabel = order.status.isCancelled
|
|
210
|
+
? "Geannuleerd"
|
|
211
|
+
: STATUS_LABELS[statusKey] ?? (order.status.isShipped ? "Verzonden" : order.status.isConfirmed ? "Order bevestigd" : "Open");
|
|
212
|
+
return (_jsxs("div", { className: "space-y-6", children: [_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: pickStr(o.customerOrderId) ?? order.sku ?? order.orderNumber })] })] }), _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: "Handelsvorm", children: tradingType }), _jsx(Row, { label: "Bestelmoment", children: fmtDateTime(order.orderedAt) }), _jsxs(Row, { label: "Aantal", children: [packages, " fusten (", piecesPerPackage, " st/f)"] }), _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: "Levering", children: _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: _jsxs("svg", { viewBox: "0 0 24 24", className: "h-5 w-5", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("path", { d: "M3 11l9-8 9 8" }), _jsx("path", { d: "M5 10v10h14V10" }), _jsx("path", { d: "M10 20v-6h4v6" })] }) }), _jsxs("div", { className: "text-sm", children: [_jsx("div", { children: addr.line || "—" }), _jsx("div", { children: [addr.postal, addr.city].filter(Boolean).join(" ") || "—" }), _jsx("div", { children: countryLabel ?? "" }), addr.gln && (_jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: addr.gln }))] })] }) })] }), _jsxs("div", { children: [_jsx(SectionTitle, { children: "Klant" }), _jsx(Row, { label: "Naam", children: _jsx("span", { className: "font-medium", children: customerName }) }), _jsx(Row, { label: "GLN", children: customerGln }), _jsx(Row, { label: "Administratienummer", children: customerAdmin }), _jsx(Row, { label: "Telefoonnummer", children: customerPhone })] })] }), _jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "rounded-md border border-border p-4", children: [_jsx("div", { className: "mb-3 text-sm font-semibold", children: "Prijs per stuk" }), _jsxs("div", { className: "space-y-2 text-sm", children: [_jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Product" }), _jsxs("span", { children: [oldProductPrice !== null && (_jsx("span", { className: "mr-2 text-destructive line-through", children: eurFlex.format(oldProductPrice) })), eurFlex.format(productUnit)] })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Transportkosten" }), _jsx("span", { children: eurFlex.format(transportPerPiece) })] }), _jsxs("div", { className: "flex justify-between", children: [_jsx("span", { className: "text-muted-foreground", children: "Inkoopprijs" }), _jsx("span", { children: eurFlex.format(purchaseUnit) })] }), _jsxs("div", { className: "mt-2 flex justify-between border-t border-border pt-2 font-semibold", children: [_jsx("span", { children: "Totaal" }), _jsxs("span", { children: [_jsxs("span", { className: "mr-2 text-xs font-normal text-muted-foreground", children: ["(", packages, " \u00D7 ", piecesPerPackage, " \u00D7 ", eurFlex.format(productUnit), ")"] }), eurFlex.format(totalAmount)] })] })] })] }), _jsxs("div", { className: "rounded-md border border-border p-4", children: [_jsx("div", { className: "mb-3 text-sm font-semibold", children: "Status" }), _jsxs("div", { className: "space-y-3 text-sm", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-muted-foreground", children: "Bestelling" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-emerald-600" }), bestellingLabel] })] }), hasCorrection && (_jsxs("div", { className: "flex items-center justify-between", children: [_jsx("div", { className: "text-muted-foreground", children: "Correctie" }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(CheckCircle2, { className: "h-4 w-4 text-emerald-600" }), "Correctie is verwerkt"] })] }))] })] })] })] })] }));
|
|
213
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Filters } from "./SalesOrdersFilters";
|
|
2
|
+
export declare function countActiveFilters(f: Filters): number;
|
|
3
|
+
export declare function SalesOrdersFilterPanel({ open, onOpenChange, value, onChange, }: {
|
|
4
|
+
open: boolean;
|
|
5
|
+
onOpenChange: (open: boolean) => void;
|
|
6
|
+
value: Filters;
|
|
7
|
+
onChange: (next: Filters) => void;
|
|
8
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=SalesOrdersFilterPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SalesOrdersFilterPanel.d.ts","sourceRoot":"","sources":["../../../src/components/verkoop/SalesOrdersFilterPanel.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAYrD;AAED,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,YAAY,EACZ,KAAK,EACL,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACnC,2CA2OA"}
|