@flowselections/floriday-voorraad 1.0.84 → 1.0.87
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/voorraad/FloridayTakeoverPanel.d.ts +7 -0
- package/dist-lib/components/voorraad/FloridayTakeoverPanel.d.ts.map +1 -0
- package/dist-lib/components/voorraad/FloridayTakeoverPanel.js +196 -0
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +2 -1
- package/dist-lib/integrations/supabase/types.d.ts +733 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/__tests__/floriday-fetch-status.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/floriday-fetch-status.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/floriday-fetch-status.test.js +25 -0
- package/dist-lib/lib/floriday-fetch-status.d.ts +17 -0
- package/dist-lib/lib/floriday-fetch-status.d.ts.map +1 -0
- package/dist-lib/lib/floriday-fetch-status.js +14 -0
- package/dist-lib/lib/floriday-takeover.functions.d.ts +65 -0
- package/dist-lib/lib/floriday-takeover.functions.d.ts.map +1 -0
- package/dist-lib/lib/floriday-takeover.functions.js +231 -0
- package/dist-lib/server-hooks/floriday-stock-reconcile.d.ts.map +1 -1
- package/dist-lib/server-hooks/floriday-stock-reconcile.js +5 -1
- package/dist-lib/server-hooks/guard.d.ts.map +1 -1
- package/dist-lib/server-hooks/guard.js +26 -3
- package/package.json +1 -1
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function FloridayLeadingSummary({ connectionId }: {
|
|
2
|
+
connectionId: string | null;
|
|
3
|
+
}): import("react").JSX.Element | null;
|
|
4
|
+
export declare function FloridayTakeoverPanel({ connectionId }: {
|
|
5
|
+
connectionId: string | null;
|
|
6
|
+
}): import("react").JSX.Element | null;
|
|
7
|
+
//# sourceMappingURL=FloridayTakeoverPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FloridayTakeoverPanel.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayTakeoverPanel.tsx"],"names":[],"mappings":"AA4CA,wBAAgB,sBAAsB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,sCAoCvF;AAED,wBAAgB,qBAAqB,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,sCA2QtF"}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Beheer: "Overnemen uit Floriday" en "ERP leidend maken".
|
|
4
|
+
* - Proefrapport (alleen lezen) → keuze → overnemen in één transactie → terugdraaien.
|
|
5
|
+
* - Overnemen zet nooit iets in de wachtrij: overgenomen artikelen zijn Floriday-leidend.
|
|
6
|
+
* - ERP leidend maken toont eerst wat er naar Floriday zou gaan.
|
|
7
|
+
* Toont ook "Floriday leidend voor X, ERP voor Y" en de alleen-lezen-stand
|
|
8
|
+
* (uit Floriday-authenticatie: floriday_write_mode()).
|
|
9
|
+
*/
|
|
10
|
+
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
11
|
+
import { useServerFn } from "@tanstack/react-start";
|
|
12
|
+
import { toast } from "sonner";
|
|
13
|
+
import * as XLSX from "xlsx";
|
|
14
|
+
import { Button } from "@flowselections/core";
|
|
15
|
+
import { supabase } from "../../integrations/supabase/client.js";
|
|
16
|
+
import { getFloridayTakeoverPreview } from "../../lib/floriday-takeover.functions.js";
|
|
17
|
+
const sb = supabase;
|
|
18
|
+
const eur = (v) => v == null ? "—" : `€ ${Number(v).toLocaleString("nl-NL", { minimumFractionDigits: 2, maximumFractionDigits: 4 })}`;
|
|
19
|
+
const num = (v) => (v == null ? "—" : Number(v).toLocaleString("nl-NL"));
|
|
20
|
+
function offersText(o) {
|
|
21
|
+
return o.map((x) => [x.customer ?? "Klant onbekend", x.price != null ? eur(x.price) : null, x.pieces != null ? `${num(x.pieces)} st.` : null,
|
|
22
|
+
x.from || x.to ? `${(x.from ?? "").slice(0, 10)} – ${(x.to ?? "").slice(0, 10)}` : null].filter(Boolean).join(" ")).join("; ");
|
|
23
|
+
}
|
|
24
|
+
function formsText(f) {
|
|
25
|
+
const parts = [];
|
|
26
|
+
if (f.continuous)
|
|
27
|
+
parts.push("Doorlopende voorraad");
|
|
28
|
+
if (f.weekly)
|
|
29
|
+
parts.push(`Weekaanbod (${f.weeks.map((w) => `wk ${w.week}: ${w.pieces}`).join(", ")})`);
|
|
30
|
+
if (f.supply_lines)
|
|
31
|
+
parts.push(`${f.supply_lines} aanbodregel${f.supply_lines === 1 ? "" : "s"}`);
|
|
32
|
+
if (f.customer_offers)
|
|
33
|
+
parts.push(`${f.customer_offers} klantaanbieding${f.customer_offers === 1 ? "" : "en"} (alleen getoond)`);
|
|
34
|
+
return parts.join(" · ") || "Geen aanbod gevonden";
|
|
35
|
+
}
|
|
36
|
+
export function FloridayLeadingSummary({ connectionId }) {
|
|
37
|
+
const [summary, setSummary] = useState(null);
|
|
38
|
+
const [mode, setMode] = useState(null);
|
|
39
|
+
const [modeError, setModeError] = useState(null);
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
if (!connectionId)
|
|
42
|
+
return;
|
|
43
|
+
sb.rpc("floriday_leading_summary", { p_connection_id: connectionId }).then(({ data }) => setSummary(data ?? null));
|
|
44
|
+
sb.rpc("floriday_write_mode").then(({ data, error }) => {
|
|
45
|
+
if (error)
|
|
46
|
+
setModeError(error.message);
|
|
47
|
+
else
|
|
48
|
+
setMode(data ?? null);
|
|
49
|
+
});
|
|
50
|
+
}, [connectionId]);
|
|
51
|
+
if (!connectionId)
|
|
52
|
+
return null;
|
|
53
|
+
const erp = (summary?.erp_leading ?? 0) + (summary?.legacy ?? 0);
|
|
54
|
+
return (_jsxs("div", { className: "flex flex-wrap items-center gap-x-6 gap-y-1 rounded-md border bg-muted/40 px-3 py-2 text-sm", children: [_jsxs("span", { children: ["Floriday is leidend voor ", _jsx("strong", { children: num(summary?.floriday_leading ?? 0) }), " artikelen, je eigen voorraad voor", " ", _jsx("strong", { children: num(erp) }), " artikelen", summary?.off ? _jsxs(_Fragment, { children: [" \u00B7 ", num(summary.off), " staan op Uit"] }) : null, "."] }), _jsxs("span", { children: ["Floriday:", " ", modeError ? (_jsx("span", { className: "text-muted-foreground", children: "stand onbekend \u2014 er wordt niets verstuurd zolang dit niet bekend is" })) : mode?.read_only === false ? (_jsx("strong", { children: "schrijven toegestaan" })) : (_jsx("strong", { children: "alleen lezen" })), mode?.environment ? _jsxs("span", { className: "text-muted-foreground", children: [" (", mode.environment, ")"] }) : null] })] }));
|
|
55
|
+
}
|
|
56
|
+
export function FloridayTakeoverPanel({ connectionId }) {
|
|
57
|
+
const preview = useServerFn(getFloridayTakeoverPreview);
|
|
58
|
+
const [open, setOpen] = useState(false);
|
|
59
|
+
const [loading, setLoading] = useState(false);
|
|
60
|
+
const [rows, setRows] = useState(null);
|
|
61
|
+
const [weekError, setWeekError] = useState(null);
|
|
62
|
+
const [fetchStatus, setFetchStatus] = useState(null);
|
|
63
|
+
const itemsBlocked = fetchStatus != null && fetchStatus.tradeItems !== "compleet";
|
|
64
|
+
const [selected, setSelected] = useState(new Set());
|
|
65
|
+
const [busy, setBusy] = useState(false);
|
|
66
|
+
const [runs, setRuns] = useState([]);
|
|
67
|
+
const [leadRows, setLeadRows] = useState([]);
|
|
68
|
+
const [leadSel, setLeadSel] = useState(new Set());
|
|
69
|
+
const [erpPreview, setErpPreview] = useState(null);
|
|
70
|
+
const loadRuns = useCallback(async () => {
|
|
71
|
+
const { data } = await sb.from("floriday_takeover_runs").select("*").order("created_at", { ascending: false }).limit(5);
|
|
72
|
+
setRuns(data ?? []);
|
|
73
|
+
}, []);
|
|
74
|
+
const loadLeadRows = useCallback(async () => {
|
|
75
|
+
if (!connectionId)
|
|
76
|
+
return;
|
|
77
|
+
const { data } = await sb
|
|
78
|
+
.from("floriday_inventory_settings")
|
|
79
|
+
.select("product_id, products(product)")
|
|
80
|
+
.eq("connection_id", connectionId)
|
|
81
|
+
.eq("erp_leading", false)
|
|
82
|
+
.limit(2000);
|
|
83
|
+
setLeadRows((data ?? []).map((r) => ({ product_id: r.product_id, name: r.products?.product ?? r.product_id })));
|
|
84
|
+
}, [connectionId]);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (open) {
|
|
87
|
+
loadRuns();
|
|
88
|
+
loadLeadRows();
|
|
89
|
+
}
|
|
90
|
+
}, [open, loadRuns, loadLeadRows]);
|
|
91
|
+
const runPreview = async () => {
|
|
92
|
+
setLoading(true);
|
|
93
|
+
try {
|
|
94
|
+
const res = await preview();
|
|
95
|
+
setRows(res.rows);
|
|
96
|
+
setWeekError(res.weekError);
|
|
97
|
+
setFetchStatus(res.fetchStatus);
|
|
98
|
+
setSelected(new Set(res.rows.filter((r) => r.action !== "niet").map((r) => r.tradeItemId)));
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
toast.error("Het proefrapport kon niet worden gemaakt.", { description: e?.message });
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
setLoading(false);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const chosen = useMemo(() => (rows ?? []).filter((r) => selected.has(r.tradeItemId) && r.action !== "niet"), [rows, selected]);
|
|
108
|
+
const download = () => {
|
|
109
|
+
if (!rows)
|
|
110
|
+
return;
|
|
111
|
+
const data = rows.map((r) => ({
|
|
112
|
+
Artikel: r.name, "Floriday-nummer": r.tradeItemId, Artikelcode: r.articleCode ?? "", Barcode: r.gtin ?? "", "VBN-code": r.vbnCode ?? "",
|
|
113
|
+
Actie: r.action, Reden: r.reason ?? "", Suggestie: r.suggestion ?? "",
|
|
114
|
+
"Floriday aantal": r.floridayStock ?? "", "Bron aantal": r.stockSource ?? "",
|
|
115
|
+
"Prijs aanbodregel": r.supplyLinePrice ?? "", Catalogusprijs: r.catalogPrice ?? "", "Gekozen prijs": r.floridayPrice ?? "", "Bron prijs": r.priceSource ?? "",
|
|
116
|
+
"Eigen voorraad": r.erpStock ?? "", "Eigen prijs": r.erpPrice ?? "", Aanbodvorm: formsText(r.forms),
|
|
117
|
+
Klantaanbiedingen: offersText(r.customerOffers),
|
|
118
|
+
}));
|
|
119
|
+
const wb = XLSX.utils.book_new();
|
|
120
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(data), "Proefrapport");
|
|
121
|
+
XLSX.writeFile(wb, `overnemen-uit-floriday-${new Date().toISOString().slice(0, 10)}.xlsx`);
|
|
122
|
+
};
|
|
123
|
+
const apply = async () => {
|
|
124
|
+
if (!connectionId || chosen.length === 0 || itemsBlocked)
|
|
125
|
+
return;
|
|
126
|
+
const created = chosen.filter((r) => r.action === "aanmaken").length;
|
|
127
|
+
if (!window.confirm(`${chosen.length} artikelen overnemen uit Floriday${created ? `, waarvan ${created} nieuw aanmaken` : ""}? ` +
|
|
128
|
+
"Voorraad en prijs worden overgenomen. Er gaat niets naar Floriday; deze artikelen blijven Floriday-leidend."))
|
|
129
|
+
return;
|
|
130
|
+
setBusy(true);
|
|
131
|
+
const items = chosen.map((r) => ({
|
|
132
|
+
trade_item_id: r.tradeItemId, create: r.action === "aanmaken", name: r.name,
|
|
133
|
+
article_code: r.articleCode, gtin: r.gtin, vbn_code: r.vbnCode,
|
|
134
|
+
stock: r.floridayStock, price: r.floridayPrice,
|
|
135
|
+
stock_source: r.stockSource, price_source: r.priceSource, offer_forms: r.forms,
|
|
136
|
+
}));
|
|
137
|
+
const { data, error } = await sb.rpc("floriday_takeover_apply", { p_connection_id: connectionId, p_items: items });
|
|
138
|
+
setBusy(false);
|
|
139
|
+
if (error) {
|
|
140
|
+
toast.error("Overnemen is niet gelukt; er is niets veranderd.", { description: error.message });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
toast.success(`${data?.items ?? 0} artikelen overgenomen${data?.created_products ? `, ${data.created_products} nieuw` : ""}.`);
|
|
144
|
+
setRows(null);
|
|
145
|
+
loadRuns();
|
|
146
|
+
loadLeadRows();
|
|
147
|
+
};
|
|
148
|
+
const undo = async (runId) => {
|
|
149
|
+
if (!window.confirm("Deze overname terugdraaien? Artikelen die sindsdien zijn gewijzigd blijven staan. Nieuw aangemaakte artikelen blijven bestaan (op Uit)."))
|
|
150
|
+
return;
|
|
151
|
+
const { data, error } = await sb.rpc("floriday_takeover_undo", { p_run_id: runId });
|
|
152
|
+
if (error) {
|
|
153
|
+
toast.error("Terugdraaien is niet gelukt.", { description: error.message });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
toast.success(`${data?.undone ?? 0} teruggedraaid${data?.skipped ? `, ${data.skipped} overgeslagen omdat ze later zijn gewijzigd` : ""}.`);
|
|
157
|
+
loadRuns();
|
|
158
|
+
};
|
|
159
|
+
const showErpPreview = async () => {
|
|
160
|
+
if (!connectionId || leadSel.size === 0)
|
|
161
|
+
return;
|
|
162
|
+
const { data, error } = await sb.rpc("floriday_erp_leading_preview", { p_connection_id: connectionId, p_product_ids: [...leadSel] });
|
|
163
|
+
if (error) {
|
|
164
|
+
toast.error("Voorbeeld kon niet worden gemaakt.", { description: error.message });
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
setErpPreview(data ?? []);
|
|
168
|
+
};
|
|
169
|
+
const confirmErp = async () => {
|
|
170
|
+
if (!connectionId || !erpPreview)
|
|
171
|
+
return;
|
|
172
|
+
const ok = erpPreview.filter((r) => !r.blocker).map((r) => r.product_id);
|
|
173
|
+
if (ok.length === 0) {
|
|
174
|
+
toast.error("Geen van deze artikelen kan je eigen voorraad gaan volgen.");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const { data, error } = await sb.rpc("floriday_make_erp_leading", { p_connection_id: connectionId, p_product_ids: ok });
|
|
178
|
+
if (error) {
|
|
179
|
+
toast.error("Omzetten is niet gelukt.", { description: error.message });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const sk = (data?.skipped ?? []);
|
|
183
|
+
toast.success(`${data?.updated ?? 0} artikelen volgen nu je eigen voorraad.`, sk.length ? { description: `${sk.length} overgeslagen: ${sk.map((s) => s.reason).join("; ")}` } : undefined);
|
|
184
|
+
setErpPreview(null);
|
|
185
|
+
setLeadSel(new Set());
|
|
186
|
+
loadLeadRows();
|
|
187
|
+
};
|
|
188
|
+
if (!connectionId)
|
|
189
|
+
return null;
|
|
190
|
+
return (_jsxs("section", { className: "rounded-md border p-3 space-y-3", children: [_jsxs("button", { type: "button", className: "text-sm font-medium", onClick: () => setOpen((v) => !v), children: [open ? "▾" : "▸", " Overnemen uit Floriday en eigen voorraad leidend maken"] }), open && (_jsxs("div", { className: "space-y-6", children: [_jsxs("div", { className: "space-y-2", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Neem voorraad en prijs over uit Floriday zonder iets terug te sturen. Maak eerst een proefrapport; er wordt dan nog niets gewijzigd." }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { size: "sm", onClick: runPreview, disabled: loading, children: loading ? "Floriday lezen…" : "Proefrapport maken" }), rows && _jsx(Button, { size: "sm", variant: "outline", onClick: download, children: "Download als Excel" }), rows && _jsx(Button, { size: "sm", onClick: apply, disabled: busy || chosen.length === 0 || itemsBlocked, children: busy ? "Bezig…" : `Overnemen (${chosen.length})` })] }), fetchStatus && fetchStatus.tradeItems !== "compleet" && (_jsx("p", { className: "text-sm text-destructive", children: fetchStatus.tradeItems === "bezig"
|
|
191
|
+
? "Artikelen worden nog opgehaald – wacht tot dit compleet is. Overnemen kan daarna."
|
|
192
|
+
: "De artikelen uit Floriday zijn nog niet opgehaald – wacht tot dit compleet is. Overnemen kan daarna." })), fetchStatus && fetchStatus.tradeItems === "compleet" && (fetchStatus.catalogPrices === "bezig" || fetchStatus.customerOffers === "bezig") && (_jsx("p", { className: "text-sm text-muted-foreground", children: "Het aanbod wordt nog opgehaald \u2013 wacht tot dit compleet is voor de juiste prijzen. Maak daarna het proefrapport opnieuw." })), weekError && (_jsxs("p", { className: "text-sm text-destructive", children: ["Het weekaanbod kon niet worden gelezen; het rapport gebruikt alleen de overige gegevens.", " ", _jsxs("details", { className: "inline", children: [_jsx("summary", { className: "inline cursor-pointer", children: "Details voor beheer" }), weekError] })] })), rows && (_jsx("div", { className: "max-h-[28rem] overflow-auto rounded border", children: _jsxs("table", { className: "w-full text-xs", children: [_jsx("thead", { className: "sticky top-0 bg-muted", children: _jsxs("tr", { className: "text-left", children: [_jsx("th", { className: "p-1" }), _jsx("th", { className: "p-1", children: "Artikel" }), _jsx("th", { className: "p-1", children: "Actie" }), _jsx("th", { className: "p-1", children: "Floriday aantal" }), _jsx("th", { className: "p-1", children: "Floriday prijs" }), _jsx("th", { className: "p-1", children: "Nu in je voorraad" }), _jsx("th", { className: "p-1", children: "Aanbodvorm" })] }) }), _jsx("tbody", { children: rows.map((r) => (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "p-1", children: _jsx("input", { type: "checkbox", disabled: r.action === "niet", checked: selected.has(r.tradeItemId), onChange: (e) => setSelected((s) => { const n = new Set(s); e.target.checked ? n.add(r.tradeItemId) : n.delete(r.tradeItemId); return n; }) }) }), _jsxs("td", { className: "p-1", children: [_jsx("div", { className: "font-medium", children: r.name }), _jsx("div", { className: "text-muted-foreground", children: [r.articleCode, r.gtin, r.vbnCode && `VBN ${r.vbnCode}`].filter(Boolean).join(" · ") }), r.suggestion && _jsx("div", { className: "text-amber-700", children: r.suggestion })] }), _jsxs("td", { className: "p-1", children: [r.action === "aanmaken" ? "Nieuw aanmaken (Uit)" : r.action === "bijwerken" ? "Bijwerken" : "Niet over te nemen", r.reason && _jsx("div", { className: "text-muted-foreground", children: r.reason })] }), _jsxs("td", { className: "p-1", children: [num(r.floridayStock), _jsx("div", { className: "text-muted-foreground", children: r.stockSource ?? "" })] }), _jsxs("td", { className: "p-1", children: [eur(r.floridayPrice), _jsx("div", { className: "text-muted-foreground", children: r.priceSource ?? "" }), r.priceDiffers && _jsxs("div", { className: "text-amber-700", children: ["Aanbodregel ", eur(r.supplyLinePrice), " \u00B7 catalogus ", eur(r.catalogPrice)] })] }), _jsxs("td", { className: "p-1", children: [num(r.erpStock), " \u00B7 ", eur(r.erpPrice)] }), _jsxs("td", { className: "p-1", children: [formsText(r.forms), r.customerOffers.length > 0 && (_jsxs("details", { children: [_jsxs("summary", { className: "cursor-pointer", children: ["Klantaanbiedingen (", r.customerOffers.length, ", alleen getoond)"] }), _jsx("ul", { className: "list-disc pl-4", children: r.customerOffers.map((o) => _jsx("li", { children: offersText([o]) }, o.id)) })] }))] })] }, r.tradeItemId))) })] }) })), runs.length > 0 && (_jsxs("div", { className: "text-sm space-y-1", children: [_jsx("div", { className: "font-medium", children: "Laatste overnames" }), runs.map((r) => (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("span", { children: [new Date(r.created_at).toLocaleString("nl-NL"), " \u2014 ", r.total_items, " artikelen", r.created_products ? `, ${r.created_products} nieuw` : "", " \u2014 ", r.status] }), r.status !== "ongedaan" && _jsx(Button, { size: "sm", variant: "outline", onClick: () => undo(r.id), children: "Terugdraaien" })] }, r.id)))] }))] }), _jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-sm font-medium", children: "Eigen voorraad leidend maken" }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Kies artikelen die nu Floriday-leidend zijn. Je ziet eerst wat er naar Floriday zou gaan; pas na bevestigen verandert er iets." }), leadRows.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn geen Floriday-leidende artikelen." })) : (_jsxs("div", { className: "max-h-48 overflow-auto rounded border p-2 text-sm space-y-1", children: [_jsxs("label", { className: "flex items-center gap-2 font-medium", children: [_jsx("input", { type: "checkbox", checked: leadSel.size === leadRows.length, onChange: (e) => setLeadSel(e.target.checked ? new Set(leadRows.map((r) => r.product_id)) : new Set()) }), "Alles kiezen"] }), leadRows.map((r) => (_jsxs("label", { className: "flex items-center gap-2", children: [_jsx("input", { type: "checkbox", checked: leadSel.has(r.product_id), onChange: (e) => setLeadSel((s) => { const n = new Set(s); e.target.checked ? n.add(r.product_id) : n.delete(r.product_id); return n; }) }), r.name] }, r.product_id)))] })), _jsxs(Button, { size: "sm", variant: "outline", disabled: leadSel.size === 0, onClick: showErpPreview, children: ["Voorbeeld tonen (", leadSel.size, ")"] }), erpPreview && (_jsxs("div", { className: "space-y-2", children: [_jsxs("p", { className: "text-xs text-muted-foreground", children: ["Alleen de huidige week en de komende ", erpPreview[0]?.horizon_weeks ?? "?", " weken worden aangepast; weken in het verleden nooit."] }), _jsxs("table", { className: "w-full text-xs rounded border", children: [_jsx("thead", { className: "bg-muted", children: _jsxs("tr", { className: "text-left", children: [_jsx("th", { className: "p-1", children: "Artikel" }), _jsx("th", { className: "p-1", children: "Week" }), _jsx("th", { className: "p-1", children: "Vorm" }), _jsx("th", { className: "p-1", children: "Aantal" }), _jsx("th", { className: "p-1", children: "Prijs per stuk" }), _jsx("th", { className: "p-1", children: "Nu in Floriday" }), _jsx("th", { className: "p-1", children: "Let op" })] }) }), _jsx("tbody", { children: erpPreview.flatMap((r) => {
|
|
193
|
+
const ws = r.weeks?.length ? r.weeks : [null];
|
|
194
|
+
return ws.map((w, i) => (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "p-1", children: i === 0 ? r.product_name : "" }), _jsx("td", { className: "p-1", children: w ? (w.week != null ? `${w.year} wk ${w.week}` : "Doorlopend") : "—" }), _jsx("td", { className: "p-1", children: w ? (w.form === "continuous" ? "Doorlopende voorraad" : "Weekaanbod") : "—" }), _jsx("td", { className: "p-1", children: w ? _jsxs(_Fragment, { children: [num(w.quantity), w.available === false ? " (niet beschikbaar)" : ""] }) : "—" }), _jsx("td", { className: "p-1", children: w ? eur(w.price) : eur(r.sale_price) }), _jsx("td", { className: "p-1", children: w && w.floriday_pieces != null ? num(w.floriday_pieces) : "onbekend" }), _jsx("td", { className: "p-1 text-amber-700", children: i === 0 ? r.blocker ?? "" : "" })] }, `${r.product_id}-${i}`)));
|
|
195
|
+
}) })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(Button, { size: "sm", onClick: confirmErp, children: "Bevestigen" }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => setErpPreview(null), children: "Annuleren" })] })] }))] })] }))] }));
|
|
196
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA0GA,wBAAgB,YAAY,gCAqX3B"}
|
|
@@ -10,6 +10,7 @@ import { StockSyncStatus } from "./StockSyncStatus.js";
|
|
|
10
10
|
import { StockIntegrityAlert } from "./StockIntegrityAlert.js";
|
|
11
11
|
import { SupplyReadScopeAlert } from "./SupplyReadScopeAlert.js";
|
|
12
12
|
import { StockPushMonitor } from "./StockPushMonitor.js";
|
|
13
|
+
import { FloridayLeadingSummary, FloridayTakeoverPanel } from "./FloridayTakeoverPanel.js";
|
|
13
14
|
import { ExcelExportButton } from "./ExcelExportButton.js";
|
|
14
15
|
import { StockBulkButtons } from "./StockBulkButtons.js";
|
|
15
16
|
import { VoorraadTable } from "./VoorraadTable.js";
|
|
@@ -165,7 +166,7 @@ export function VoorraadPage() {
|
|
|
165
166
|
noPriceBreakdown.unit > 0 ? `${noPriceBreakdown.unit} met een andere eenheid dan stuks` : null,
|
|
166
167
|
]
|
|
167
168
|
.filter(Boolean)
|
|
168
|
-
.join(", "), "."] }), " ", "Kies \"Toon deze artikelen\", open een artikel en vul bij Artikel regulier de verkoopprijs per stuk (excl. btw) in. De voorraadsturing van elk artikel heeft ook een link \"Prijs invullen\"."] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => setNoPriceOnly((v) => !v), children: noPriceOnly ? "Alle artikelen tonen" : "Toon deze artikelen" })] })), auth.isAdmin && _jsx(StockPushMonitor, { connectionId: activeId ?? null }), connError && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Floriday-koppeling laden mislukt. ", _jsxs("details", { className: "inline", children: [_jsx("summary", { className: "cursor-pointer inline", children: "Details voor beheer" }), connError] })] })), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Voorraad laden mislukt: ", error] })), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [_jsx(KpiCard, { icon: _jsx(FileText, { className: "h-4 w-4" }), tone: "primary", label: "Artikelen", count: counts.total, active: stockFilter === "all", onClick: () => toggle("all") }), _jsx(KpiCard, { icon: _jsx(CheckCircle2, { className: "h-4 w-4" }), tone: "emerald", label: "Op voorraad", count: counts.inStock, active: stockFilter === "in_stock", onClick: () => toggle("in_stock") }), _jsx(KpiCard, { icon: _jsx(PackageX, { className: "h-4 w-4" }), tone: "rose", label: "Niet op voorraad", count: counts.outOfStock, active: stockFilter === "out_of_stock", onClick: () => toggle("out_of_stock") }), _jsx(KpiCard, { icon: _jsx(AlertTriangle, { className: "h-4 w-4" }), tone: "amber", label: "Onder minimum", count: counts.belowMin, active: stockFilter === "below_min", onClick: () => toggle("below_min") })] }), _jsxs(Card, { className: "overflow-hidden p-0", children: [_jsx("div", { className: "flex flex-wrap items-center gap-1 border-b px-4", children: TABS.map((t) => {
|
|
169
|
+
.join(", "), "."] }), " ", "Kies \"Toon deze artikelen\", open een artikel en vul bij Artikel regulier de verkoopprijs per stuk (excl. btw) in. De voorraadsturing van elk artikel heeft ook een link \"Prijs invullen\"."] }), _jsx(Button, { variant: "outline", size: "sm", onClick: () => setNoPriceOnly((v) => !v), children: noPriceOnly ? "Alle artikelen tonen" : "Toon deze artikelen" })] })), _jsx(FloridayLeadingSummary, { connectionId: activeId ?? null }), auth.isAdmin && _jsx(FloridayTakeoverPanel, { connectionId: activeId ?? null }), auth.isAdmin && _jsx(StockPushMonitor, { connectionId: activeId ?? null }), connError && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Floriday-koppeling laden mislukt. ", _jsxs("details", { className: "inline", children: [_jsx("summary", { className: "cursor-pointer inline", children: "Details voor beheer" }), connError] })] })), error && (_jsxs("div", { className: "rounded-md border border-destructive/40 bg-destructive/10 text-destructive p-3 text-sm", children: ["Voorraad laden mislukt: ", error] })), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4", children: [_jsx(KpiCard, { icon: _jsx(FileText, { className: "h-4 w-4" }), tone: "primary", label: "Artikelen", count: counts.total, active: stockFilter === "all", onClick: () => toggle("all") }), _jsx(KpiCard, { icon: _jsx(CheckCircle2, { className: "h-4 w-4" }), tone: "emerald", label: "Op voorraad", count: counts.inStock, active: stockFilter === "in_stock", onClick: () => toggle("in_stock") }), _jsx(KpiCard, { icon: _jsx(PackageX, { className: "h-4 w-4" }), tone: "rose", label: "Niet op voorraad", count: counts.outOfStock, active: stockFilter === "out_of_stock", onClick: () => toggle("out_of_stock") }), _jsx(KpiCard, { icon: _jsx(AlertTriangle, { className: "h-4 w-4" }), tone: "amber", label: "Onder minimum", count: counts.belowMin, active: stockFilter === "below_min", onClick: () => toggle("below_min") })] }), _jsxs(Card, { className: "overflow-hidden p-0", children: [_jsx("div", { className: "flex flex-wrap items-center gap-1 border-b px-4", children: TABS.map((t) => {
|
|
169
170
|
const active = stockFilter === t.id;
|
|
170
171
|
return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
|
|
171
172
|
? "text-foreground"
|