@flowselections/floriday-voorraad 1.0.68 → 1.0.70
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/ArtikelWizard.js +5 -4
- package/dist-lib/components/voorraad/InventorySettingsDrawer.d.ts.map +1 -1
- package/dist-lib/components/voorraad/InventorySettingsDrawer.js +2 -1
- package/dist-lib/components/voorraad/NieuwArtikelPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/NieuwArtikelPage.js +38 -24
- package/dist-lib/components/voorraad/ProductSalePriceField.d.ts +9 -0
- package/dist-lib/components/voorraad/ProductSalePriceField.d.ts.map +1 -0
- package/dist-lib/components/voorraad/ProductSalePriceField.js +111 -0
- package/dist-lib/components/voorraad/VoorraadPage.js +1 -1
- package/dist-lib/integrations/supabase/types.d.ts +13 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -2005,7 +2005,7 @@ function StepVbn({ data, update, acceptedVbnCodes }) {
|
|
|
2005
2005
|
function InfoTip({ text }) {
|
|
2006
2006
|
return (_jsx("span", { title: text, className: "inline-flex cursor-help text-muted-foreground", children: _jsx(Info, { className: "h-3.5 w-3.5" }) }));
|
|
2007
2007
|
}
|
|
2008
|
-
function StepArtikel({ data, update, collectionsList, setCollectionsList, articleCodeLocked }) {
|
|
2008
|
+
function StepArtikel({ data, update, collectionsList, setCollectionsList, articleCodeLocked, parentProductId }) {
|
|
2009
2009
|
const [newCollection, setNewCollection] = useState("");
|
|
2010
2010
|
const [showNewCollection, setShowNewCollection] = useState(false);
|
|
2011
2011
|
const addCollection = async (name) => {
|
|
@@ -2063,9 +2063,10 @@ function StepArtikel({ data, update, collectionsList, setCollectionsList, articl
|
|
|
2063
2063
|
return (_jsxs("button", { type: "button", onClick: () => update("articleType", t.value), className: `flex flex-col items-center gap-2 rounded-lg border p-4 text-center transition-colors ${active
|
|
2064
2064
|
? "border-primary bg-primary text-primary-foreground"
|
|
2065
2065
|
: "border-border hover:border-primary/50"}`, children: [_jsx("div", { className: "flex w-full items-start", children: _jsx("span", { className: `flex h-4 w-4 items-center justify-center rounded-full border ${active ? "border-primary-foreground" : "border-muted-foreground/50"}`, children: active && (_jsx("span", { className: "h-2 w-2 rounded-full bg-primary-foreground" })) }) }), _jsx(Icon, { className: "h-6 w-6" }), _jsx("div", { className: "font-medium", children: t.label }), _jsx("div", { className: `text-xs ${active ? "text-primary-foreground/80" : "text-muted-foreground"}`, children: t.description })] }, t.value));
|
|
2066
|
-
}) })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("label", { className: "flex items-start gap-2 text-sm", children: [_jsx(Checkbox, { checked: data.visibleInExplorer, onCheckedChange: (v) => update("visibleInExplorer", v === true), className: "mt-0.5" }), _jsx("span", { children: "Artikel weergeven bij ontdekken in de Explorer voor kopers" })] }), _jsx("p", { className: "pl-6 text-xs text-muted-foreground", children: "Artikel is altijd zichtbaar via de customer API" })] })] }));
|
|
2066
|
+
}) })] }), _jsxs("div", { className: "space-y-1", children: [_jsxs("label", { className: "flex items-start gap-2 text-sm", children: [_jsx(Checkbox, { checked: data.visibleInExplorer, onCheckedChange: (v) => update("visibleInExplorer", v === true), className: "mt-0.5" }), _jsx("span", { children: "Artikel weergeven bij ontdekken in de Explorer voor kopers" })] }), _jsx("p", { className: "pl-6 text-xs text-muted-foreground", children: "Artikel is altijd zichtbaar via de customer API" })] }), parentProductId && (_jsx(ProductSalePriceField, { productId: parentProductId, onSaved: (p) => update("salePricePerPiece", p) }))] }));
|
|
2067
2067
|
}
|
|
2068
2068
|
import { COUNTRIES } from "../../lib/countries";
|
|
2069
|
+
import { ProductSalePriceField } from "./ProductSalePriceField";
|
|
2069
2070
|
function StepPaspoort({ data, update }) {
|
|
2070
2071
|
const [countryOpen, setCountryOpen] = useState(false);
|
|
2071
2072
|
const [countryQuery, setCountryQuery] = useState("");
|
|
@@ -2795,7 +2796,7 @@ function StepBeschikbaarheid({ data, update }) {
|
|
|
2795
2796
|
// ------------------------------------------------------------
|
|
2796
2797
|
// Stap "Aanbieden & voorraad" — Direct Sale via CustomerOffer
|
|
2797
2798
|
// ------------------------------------------------------------
|
|
2798
|
-
function StepAanbieden({ data, update, priceGroups }) {
|
|
2799
|
+
function StepAanbieden({ data, update, priceGroups, parentProductId }) {
|
|
2799
2800
|
const priceStr = Number.isFinite(data.salePricePerPiece)
|
|
2800
2801
|
? String(data.salePricePerPiece)
|
|
2801
2802
|
: "";
|
|
@@ -2820,7 +2821,7 @@ function StepAanbieden({ data, update, priceGroups }) {
|
|
|
2820
2821
|
: "Alle gekoppelde Floriday-klanten" }), _jsxs("p", { className: "text-xs text-muted-foreground", children: ["Klantselectie beheer je in de stap ", _jsx("em", { children: "Klanten" }), "."] })] })] }), _jsxs("div", { className: "space-y-2 rounded-md border p-4", children: [_jsxs("div", { className: "flex items-baseline justify-between", children: [_jsxs("div", { children: [_jsx("div", { className: "text-sm font-medium", children: "Prijzen per Floriday-prijsgroep" }), _jsx("div", { className: "text-xs text-muted-foreground", children: "Laat leeg om voor die groep g\u00E9\u00E9n afwijkende prijs te publiceren. Basisprijs blijft dan gelden." })] }), _jsxs("div", { className: "text-xs text-muted-foreground", children: [priceGroups.length, " groep", priceGroups.length === 1 ? "" : "en"] })] }), priceGroups.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground py-4", children: "Geen actieve prijsgroepen gevonden voor deze Floriday-connectie." })) : (_jsx("div", { className: "grid gap-3 sm:grid-cols-2 pt-2", children: priceGroups.map((g) => {
|
|
2821
2822
|
const val = data.priceGroupPrices?.[g.id];
|
|
2822
2823
|
return (_jsxs("div", { className: "space-y-1", children: [_jsxs(Label, { className: "text-sm", children: [g.name, _jsxs("span", { className: "ml-2 text-xs font-normal text-muted-foreground", children: [g.customerOrganizationIds.length, " klant", g.customerOrganizationIds.length === 1 ? "" : "en"] })] }), _jsx(Input, { type: "number", inputMode: "decimal", min: 0, step: "0.01", placeholder: "\u20AC", value: val ? String(val) : "", onChange: (e) => setGroupPrice(g.id, e.target.value) })] }, g.id));
|
|
2823
|
-
}) }))] })] })), _jsx(VatSection, { data: data, update: update })] }));
|
|
2824
|
+
}) }))] })] })), parentProductId && (_jsx(ProductSalePriceField, { productId: parentProductId, onSaved: (p) => update("salePricePerPiece", p) })), _jsx(VatSection, { data: data, update: update })] }));
|
|
2824
2825
|
}
|
|
2825
2826
|
// ------------------------------------------------------------
|
|
2826
2827
|
// BTW-blok (facturatie) — lokaal, niet naar Floriday
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InventorySettingsDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/InventorySettingsDrawer.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"InventorySettingsDrawer.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/InventorySettingsDrawer.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kCAAkC,CAAC;AAkBzE,wBAAgB,uBAAuB,CAAC,EACtC,IAAI,EACJ,YAAY,EACZ,YAAY,EACZ,UAAU,EACV,OAAO,EACP,OAAO,GACR,EAAE;IACD,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACvC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB,+BAkMA"}
|
|
@@ -8,6 +8,7 @@ import { Button, Dialog, DialogContent, DialogDescription, DialogFooter, DialogH
|
|
|
8
8
|
import { useServerFn } from "@tanstack/react-start";
|
|
9
9
|
import { toast } from "sonner";
|
|
10
10
|
import { saveInventorySettings, republishInventory, } from "../../lib/inventory-settings.functions";
|
|
11
|
+
import { MissingPriceNotice } from "./ProductSalePriceField";
|
|
11
12
|
import { MODE_LABELS, ACTION_REASON_LABELS, publishedLabel, } from "../../lib/stock-modes";
|
|
12
13
|
const MODE_HELP = {
|
|
13
14
|
FOLLOW_ERP: "Publiceert beschikbaar min gereserveerd min buffer, afgerond op hele verpakkingen.",
|
|
@@ -95,5 +96,5 @@ export function InventorySettingsDrawer({ open, onOpenChange, connectionId, prod
|
|
|
95
96
|
const activeMode = (mode || current?.stock_mode || "");
|
|
96
97
|
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Voorraadsturing Floriday" }), _jsx(DialogDescription, { children: bulk
|
|
97
98
|
? `${productIds.length} artikelen. Alleen de velden die je invult worden aangepast.`
|
|
98
|
-
: "Bepaal welk aantal er naar Floriday wordt gepubliceerd." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Modus" }), _jsxs(Select, { value: mode, onValueChange: (v) => setMode(v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: bulk ? "Niet wijzigen" : "Kies een modus" }) }), _jsx(SelectContent, { children: Object.keys(MODE_LABELS).map((m) => (_jsx(SelectItem, { value: m, children: MODE_LABELS[m] }, m))) })] }), activeMode && (_jsx("p", { className: "text-xs text-muted-foreground", children: MODE_HELP[activeMode] }))] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Buffer" }), _jsx(Input, { inputMode: "numeric", value: buffer, onChange: (e) => setBuffer(e.target.value), placeholder: bulk ? "Niet wijzigen" : "0" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Aantal dat je achterhoudt en dus niet aanbiedt." })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Maximum" }), _jsx(Input, { inputMode: "numeric", value: maximum, onChange: (e) => setMaximum(e.target.value), placeholder: bulk ? "Niet wijzigen" : "Geen", disabled: activeMode !== "" && activeMode !== "MAXIMUM" && !bulk })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Vast aantal" }), _jsx(Input, { inputMode: "numeric", value: fixed, onChange: (e) => setFixed(e.target.value), placeholder: bulk ? "Niet wijzigen" : "Geen", disabled: activeMode !== "" && activeMode !== "FIXED_REFILL" && !bulk })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Handmatig aantal" }), _jsx(Input, { inputMode: "numeric", value: manual, onChange: (e) => setManual(e.target.value), placeholder: bulk ? "Niet wijzigen" : "Geen", disabled: activeMode !== "" && activeMode !== "MANUAL" && !bulk })] })] }), !bulk && current && (_jsxs("div", { className: "rounded-md border bg-muted/40 p-3 text-xs text-muted-foreground space-y-1", children: [_jsxs("div", { children: [_jsx("strong", { className: "text-foreground", children: publishedLabel(current.desired_quantity ?? 0, current.packing_size) }), " ", "\u00B7 In Floriday:", " ", _jsx("strong", { className: "text-foreground", children: current.remote_checked_at ? (current.remote_quantity ?? "geen aanbod") : "nog niet gecontroleerd" })] }), current.last_published_at && (_jsxs("div", { children: ["Laatst verstuurd:", " ", new Date(current.last_published_at).toLocaleString("nl-NL")] })), current.sync_status === "ACTION_REQUIRED" && (_jsxs("div", { className: "text-destructive", children: ["Actie nodig: ", ACTION_REASON_LABELS[current.action_reason ?? ""] ?? current.action_reason, ". Er gaat niets naar Floriday; de voorraad zelf is wel opgeslagen."] })), current.last_sync_error && (_jsxs("div", { className: "text-destructive", children: ["Laatste fout: ", current.last_sync_error] }))] }))] }), _jsxs(DialogFooter, { className: "gap-2", children: [_jsx(Button, { variant: "outline", onClick: onRepublish, disabled: busy, children: "Opnieuw versturen" }), _jsx(Button, { onClick: onSave, disabled: busy, children: busy ? "Opslaan..." : "Opslaan" })] })] }) }));
|
|
99
|
+
: "Bepaal welk aantal er naar Floriday wordt gepubliceerd." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Modus" }), _jsxs(Select, { value: mode, onValueChange: (v) => setMode(v), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: bulk ? "Niet wijzigen" : "Kies een modus" }) }), _jsx(SelectContent, { children: Object.keys(MODE_LABELS).map((m) => (_jsx(SelectItem, { value: m, children: MODE_LABELS[m] }, m))) })] }), activeMode && (_jsx("p", { className: "text-xs text-muted-foreground", children: MODE_HELP[activeMode] }))] }), _jsxs("div", { className: "grid grid-cols-2 gap-3", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Buffer" }), _jsx(Input, { inputMode: "numeric", value: buffer, onChange: (e) => setBuffer(e.target.value), placeholder: bulk ? "Niet wijzigen" : "0" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Aantal dat je achterhoudt en dus niet aanbiedt." })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Maximum" }), _jsx(Input, { inputMode: "numeric", value: maximum, onChange: (e) => setMaximum(e.target.value), placeholder: bulk ? "Niet wijzigen" : "Geen", disabled: activeMode !== "" && activeMode !== "MAXIMUM" && !bulk })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Vast aantal" }), _jsx(Input, { inputMode: "numeric", value: fixed, onChange: (e) => setFixed(e.target.value), placeholder: bulk ? "Niet wijzigen" : "Geen", disabled: activeMode !== "" && activeMode !== "FIXED_REFILL" && !bulk })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { children: "Handmatig aantal" }), _jsx(Input, { inputMode: "numeric", value: manual, onChange: (e) => setManual(e.target.value), placeholder: bulk ? "Niet wijzigen" : "Geen", disabled: activeMode !== "" && activeMode !== "MANUAL" && !bulk })] })] }), !bulk && current && (_jsxs("div", { className: "rounded-md border bg-muted/40 p-3 text-xs text-muted-foreground space-y-1", children: [_jsxs("div", { children: [_jsx("strong", { className: "text-foreground", children: publishedLabel(current.desired_quantity ?? 0, current.packing_size) }), " ", "\u00B7 In Floriday:", " ", _jsx("strong", { className: "text-foreground", children: current.remote_checked_at ? (current.remote_quantity ?? "geen aanbod") : "nog niet gecontroleerd" })] }), current.last_published_at && (_jsxs("div", { children: ["Laatst verstuurd:", " ", new Date(current.last_published_at).toLocaleString("nl-NL")] })), !bulk && productIds[0] && _jsx(MissingPriceNotice, { productId: productIds[0] }), current.sync_status === "ACTION_REQUIRED" && (_jsxs("div", { className: "text-destructive", children: ["Actie nodig: ", ACTION_REASON_LABELS[current.action_reason ?? ""] ?? current.action_reason, ". Er gaat niets naar Floriday; de voorraad zelf is wel opgeslagen."] })), current.last_sync_error && (_jsxs("div", { className: "text-destructive", children: ["Laatste fout: ", current.last_sync_error] }))] }))] }), _jsxs(DialogFooter, { className: "gap-2", children: [_jsx(Button, { variant: "outline", onClick: onRepublish, disabled: busy, children: "Opnieuw versturen" }), _jsx(Button, { onClick: onSave, disabled: busy, children: busy ? "Opslaan..." : "Opslaan" })] })] }) }));
|
|
99
100
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AAgBA,wBAAgB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"NieuwArtikelPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/NieuwArtikelPage.tsx"],"names":[],"mappings":"AAgBA,wBAAgB,gBAAgB,gCAiM/B"}
|
|
@@ -5,7 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
5
5
|
* lijst van wat daarvoor nog ontbreekt. De volledige Floriday-invoer blijft
|
|
6
6
|
* beschikbaar via de knop onderaan.
|
|
7
7
|
*/
|
|
8
|
-
import { useState } from "react";
|
|
8
|
+
import { useRef, useState } from "react";
|
|
9
9
|
import { Link, useNavigate } from "@tanstack/react-router";
|
|
10
10
|
import { Button, Card, Input, Label, toast } from "@flowselections/core";
|
|
11
11
|
import { ArrowLeft, Check, Loader2, Send } from "lucide-react";
|
|
@@ -23,46 +23,60 @@ export function NieuwArtikelPage() {
|
|
|
23
23
|
const [price, setPrice] = useState("");
|
|
24
24
|
const [saving, setSaving] = useState(false);
|
|
25
25
|
const [createdId, setCreatedId] = useState(null);
|
|
26
|
+
const [saveError, setSaveError] = useState(null);
|
|
27
|
+
// Voorkomt dubbele artikelen bij snel herhaald klikken of opnieuw proberen.
|
|
28
|
+
const savingRef = useRef(false);
|
|
29
|
+
const insertedIdRef = useRef(null);
|
|
26
30
|
if (full)
|
|
27
31
|
return _jsx(ArtikelWizard, {});
|
|
28
32
|
const canNext = step === 0 ? name.trim().length > 0 : step === 1 ? Number(stock) >= 0 && stock !== "" : true;
|
|
29
33
|
const save = async () => {
|
|
34
|
+
if (savingRef.current)
|
|
35
|
+
return;
|
|
36
|
+
savingRef.current = true;
|
|
30
37
|
setSaving(true);
|
|
38
|
+
setSaveError(null);
|
|
31
39
|
try {
|
|
32
40
|
const priceNum = price.trim() === "" ? null : Number(price.replace(",", "."));
|
|
33
|
-
if (priceNum !== null && !(priceNum
|
|
34
|
-
throw new Error("Vul een geldige prijs in");
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
if (priceNum !== null && !(priceNum > 0))
|
|
42
|
+
throw new Error("Vul een geldige prijs in (groter dan 0)");
|
|
43
|
+
let id = insertedIdRef.current;
|
|
44
|
+
if (!id) {
|
|
45
|
+
const { data, error } = await supabase
|
|
46
|
+
.from("products")
|
|
47
|
+
.insert({
|
|
48
|
+
product: name.trim(),
|
|
49
|
+
article_number: code.trim() || null,
|
|
50
|
+
unit: "stuks",
|
|
51
|
+
sale_price: priceNum,
|
|
52
|
+
batch: "",
|
|
53
|
+
location: "",
|
|
54
|
+
})
|
|
55
|
+
.select("id")
|
|
56
|
+
.single();
|
|
57
|
+
if (error)
|
|
58
|
+
throw new Error(`Artikel niet opgeslagen: ${error.message}`);
|
|
59
|
+
id = data.id;
|
|
60
|
+
insertedIdRef.current = id;
|
|
61
|
+
}
|
|
50
62
|
const { error: stockErr } = await supabase.rpc("set_product_stock", {
|
|
51
63
|
p_product_id: id,
|
|
52
64
|
p_physical_stock: Math.trunc(Number(stock) || 0),
|
|
53
|
-
p_minimum_stock: null,
|
|
54
|
-
p_incoming_stock: null,
|
|
55
65
|
p_notes: "Nieuw artikel",
|
|
56
66
|
});
|
|
57
|
-
if (stockErr)
|
|
58
|
-
throw new Error(`Artikel aangemaakt, voorraad niet opgeslagen: ${stockErr.message}
|
|
67
|
+
if (stockErr) {
|
|
68
|
+
throw new Error(`Artikel is aangemaakt, maar de voorraad is niet opgeslagen: ${stockErr.message}. Klik opnieuw op "Artikel opslaan" om alleen de voorraad nog eens te proberen.`);
|
|
69
|
+
}
|
|
59
70
|
toast.success("Artikel aangemaakt");
|
|
60
71
|
setCreatedId(id);
|
|
61
72
|
}
|
|
62
73
|
catch (e) {
|
|
63
|
-
|
|
74
|
+
const msg = e?.message ?? "Opslaan mislukt";
|
|
75
|
+
setSaveError(msg);
|
|
76
|
+
toast.error(msg);
|
|
64
77
|
}
|
|
65
78
|
finally {
|
|
79
|
+
savingRef.current = false;
|
|
66
80
|
setSaving(false);
|
|
67
81
|
}
|
|
68
82
|
};
|
|
@@ -71,5 +85,5 @@ export function NieuwArtikelPage() {
|
|
|
71
85
|
const hasPrice = Number(price.replace(",", ".")) > 0;
|
|
72
86
|
return (_jsx("main", { className: "p-6 space-y-4 max-w-2xl", children: _jsxs(Card, { className: "p-5 space-y-3", children: [_jsxs("div", { className: "flex items-center gap-2 text-emerald-700", children: [_jsx(Check, { className: "h-5 w-5" }), _jsxs("p", { className: "font-medium", children: [name, " is aangemaakt."] })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Wil je het artikel ook op Floriday zetten? Daarvoor is nog nodig:" }), _jsxs("ul", { className: "list-disc pl-5 text-sm", children: [!hasPrice && _jsx("li", { children: "Prijs per stuk (excl. btw)" }), missing.map((m) => (_jsx("li", { children: m }, m)))] }), _jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsxs(Button, { onClick: () => navigate({ to: "/voorraad/$id", params: { id: createdId } }), children: [_jsx(Send, { className: "mr-2 h-4 w-4" }), "Op Floriday zetten"] }), _jsx(Button, { variant: "outline", asChild: true, children: _jsx(Link, { to: "/voorraad", children: "Terug naar voorraad" }) })] })] }) }));
|
|
73
87
|
}
|
|
74
|
-
return (_jsxs("main", { className: "p-6 space-y-4 max-w-2xl", children: [_jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Artikel toevoegen" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Stap ", step + 1, " van ", STEPS.length, ": ", STEPS[step]] })] }), _jsxs(Card, { className: "p-5 space-y-4", children: [step === 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-name", children: "Naam" }), _jsx(Input, { id: "na-name", value: name, onChange: (e) => setName(e.target.value), autoFocus: true })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-code", children: "Artikelcode (optioneel)" }), _jsx(Input, { id: "na-code", value: code, onChange: (e) => setCode(e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Leeg laten: er wordt automatisch een code gemaakt." })] })] })), step === 1 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-stock", children: "Voorraad (stuks)" }), _jsx(Input, { id: "na-stock", type: "number", min: 0, value: stock, onChange: (e) => setStock(e.target.value), autoFocus: true })] })), step === 2 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-price", children: "Prijs per stuk (EUR, excl. btw)" }), _jsx(Input, { id: "na-price", inputMode: "decimal", value: price, onChange: (e) => setPrice(e.target.value), placeholder: "Bijv. 1,10", autoFocus: true }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Zonder prijs komt het artikel niet in Floriday (\"Prijs ontbreekt\")." })] })), _jsxs("div", { className: "flex flex-wrap justify-between gap-2", children: [_jsx(Button, { variant: "outline", disabled: step === 0, onClick: () => setStep((s) => s - 1), children: "Vorige" }), step < STEPS.length - 1 ? (_jsx(Button, { disabled: !canNext, onClick: () => setStep((s) => s + 1), children: "Volgende" })) : (_jsxs(Button, { disabled: saving || !name.trim(), onClick: () => void save(), children: [saving ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Check, { className: "mr-2 h-4 w-4" }), "Artikel opslaan"] }))] })] }), _jsx("button", { type: "button", className: "text-sm text-muted-foreground underline hover:text-foreground", onClick: () => setFull(true), children: "Meteen alle Floriday-gegevens invullen" })] }));
|
|
88
|
+
return (_jsxs("main", { className: "p-6 space-y-4 max-w-2xl", children: [_jsxs(Link, { to: "/voorraad", className: "inline-flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-4 w-4" }), "Terug naar voorraad"] }), _jsxs("div", { children: [_jsx("h1", { className: "text-xl font-semibold", children: "Artikel toevoegen" }), _jsxs("p", { className: "text-sm text-muted-foreground", children: ["Stap ", step + 1, " van ", STEPS.length, ": ", STEPS[step]] })] }), _jsxs(Card, { className: "p-5 space-y-4", children: [step === 0 && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-name", children: "Naam" }), _jsx(Input, { id: "na-name", value: name, onChange: (e) => setName(e.target.value), autoFocus: true })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-code", children: "Artikelcode (optioneel)" }), _jsx(Input, { id: "na-code", value: code, onChange: (e) => setCode(e.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Leeg laten: er wordt automatisch een code gemaakt." })] })] })), step === 1 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-stock", children: "Voorraad (stuks)" }), _jsx(Input, { id: "na-stock", type: "number", min: 0, value: stock, onChange: (e) => setStock(e.target.value), autoFocus: true })] })), step === 2 && (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "na-price", children: "Prijs per stuk (EUR, excl. btw)" }), _jsx(Input, { id: "na-price", inputMode: "decimal", value: price, onChange: (e) => setPrice(e.target.value), placeholder: "Bijv. 1,10", autoFocus: true }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Zonder prijs komt het artikel niet in Floriday (\"Prijs ontbreekt\")." })] })), saveError && (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/40 bg-destructive/10 p-3 text-sm text-destructive", children: saveError })), _jsxs("div", { className: "flex flex-wrap justify-between gap-2", children: [_jsx(Button, { variant: "outline", disabled: step === 0 || saving, onClick: () => setStep((s) => s - 1), children: "Vorige" }), step < STEPS.length - 1 ? (_jsx(Button, { disabled: !canNext, onClick: () => setStep((s) => s + 1), children: "Volgende" })) : (_jsxs(Button, { disabled: saving || !name.trim(), onClick: () => void save(), children: [saving ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : _jsx(Check, { className: "mr-2 h-4 w-4" }), saving ? "Bezig met opslaan…" : "Artikel opslaan"] }))] })] }), _jsx("button", { type: "button", className: "text-sm text-muted-foreground underline hover:text-foreground", onClick: () => setFull(true), children: "Meteen alle Floriday-gegevens invullen" })] }));
|
|
75
89
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare function ProductSalePriceField({ productId, onSaved, }: {
|
|
2
|
+
productId: string;
|
|
3
|
+
onSaved?: (price: number) => void;
|
|
4
|
+
}): import("react").JSX.Element | null;
|
|
5
|
+
/** Melding met directe link naar het prijsveld; toont niets als de prijs in orde is. */
|
|
6
|
+
export declare function MissingPriceNotice({ productId }: {
|
|
7
|
+
productId: string;
|
|
8
|
+
}): import("react").JSX.Element | null;
|
|
9
|
+
//# sourceMappingURL=ProductSalePriceField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProductSalePriceField.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ProductSalePriceField.tsx"],"names":[],"mappings":"AAkBA,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,OAAO,GACR,EAAE;IACD,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,sCAkGA;AAED,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,EAAE,SAAS,EAAE,EAAE;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,sCAmCtE"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Verkoopprijs per stuk (excl. btw) van een bestaand artikel.
|
|
4
|
+
*
|
|
5
|
+
* Leest en schrijft rechtstreeks `products.sale_price`: dat is de prijs die de
|
|
6
|
+
* Floriday-voorraadsync gebruikt. Bij opslaan zet de database de laatste
|
|
7
|
+
* wachtrijregel "Prijs ontbreekt" automatisch opnieuw klaar.
|
|
8
|
+
*/
|
|
9
|
+
import { useEffect, useState } from "react";
|
|
10
|
+
import { Link } from "@tanstack/react-router";
|
|
11
|
+
import { Button, Input, Label, toast } from "@flowselections/core";
|
|
12
|
+
import { AlertTriangle, Loader2 } from "lucide-react";
|
|
13
|
+
import { supabase } from "../../integrations/supabase/client";
|
|
14
|
+
function isPieceUnit(unit) {
|
|
15
|
+
const u = String(unit ?? "").trim().toLowerCase();
|
|
16
|
+
return u === "" || u === "stuks" || u === "stuk";
|
|
17
|
+
}
|
|
18
|
+
export function ProductSalePriceField({ productId, onSaved, }) {
|
|
19
|
+
const [loaded, setLoaded] = useState(false);
|
|
20
|
+
const [unit, setUnit] = useState(null);
|
|
21
|
+
const [saved, setSaved] = useState(null);
|
|
22
|
+
const [value, setValue] = useState("");
|
|
23
|
+
const [saving, setSaving] = useState(false);
|
|
24
|
+
const [error, setError] = useState(null);
|
|
25
|
+
const [exists, setExists] = useState(true);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
let cancelled = false;
|
|
28
|
+
(async () => {
|
|
29
|
+
const { data } = await supabase
|
|
30
|
+
.from("products")
|
|
31
|
+
.select("sale_price, unit")
|
|
32
|
+
.eq("id", productId)
|
|
33
|
+
.maybeSingle();
|
|
34
|
+
if (cancelled)
|
|
35
|
+
return;
|
|
36
|
+
if (!data) {
|
|
37
|
+
setExists(false);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const p = data?.sale_price != null ? Number(data.sale_price) : null;
|
|
41
|
+
setSaved(p);
|
|
42
|
+
setUnit(data?.unit ?? null);
|
|
43
|
+
setValue(p && p > 0 ? String(p).replace(".", ",") : "");
|
|
44
|
+
setLoaded(true);
|
|
45
|
+
})();
|
|
46
|
+
return () => {
|
|
47
|
+
cancelled = true;
|
|
48
|
+
};
|
|
49
|
+
}, [productId]);
|
|
50
|
+
const parsed = Number(value.trim().replace(",", "."));
|
|
51
|
+
const valid = value.trim() !== "" && Number.isFinite(parsed) && parsed > 0 &&
|
|
52
|
+
Math.round(parsed * 1e5) === parsed * 1e5;
|
|
53
|
+
const dirty = valid && parsed !== saved;
|
|
54
|
+
const save = async () => {
|
|
55
|
+
if (!valid || saving)
|
|
56
|
+
return;
|
|
57
|
+
setSaving(true);
|
|
58
|
+
setError(null);
|
|
59
|
+
try {
|
|
60
|
+
const { data, error: err } = await supabase
|
|
61
|
+
.from("products")
|
|
62
|
+
.update({ sale_price: parsed })
|
|
63
|
+
.eq("id", productId)
|
|
64
|
+
.select("id");
|
|
65
|
+
if (err)
|
|
66
|
+
throw new Error(err.message);
|
|
67
|
+
if (!data || data.length === 0)
|
|
68
|
+
throw new Error("geen rechten om dit artikel te wijzigen");
|
|
69
|
+
setSaved(parsed);
|
|
70
|
+
onSaved?.(parsed);
|
|
71
|
+
toast.success("Prijs opgeslagen. Het artikel gaat opnieuw naar Floriday.");
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
const msg = `Prijs niet opgeslagen: ${e?.message ?? e}`;
|
|
75
|
+
setError(msg);
|
|
76
|
+
toast.error(msg);
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
setSaving(false);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
if (!exists)
|
|
83
|
+
return null;
|
|
84
|
+
return (_jsxs("div", { id: "verkoopprijs", className: "space-y-2 rounded-md border p-4", children: [_jsx(Label, { htmlFor: `price-${productId}`, children: "Verkoopprijs per stuk (excl. btw)" }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Input, { id: `price-${productId}`, inputMode: "decimal", className: "w-40", placeholder: "Bijv. 1,40", disabled: !loaded || saving, value: value, onChange: (e) => setValue(e.target.value) }), _jsxs(Button, { size: "sm", disabled: !dirty || saving, onClick: () => void save(), children: [saving && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), saving ? "Bezig met opslaan…" : "Prijs opslaan"] })] }), value.trim() !== "" && !valid && (_jsx("p", { className: "text-xs text-destructive", children: "Vul een prijs groter dan 0 in, met maximaal 5 decimalen." })), loaded && !isPieceUnit(unit) && (_jsxs("p", { className: "text-xs text-amber-700", children: ["Eenheid onduidelijk (", unit, "): de prijs telt voor Floriday alleen bij eenheid \"stuks\"."] })), loaded && !(saved && saved > 0) && (_jsx("p", { className: "text-xs text-muted-foreground", children: "Zonder prijs komt dit artikel niet in Floriday (\"Prijs ontbreekt\")." })), error && _jsx("p", { role: "alert", className: "text-xs text-destructive", children: error })] }));
|
|
85
|
+
}
|
|
86
|
+
/** Melding met directe link naar het prijsveld; toont niets als de prijs in orde is. */
|
|
87
|
+
export function MissingPriceNotice({ productId }) {
|
|
88
|
+
const [state, setState] = useState(null);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
let cancelled = false;
|
|
91
|
+
(async () => {
|
|
92
|
+
const { data } = await supabase
|
|
93
|
+
.from("products")
|
|
94
|
+
.select("sale_price, unit")
|
|
95
|
+
.eq("id", productId)
|
|
96
|
+
.maybeSingle();
|
|
97
|
+
if (cancelled || !data)
|
|
98
|
+
return;
|
|
99
|
+
if (!isPieceUnit(data.unit))
|
|
100
|
+
setState("unit");
|
|
101
|
+
else
|
|
102
|
+
setState(Number(data.sale_price) > 0 ? "ok" : "missing");
|
|
103
|
+
})();
|
|
104
|
+
return () => {
|
|
105
|
+
cancelled = true;
|
|
106
|
+
};
|
|
107
|
+
}, [productId]);
|
|
108
|
+
if (state !== "missing" && state !== "unit")
|
|
109
|
+
return null;
|
|
110
|
+
return (_jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "h-4 w-4 shrink-0" }), _jsx("span", { className: "flex-1", children: state === "missing" ? "Prijs ontbreekt: dit artikel gaat niet naar Floriday." : "Eenheid onduidelijk: de prijs telt alleen per stuk." }), _jsx(Link, { to: "/voorraad/$id", params: { id: productId }, hash: "verkoopprijs", className: "font-medium underline", children: "Prijs invullen" })] }));
|
|
111
|
+
}
|
|
@@ -145,7 +145,7 @@ export function VoorraadPage() {
|
|
|
145
145
|
{ id: "out_of_stock", label: "Niet op voorraad", count: counts.outOfStock },
|
|
146
146
|
{ id: "below_min", label: "Onder minimum", count: counts.belowMin },
|
|
147
147
|
];
|
|
148
|
-
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "min-w-[12rem] flex-1", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/validatie", children: "Validatie" }) }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/publicaties", children: "Publicaties" }) }), _jsx(Button, { asChild: true, disabled: !activeId, size: "sm", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] })] }), _jsx(StockIntegrityAlert, {}), _jsx(SupplyReadScopeAlert, {}), _jsx(StockSyncStatus, {}), noPriceIds.size > 0 && (_jsxs("div", { className: "flex flex-wrap items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { className: "flex-1 min-w-[12rem]", children: [_jsxs("span", { className: "font-medium", children: [noPriceIds.size, " artikel", noPriceIds.size === 1 ? "" : "en", " staan niet in Floriday omdat de prijs ontbreekt."] }), " ", "
|
|
148
|
+
return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs("header", { className: "flex flex-wrap items-start justify-between gap-4", children: [_jsxs("div", { className: "min-w-[12rem] flex-1", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: "Voorraad" }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: "Beheer je artikelen en voorraadniveaus." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/validatie", children: "Validatie" }) }), _jsx(Button, { asChild: true, variant: "outline", size: "sm", children: _jsx(Link, { to: "/voorraad/publicaties", children: "Publicaties" }) }), _jsx(Button, { asChild: true, disabled: !activeId, size: "sm", children: _jsxs(Link, { to: "/voorraad/nieuw", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Artikel toevoegen"] }) })] })] }), _jsx(StockIntegrityAlert, {}), _jsx(SupplyReadScopeAlert, {}), _jsx(StockSyncStatus, {}), noPriceIds.size > 0 && (_jsxs("div", { className: "flex flex-wrap items-start gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800", children: [_jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0" }), _jsxs("div", { className: "flex-1 min-w-[12rem]", children: [_jsxs("span", { className: "font-medium", children: [noPriceIds.size, " artikel", noPriceIds.size === 1 ? "" : "en", " staan niet in Floriday omdat de prijs ontbreekt."] }), " ", "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(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-connectie laden mislukt: ", 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) => {
|
|
149
149
|
const active = stockFilter === t.id;
|
|
150
150
|
return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
|
|
151
151
|
? "text-foreground"
|
|
@@ -29598,6 +29598,12 @@ export type Database = {
|
|
|
29598
29598
|
};
|
|
29599
29599
|
Returns: Json;
|
|
29600
29600
|
};
|
|
29601
|
+
invoice_resync_concept_from_work_order: {
|
|
29602
|
+
Args: {
|
|
29603
|
+
_werkbon_id: string;
|
|
29604
|
+
};
|
|
29605
|
+
Returns: undefined;
|
|
29606
|
+
};
|
|
29601
29607
|
invoice_save_branding_settings: {
|
|
29602
29608
|
Args: {
|
|
29603
29609
|
_payload: Json;
|
|
@@ -32003,6 +32009,13 @@ export type Database = {
|
|
|
32003
32009
|
};
|
|
32004
32010
|
Returns: undefined;
|
|
32005
32011
|
};
|
|
32012
|
+
logistics_slip_note_hold_kenmerk: {
|
|
32013
|
+
Args: {
|
|
32014
|
+
_id: string;
|
|
32015
|
+
_reason: string;
|
|
32016
|
+
};
|
|
32017
|
+
Returns: undefined;
|
|
32018
|
+
};
|
|
32006
32019
|
logistics_slip_note_keep_waiting: {
|
|
32007
32020
|
Args: {
|
|
32008
32021
|
_class?: string;
|