@flowselections/floriday-voorraad 1.0.137 → 1.0.141
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.d.ts.map +1 -1
- package/dist-lib/components/voorraad/ArtikelWizard.js +9 -7
- package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/StockMutationHistoryCard.js +19 -4
- package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadPage.js +2 -5
- package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
- package/dist-lib/components/voorraad/VoorraadStockCard.js +69 -47
- package/dist-lib/hooks/useTradeItemStock.d.ts +6 -1
- package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
- package/dist-lib/hooks/useTradeItemStock.js +32 -44
- package/dist-lib/integrations/supabase/types.d.ts +416 -0
- package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
- package/dist-lib/lib/__tests__/release-140.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/release-140.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/release-140.test.js +42 -0
- package/dist-lib/lib/__tests__/stock-save.test.d.ts +2 -0
- package/dist-lib/lib/__tests__/stock-save.test.d.ts.map +1 -0
- package/dist-lib/lib/__tests__/stock-save.test.js +35 -0
- package/dist-lib/lib/db-error-message.d.ts +7 -0
- package/dist-lib/lib/db-error-message.d.ts.map +1 -0
- package/dist-lib/lib/db-error-message.js +36 -0
- package/dist-lib/lib/stock-save.d.ts +43 -0
- package/dist-lib/lib/stock-save.d.ts.map +1 -0
- package/dist-lib/lib/stock-save.js +45 -0
- package/package.json +3 -1
- package/sql/AGENTS.md +3 -0
- package/sql/floriday-voorraad-schema.sql +301 -77
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA2DA,OAAO,EAUL,KAAK,UAAU,EAMhB,MAAM,4BAA4B,CAAC;AA4DpC,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAMpF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,UAAU,CA0BrF;AAiED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EACX,SAAS,EACT,WAAW,EACX,OAAO,GACR,GAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,aAAa,CAAC;CACpB,+
|
|
1
|
+
{"version":3,"file":"ArtikelWizard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/ArtikelWizard.tsx"],"names":[],"mappings":"AA2DA,OAAO,EAUL,KAAK,UAAU,EAMhB,MAAM,4BAA4B,CAAC;AA4DpC,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAMpF,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,aAAa,CAAC;CACzB;AAED,4EAA4E;AAC5E,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,EAAE,aAAa,GAAG,UAAU,CA0BrF;AAiED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EAAE,cAAc,EACvB,WAAW,EACX,SAAS,EACT,WAAW,EACX,OAAO,GACR,GAAE;IACD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,aAAa,CAAC;CACpB,+BAy4EL"}
|
|
@@ -1908,13 +1908,15 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
|
|
|
1908
1908
|
}
|
|
1909
1909
|
}, [persistAndPush, data]);
|
|
1910
1910
|
if (connLoading || draftLoading || publishedLoading || productLoading) {
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
? "
|
|
1915
|
-
:
|
|
1916
|
-
? "
|
|
1917
|
-
:
|
|
1911
|
+
// Voorraadblok direct bruikbaar; Floriday-gegevens laden op de achtergrond (1.0.138).
|
|
1912
|
+
const vroegVoorraad = !connLoading && (tradeItemId || productId || draftId);
|
|
1913
|
+
return (_jsxs("main", { className: "p-6 space-y-4", children: [vroegVoorraad && (_jsx(VoorraadStockCard, { connectionId: activeId, tradeItemId: tradeItemId ?? null, draftId: draftId ?? null, productId: productId ?? null })), _jsxs(Card, { className: "p-6 flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), publishedLoading
|
|
1914
|
+
? "Artikel laden..."
|
|
1915
|
+
: draftLoading
|
|
1916
|
+
? "Lokaal artikel laden..."
|
|
1917
|
+
: productLoading
|
|
1918
|
+
? "Product laden..."
|
|
1919
|
+
: "Floriday-koppeling laden..."] })] }));
|
|
1918
1920
|
}
|
|
1919
1921
|
if (!activeConnection) {
|
|
1920
1922
|
return (_jsx("main", { className: "p-6", children: _jsx(Card, { className: "p-6", children: "Geen actieve Floriday-koppeling gevonden. Voeg er eerst een toe in Instellingen \u203A Floriday." }) }));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StockMutationHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockMutationHistoryCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"StockMutationHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockMutationHistoryCard.tsx"],"names":[],"mappings":"AAYA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAiDD,wBAAgB,wBAAwB,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,KAAK,+BA4O1E"}
|
|
@@ -5,6 +5,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
5
5
|
* de groepssleutel wanneer het artikel via een groep wordt afgeboekt).
|
|
6
6
|
*/
|
|
7
7
|
import { useEffect, useState } from "react";
|
|
8
|
+
import { dbFoutTekst } from "../../lib/db-error-message.js";
|
|
8
9
|
import { Card, CardContent, Badge, Button } from "@flowselections/core";
|
|
9
10
|
import { ChevronDown, ChevronUp, Loader2, History } from "lucide-react";
|
|
10
11
|
import { supabase } from "../../integrations/supabase/client.js";
|
|
@@ -16,6 +17,7 @@ const TYPE_LABEL = {
|
|
|
16
17
|
reservation: { label: "Reservering", tone: "bg-blue-100 text-blue-800 border-blue-200" },
|
|
17
18
|
reservation_release: { label: "Reservering vrijgegeven", tone: "bg-blue-100 text-blue-800 border-blue-200" },
|
|
18
19
|
reservation_skipped: { label: "Reservering overgeslagen", tone: "bg-amber-100 text-amber-800 border-amber-200" },
|
|
20
|
+
manual_writeoff: { label: "Afgeboekt (met reden)", tone: "bg-rose-100 text-rose-800 border-rose-200" },
|
|
19
21
|
manual_correction: { label: "Handmatige correctie", tone: "bg-slate-100 text-slate-700 border-slate-200" },
|
|
20
22
|
order_sync: { label: "Verwerking verkooporders", tone: "bg-indigo-100 text-indigo-800 border-indigo-200" },
|
|
21
23
|
import: { label: "Import", tone: "bg-slate-100 text-slate-700 border-slate-200" },
|
|
@@ -69,7 +71,7 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
|
69
71
|
}
|
|
70
72
|
const { data, error } = await supabase
|
|
71
73
|
.from("stock_mutation_log")
|
|
72
|
-
.select("id,created_at,mutation_type,qty_delta,physical_before,physical_after,reserved_before,reserved_after,reason,order_id")
|
|
74
|
+
.select("id,created_at,mutation_type,qty_delta,physical_before,physical_after,reserved_before,reserved_after,reason,order_id,created_by")
|
|
73
75
|
.in("trade_item_key", [...keys])
|
|
74
76
|
.order("created_at", { ascending: false })
|
|
75
77
|
.limit(100);
|
|
@@ -90,6 +92,18 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
|
90
92
|
orderNumbers.set(String(o.id), String(o.order_number ?? ""));
|
|
91
93
|
}
|
|
92
94
|
}
|
|
95
|
+
// Wie: namen los ophalen uit profiles (geen databaserelatie).
|
|
96
|
+
const userIds = Array.from(new Set((data ?? []).map((r) => r.created_by).filter(Boolean)));
|
|
97
|
+
const userNames = new Map();
|
|
98
|
+
if (userIds.length > 0) {
|
|
99
|
+
const { data: profs } = await supabase
|
|
100
|
+
.from("profiles")
|
|
101
|
+
.select("id, display_name, email")
|
|
102
|
+
.in("id", userIds);
|
|
103
|
+
for (const p of (profs ?? [])) {
|
|
104
|
+
userNames.set(String(p.id), String(p.display_name || p.email || ""));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
93
107
|
if (cancelled)
|
|
94
108
|
return;
|
|
95
109
|
setRows((data ?? []).map((r) => ({
|
|
@@ -104,11 +118,12 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
|
104
118
|
reason: r.reason ?? null,
|
|
105
119
|
order_id: r.order_id ?? null,
|
|
106
120
|
order_number: r.order_id ? orderNumbers.get(String(r.order_id)) ?? null : null,
|
|
121
|
+
created_by_name: r.created_by ? userNames.get(String(r.created_by)) || "Onbekende gebruiker" : null,
|
|
107
122
|
})));
|
|
108
123
|
}
|
|
109
124
|
catch (e) {
|
|
110
125
|
if (!cancelled)
|
|
111
|
-
setError(e
|
|
126
|
+
setError(dbFoutTekst(e, "Geschiedenis laden"));
|
|
112
127
|
}
|
|
113
128
|
};
|
|
114
129
|
load();
|
|
@@ -121,7 +136,7 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
|
121
136
|
supabase.removeChannel(channel);
|
|
122
137
|
};
|
|
123
138
|
}, [tradeItemKey, productId]);
|
|
124
|
-
return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(History, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Afboekgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} mutatie${rows.length === 1 ? "" : "s"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (
|
|
139
|
+
return (_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(History, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Afboekgeschiedenis" }), _jsx("span", { className: "text-xs text-muted-foreground tabular-nums", children: rows ? `${rows.length} mutatie${rows.length === 1 ? "" : "s"}` : "" })] }), _jsxs(CardContent, { className: "pt-0", children: [error && (_jsx("div", { className: "text-sm text-destructive", children: error })), !rows && !error && (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Mutaties laden\u2026"] })), rows && rows.length === 0 && !error && (_jsx("div", { className: "text-sm text-muted-foreground", children: "Nog geen voorraadmutaties voor dit artikel." })), rows && rows.length > 0 && (_jsxs("div", { className: "overflow-x-auto rounded-md border", children: [_jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/50 text-xs uppercase text-muted-foreground", children: _jsxs("tr", { children: [_jsx("th", { className: "px-3 py-2 text-left", children: "Wanneer" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Soort" }), _jsx("th", { className: "px-3 py-2 text-right", children: "Aantal" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Van \u2192 naar" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Order" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Door" }), _jsx("th", { className: "px-3 py-2 text-left", children: "Reden" })] }) }), _jsx("tbody", { children: (expanded ? rows : rows.slice(0, 5)).map((r) => {
|
|
125
140
|
const meta = TYPE_LABEL[r.mutation_type] ?? {
|
|
126
141
|
label: r.mutation_type || "Onbekend",
|
|
127
142
|
tone: "bg-slate-100 text-slate-700 border-slate-200",
|
|
@@ -132,6 +147,6 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
|
|
|
132
147
|
const resChanged = r.reserved_before !== null &&
|
|
133
148
|
r.reserved_after !== null &&
|
|
134
149
|
r.reserved_before !== r.reserved_after;
|
|
135
|
-
return (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "whitespace-nowrap px-3 py-2", children: formatMoment(r.created_at) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: "outline", className: `border ${meta.tone}`, children: meta.label }) }), _jsxs("td", { className: `px-3 py-2 text-right tabular-nums font-medium ${r.qty_delta < 0 ? "text-rose-700" : r.qty_delta > 0 ? "text-emerald-700" : ""}`, children: [r.qty_delta > 0 ? "+" : "", r.qty_delta] }), _jsxs("td", { className: "whitespace-nowrap px-3 py-2 tabular-nums text-muted-foreground", children: [physChanged && (_jsxs("div", { children: ["Liggend ", r.physical_before, " \u2192 ", r.physical_after] })), resChanged && (_jsxs("div", { children: ["Gereserveerd ", r.reserved_before, " \u2192 ", r.reserved_after] })), !physChanged && !resChanged && _jsx("span", { children: "\u2014" })] }), _jsx("td", { className: "whitespace-nowrap px-3 py-2", children: r.order_id ? (_jsx("a", { href: `/floriday-verkoop/verkooporders/${r.order_id}`, className: "text-primary hover:underline", children: r.order_number ?? "Order" })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) }), _jsx("td", { className: "px-3 py-2 text-muted-foreground", children: _jsx(CollapsibleText, { text: r.reason }) })] }, r.id));
|
|
150
|
+
return (_jsxs("tr", { className: "border-t align-top", children: [_jsx("td", { className: "whitespace-nowrap px-3 py-2", children: formatMoment(r.created_at) }), _jsx("td", { className: "px-3 py-2", children: _jsx(Badge, { variant: "outline", className: `border ${meta.tone}`, children: meta.label }) }), _jsxs("td", { className: `px-3 py-2 text-right tabular-nums font-medium ${r.qty_delta < 0 ? "text-rose-700" : r.qty_delta > 0 ? "text-emerald-700" : ""}`, children: [r.qty_delta > 0 ? "+" : "", r.qty_delta] }), _jsxs("td", { className: "whitespace-nowrap px-3 py-2 tabular-nums text-muted-foreground", children: [physChanged && (_jsxs("div", { children: ["Liggend ", r.physical_before, " \u2192 ", r.physical_after] })), resChanged && (_jsxs("div", { children: ["Gereserveerd ", r.reserved_before, " \u2192 ", r.reserved_after] })), !physChanged && !resChanged && _jsx("span", { children: "\u2014" })] }), _jsx("td", { className: "whitespace-nowrap px-3 py-2", children: r.order_id ? (_jsx("a", { href: `/floriday-verkoop/verkooporders/${r.order_id}`, className: "text-primary hover:underline", children: r.order_number ?? "Order" })) : (_jsx("span", { className: "text-muted-foreground", children: "\u2014" })) }), _jsx("td", { className: "whitespace-nowrap px-3 py-2 text-muted-foreground", children: r.created_by_name ?? "Systeem" }), _jsx("td", { className: "px-3 py-2 text-muted-foreground", children: _jsx(CollapsibleText, { text: r.reason }) })] }, r.id));
|
|
136
151
|
}) })] }), rows.length > 5 && (_jsx("div", { className: "flex justify-center border-t bg-muted/20 px-3 py-2", children: _jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => setExpanded((current) => !current), className: "gap-1.5", children: expanded ? (_jsxs(_Fragment, { children: [_jsx(ChevronUp, { className: "h-4 w-4" }), " Inklappen"] })) : (_jsxs(_Fragment, { children: [_jsx(ChevronDown, { className: "h-4 w-4" }), " Meer tonen (", rows.length - 5, ")"] })) }) }))] }))] })] }));
|
|
137
152
|
}
|
|
@@ -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":"AA6BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAmF3D;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,mBAAmB,GAAG,aAAa,CAAC,CAAC;;;;;;;EAgBzG;AAED,wBAAgB,YAAY,gCAqW3B"}
|
|
@@ -4,7 +4,7 @@ import { useNoPriceProducts } from "../../hooks/useNoPriceProducts.js";
|
|
|
4
4
|
import { pageRequestGuard } from "../../lib/request-guard.js";
|
|
5
5
|
const EMPTY_TRIPS = [];
|
|
6
6
|
import { Link } from "@tanstack/react-router";
|
|
7
|
-
import { Button, Card, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue,
|
|
7
|
+
import { Button, Card, Input, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
|
|
8
8
|
import { AlertTriangle, CheckCircle2, FileText, PackageX, Plus, Search, } from "lucide-react";
|
|
9
9
|
import { useFloridayConnection } from "../../hooks/useFloridayConnection.js";
|
|
10
10
|
import { FloridayConnectionNotice } from "./FloridayConnectionNotice.js";
|
|
@@ -12,8 +12,6 @@ import { useVoorraadData } from "../../hooks/useVoorraadData.js";
|
|
|
12
12
|
import { StockSyncStatus } from "./StockSyncStatus.js";
|
|
13
13
|
import { StockIntegrityAlert } from "./StockIntegrityAlert.js";
|
|
14
14
|
import { SupplyReadScopeAlert } from "./SupplyReadScopeAlert.js";
|
|
15
|
-
import { StockPushMonitor } from "./StockPushMonitor.js";
|
|
16
|
-
import { FloridayLeadingSummary, FloridayTakeoverPanel } from "./FloridayTakeoverPanel.js";
|
|
17
15
|
import { ExcelExportButton } from "./ExcelExportButton.js";
|
|
18
16
|
import { StockBulkButtons } from "./StockBulkButtons.js";
|
|
19
17
|
import { VoorraadTable } from "./VoorraadTable.js";
|
|
@@ -71,7 +69,6 @@ export function countStock(items) {
|
|
|
71
69
|
return { total: items.length, inStock, outOfStock, belowMin, fullyReserved, freeAvailable };
|
|
72
70
|
}
|
|
73
71
|
export function VoorraadPage() {
|
|
74
|
-
const auth = useAuthState();
|
|
75
72
|
const conn = useFloridayConnection();
|
|
76
73
|
const { activeId, error: connError } = conn;
|
|
77
74
|
const { items, loading, error, reload } = useVoorraadData(activeId);
|
|
@@ -152,7 +149,7 @@ export function VoorraadPage() {
|
|
|
152
149
|
noPriceBreakdown.unit > 0 ? `${noPriceBreakdown.unit} met een andere eenheid dan stuks` : null,
|
|
153
150
|
]
|
|
154
151
|
.filter(Boolean)
|
|
155
|
-
.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(
|
|
152
|
+
.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(FloridayConnectionNotice, { status: conn.status, environment: conn.selectedEnvironment, error: 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-5 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"), hint: "Artikelen waarvan nu iets op de kwekerij staat." }), _jsx(KpiCard, { icon: _jsx(CheckCircle2, { className: "h-4 w-4" }), tone: "primary", label: "Vrij beschikbaar", count: counts.freeAvailable, active: false, hint: "Artikelen waarvan na aftrek van wat voor orders gereserveerd is nog iets over is." }), _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) => {
|
|
156
153
|
const active = stockFilter === t.id;
|
|
157
154
|
return (_jsxs("button", { type: "button", onClick: () => setStockFilter(t.id), className: `relative py-3 px-3 text-sm font-medium whitespace-nowrap transition-colors ${active
|
|
158
155
|
? "text-foreground"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"VoorraadStockCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadStockCard.tsx"],"names":[],"mappings":"AA4BA,UAAU,KAAK;IACb,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,iBAAiB,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,KAAK,sCA+RzF"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import { Button, Card, CardContent, Input, Label, Textarea, toast, } from "@flowselections/core";
|
|
3
|
+
import { Button, Card, CardContent, Input, Label, Textarea, toast, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@flowselections/core";
|
|
4
4
|
import { Loader2, Package, Check } from "lucide-react";
|
|
5
5
|
import { useTradeItemStock, stockKeysFor } from "../../hooks/useTradeItemStock.js";
|
|
6
6
|
import { supabase } from "../../integrations/supabase/client.js";
|
|
7
|
-
import { FloridaySentCard } from "./FloridaySentCard.js";
|
|
8
7
|
import { LOCAL_STOCK_TEXT } from "../../lib/stock-scope.js";
|
|
8
|
+
import { AFBOEK_REDENEN, afboekFout, opslaanMelding, OPSLAAN_MISLUKT_TEKST, valuesAfterSave } from "../../lib/stock-save.js";
|
|
9
|
+
import { dbFoutTekst } from "../../lib/db-error-message.js";
|
|
9
10
|
export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }) {
|
|
10
11
|
const keys = stockKeysFor({
|
|
11
12
|
tradeItemId,
|
|
@@ -13,32 +14,18 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
|
|
|
13
14
|
productId,
|
|
14
15
|
});
|
|
15
16
|
const key = keys[0] ?? null;
|
|
16
|
-
const { row, loading, save, error: stockError } = useTradeItemStock(connectionId, keys, productId);
|
|
17
|
+
const { row, loading, save, error: stockError, resolvedKey, resolvedConnectionId, refetch } = useTradeItemStock(connectionId, keys, productId);
|
|
17
18
|
const [values, setValues] = useState(row);
|
|
18
19
|
const [saving, setSaving] = useState(false);
|
|
19
20
|
const [touched, setTouched] = useState(false);
|
|
20
|
-
const [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
let query = supabase.from("product_bom_items").select("id").limit(1);
|
|
31
|
-
query = productId
|
|
32
|
-
? query.eq("parent_product_id", productId)
|
|
33
|
-
: query.eq("parent_trade_item_floriday_id", tradeItemId);
|
|
34
|
-
const { data } = await query;
|
|
35
|
-
if (!cancelled)
|
|
36
|
-
setHasBom((data ?? []).length > 0);
|
|
37
|
-
})();
|
|
38
|
-
return () => {
|
|
39
|
-
cancelled = true;
|
|
40
|
-
};
|
|
41
|
-
}, [productId, tradeItemId]);
|
|
21
|
+
const [afboekOpen, setAfboekOpen] = useState(false);
|
|
22
|
+
const [afboekAantal, setAfboekAantal] = useState("");
|
|
23
|
+
const [afboekReden, setAfboekReden] = useState(null);
|
|
24
|
+
const [afboekNotitie, setAfboekNotitie] = useState("");
|
|
25
|
+
const [afboekBezig, setAfboekBezig] = useState(false);
|
|
26
|
+
const [wijzigingNotitie, setWijzigingNotitie] = useState("");
|
|
27
|
+
const [artikelNotitieOpen, setArtikelNotitieOpen] = useState(false);
|
|
28
|
+
const [artikelNotitieGewijzigd, setArtikelNotitieGewijzigd] = useState(false);
|
|
42
29
|
// Is dit artikel de kop van een productgroep? Dan geen voorraad invoeren.
|
|
43
30
|
const [isGroupHead, setIsGroupHead] = useState(false);
|
|
44
31
|
useEffect(() => {
|
|
@@ -70,45 +57,80 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
|
|
|
70
57
|
return (_jsx(Card, { className: "p-4 text-sm text-muted-foreground", children: "Voorraad kan pas worden ingesteld nadat het artikel voor het eerst is opgeslagen." }));
|
|
71
58
|
}
|
|
72
59
|
if (stockError) {
|
|
73
|
-
return (
|
|
60
|
+
return (_jsx(Card, { className: "p-4 text-sm text-destructive", children: "Voorraad laden lukt niet. Controleer of je bent ingelogd en of dit artikel aan een voorraadverbinding is gekoppeld." }));
|
|
74
61
|
}
|
|
75
62
|
const available = Math.max(0, values.physical_stock - values.reserved_stock);
|
|
76
|
-
// Foutmeldingen uit de database zijn soms JSON of technisch; hier maken we
|
|
77
|
-
// er gewone taal van zodat de gebruiker weet wat er moet gebeuren.
|
|
78
|
-
const humanError = (e) => {
|
|
79
|
-
const raw = e?.message ?? String(e);
|
|
80
|
-
try {
|
|
81
|
-
const parsed = JSON.parse(raw);
|
|
82
|
-
const msg = parsed?.message ?? parsed?.error ?? raw;
|
|
83
|
-
if (typeof msg === "string" && msg.toLowerCase().includes("onvoldoende voorraad")) {
|
|
84
|
-
return "Onvoldoende voorraad van een onderdeel. Pas de voorraad van het onderdeel eerst aan.";
|
|
85
|
-
}
|
|
86
|
-
return typeof msg === "string" ? msg : raw;
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return raw;
|
|
90
|
-
}
|
|
91
|
-
};
|
|
92
63
|
// Opslaan via set_product_stock werkt ook zonder koppeling (lokale context, 1.0.107).
|
|
93
64
|
const canWrite = !!productId || !!connectionId;
|
|
94
65
|
const handleSave = async () => {
|
|
95
66
|
if (!canWrite)
|
|
96
67
|
return;
|
|
97
68
|
setSaving(true);
|
|
69
|
+
let result = null;
|
|
70
|
+
let fout = null;
|
|
71
|
+
try {
|
|
72
|
+
result = await save(values, { wijzigingNotitie, artikelNotitieWijzigen: artikelNotitieGewijzigd });
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
fout = dbFoutTekst(e, "Opslaan");
|
|
76
|
+
}
|
|
77
|
+
const melding = opslaanMelding(result, !!tradeItemId);
|
|
78
|
+
setValues(valuesAfterSave(melding.ok, values, row));
|
|
79
|
+
setTouched(false);
|
|
80
|
+
if (melding.ok) {
|
|
81
|
+
setWijzigingNotitie("");
|
|
82
|
+
setArtikelNotitieGewijzigd(false);
|
|
83
|
+
toast.success(melding.tekst);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
// Niets opgeslagen (ook bij teruggedraaide opslag): oude waarden terug.
|
|
87
|
+
toast.error(fout ? `${OPSLAAN_MISLUKT_TEKST}. ${fout}` : OPSLAAN_MISLUKT_TEKST);
|
|
88
|
+
}
|
|
89
|
+
setSaving(false);
|
|
90
|
+
};
|
|
91
|
+
const handleAfboeken = async () => {
|
|
92
|
+
const aantal = Number(afboekAantal);
|
|
93
|
+
const fout = afboekFout(aantal, afboekReden, Number(row.physical_stock));
|
|
94
|
+
if (fout) {
|
|
95
|
+
toast.error(fout);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const key2 = resolvedKey ?? key;
|
|
99
|
+
if (!key2) {
|
|
100
|
+
toast.error("Sla eerst de voorraad op voordat je afboekt.");
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
setAfboekBezig(true);
|
|
98
104
|
try {
|
|
99
|
-
await
|
|
100
|
-
|
|
105
|
+
const { error } = await supabase.rpc("voorraad_afboeken", {
|
|
106
|
+
p_connection_id: resolvedConnectionId ?? connectionId,
|
|
107
|
+
p_trade_item_key: key2,
|
|
108
|
+
p_aantal: aantal,
|
|
109
|
+
p_reden: afboekReden,
|
|
110
|
+
p_notitie: afboekNotitie.trim() || null,
|
|
111
|
+
});
|
|
112
|
+
if (error)
|
|
113
|
+
throw error;
|
|
114
|
+
toast.success(`${aantal} afgeboekt`);
|
|
115
|
+
setAfboekOpen(false);
|
|
116
|
+
setAfboekAantal("");
|
|
117
|
+
setAfboekReden(null);
|
|
118
|
+
setAfboekNotitie("");
|
|
119
|
+
await refetch();
|
|
101
120
|
}
|
|
102
121
|
catch (e) {
|
|
103
|
-
toast.error(`
|
|
122
|
+
toast.error(`Afboeken mislukt, niets gewijzigd. ${dbFoutTekst(e, "Afboeken")}`);
|
|
104
123
|
}
|
|
105
124
|
finally {
|
|
106
|
-
|
|
125
|
+
setAfboekBezig(false);
|
|
107
126
|
}
|
|
108
127
|
};
|
|
109
128
|
const numField = (id, label) => (_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: id, children: label }), _jsx(Input, { id: id, type: "number", min: 0, disabled: !canWrite, value: String(values[id] ?? 0), onChange: (e) => {
|
|
110
129
|
setTouched(true);
|
|
111
130
|
setValues((v) => ({ ...v, [id]: Number(e.target.value) || 0 }));
|
|
112
131
|
} })] }));
|
|
113
|
-
return (_jsxs("div", { className: "space-y-4", children: [_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(Package, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Voorraad" }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Beschikbaar: ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: available })] })] }), _jsx(CardContent, { className: "pt-0 space-y-4", children: loading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Voorraad laden\u2026"] })) : (_jsxs(_Fragment, { children: [Number(values.reserved_stock) > Number(values.physical_stock) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ["Let op: er is meer gereserveerd (", Number(values.reserved_stock), ") dan er ligt (", Number(values.physical_stock), "). Controleer de open orders van dit artikel."] })), _jsxs("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-4", children: [numField("physical_stock", "Liggend"), numField("minimum_stock", "Minimum"),
|
|
132
|
+
return (_jsxs("div", { className: "space-y-4", children: [_jsxs(Card, { children: [_jsxs("div", { className: "grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-3 px-6 py-4", children: [_jsx("span", { className: "flex h-7 w-7 items-center justify-center rounded-full border border-primary/40 bg-primary/10 text-primary", children: _jsx(Package, { className: "h-4 w-4" }) }), _jsx("span", { className: "font-medium", children: "Voorraad" }), _jsxs("span", { className: "text-xs text-muted-foreground", children: ["Beschikbaar: ", _jsx("span", { className: "tabular-nums font-medium text-foreground", children: available })] })] }), _jsx(CardContent, { className: "pt-0 space-y-4", children: loading ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Voorraad laden\u2026"] })) : (_jsxs(_Fragment, { children: [Number(values.reserved_stock) > Number(values.physical_stock) && (_jsxs("div", { className: "rounded-md border border-amber-300 bg-amber-50 px-3 py-2 text-xs text-amber-800", children: ["Let op: er is meer gereserveerd (", Number(values.reserved_stock), ") dan er ligt (", Number(values.physical_stock), "). Controleer de open orders van dit artikel."] })), _jsxs("div", { className: "grid grid-cols-2 gap-4 sm:grid-cols-4", children: [numField("physical_stock", "Liggend"), numField("minimum_stock", "Minimum"), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "reserved_stock", children: "Gereserveerd" }), _jsx(Input, { id: "reserved_stock", type: "number", readOnly: true, disabled: true, value: String(values.reserved_stock ?? 0) }), _jsx("p", { className: "text-[11px] text-muted-foreground", children: "Berekend uit open orders" })] }), numField("incoming_stock", "Inkomend")] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "stock-change-note", children: "Notitie bij deze wijziging (optioneel)" }), _jsx(Textarea, { id: "stock-change-note", rows: 2, value: wijzigingNotitie, disabled: !canWrite, onChange: (e) => setWijzigingNotitie(e.target.value), placeholder: "Komt alleen in de geschiedenis bij deze wijziging" })] }), _jsxs("div", { className: "space-y-1", children: [_jsx("button", { type: "button", className: "text-xs text-muted-foreground underline-offset-2 hover:underline", onClick: () => setArtikelNotitieOpen((o) => !o), children: artikelNotitieOpen ? "Vaste artikelnotitie verbergen" : `Vaste artikelnotitie${values.notes ? " (ingevuld)" : ""}` }), artikelNotitieOpen && (_jsx(Textarea, { id: "stock-notes", rows: 2, value: values.notes ?? "", disabled: !canWrite, onChange: (e) => {
|
|
133
|
+
setArtikelNotitieGewijzigd(true);
|
|
134
|
+
setValues((v) => ({ ...v, notes: e.target.value }));
|
|
135
|
+
}, placeholder: "Bijv. vaste locatie; verandert alleen als je hem hier aanpast" }))] }), _jsxs("div", { className: "flex justify-end", children: [!connectionId && (_jsx("span", { className: "mr-auto self-center text-xs text-muted-foreground", children: LOCAL_STOCK_TEXT })), _jsx(Button, { variant: "outline", className: "mr-2", onClick: () => setAfboekOpen(true), disabled: saving || !canWrite || Number(row.physical_stock) <= 0, children: "Afboeken" }), _jsxs(Button, { onClick: handleSave, disabled: saving || !canWrite, children: [saving ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : (_jsx(Check, { className: "mr-2 h-4 w-4" })), "Voorraad opslaan"] })] })] })) })] }), _jsx(Dialog, { open: afboekOpen, onOpenChange: setAfboekOpen, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Afboeken" }), _jsxs(DialogDescription, { children: ["Haal planten van de liggende voorraad (", Number(row.physical_stock), "). Kies altijd een reden."] })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "afboek-aantal", children: "Aantal" }), _jsx(Input, { id: "afboek-aantal", type: "number", min: 1, value: afboekAantal, onChange: (e) => setAfboekAantal(e.target.value) })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { children: "Reden" }), _jsxs(Select, { value: afboekReden ?? undefined, onValueChange: setAfboekReden, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, { placeholder: "Kies een reden" }) }), _jsx(SelectContent, { children: AFBOEK_REDENEN.map((r) => (_jsx(SelectItem, { value: r.value, children: r.label }, r.value))) })] })] }), _jsxs("div", { className: "space-y-1", children: [_jsx(Label, { htmlFor: "afboek-notitie", children: "Notitie (optioneel)" }), _jsx(Textarea, { id: "afboek-notitie", rows: 2, value: afboekNotitie, onChange: (e) => setAfboekNotitie(e.target.value) })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "ghost", onClick: () => setAfboekOpen(false), children: "Annuleren" }), _jsxs(Button, { onClick: handleAfboeken, disabled: afboekBezig || !afboekReden || !afboekAantal, children: [afboekBezig && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Afboeken"] })] })] }) })] }));
|
|
114
136
|
}
|
|
@@ -40,9 +40,14 @@ export declare function lookupStock(map: Map<string, TradeItemStockRow>, params:
|
|
|
40
40
|
export declare function useTradeItemStock(connectionId: string | null, keyOrKeys: string | string[] | null, productId?: string | null): {
|
|
41
41
|
row: TradeItemStockRow;
|
|
42
42
|
loading: boolean;
|
|
43
|
-
save: (values: TradeItemStockRow
|
|
43
|
+
save: (values: TradeItemStockRow, opts?: {
|
|
44
|
+
wijzigingNotitie?: string | null;
|
|
45
|
+
artikelNotitieWijzigen?: boolean;
|
|
46
|
+
}) => Promise<Record<string, unknown>>;
|
|
44
47
|
error: string | null;
|
|
45
48
|
resolvedKey: string | null;
|
|
49
|
+
resolvedConnectionId: string | null;
|
|
50
|
+
refetch: () => Promise<void>;
|
|
46
51
|
};
|
|
47
52
|
export declare function fetchStockMap(connectionId: string | null): Promise<Map<string, TradeItemStockRow>>;
|
|
48
53
|
//# sourceMappingURL=useTradeItemStock.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAEhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAUX;AAGD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnC,MAAM,EAAE;IACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,iBAAiB,GAAG,SAAS,CAa/B;AAGD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EACnC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;;;
|
|
1
|
+
{"version":3,"file":"useTradeItemStock.d.ts","sourceRoot":"","sources":["../../src/hooks/useTradeItemStock.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAUD,wBAAgB,WAAW,CAAC,MAAM,EAAE;IAClC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,GAAG,IAAI,CAEhB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE;IACnC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,GAAG,MAAM,EAAE,CAUX;AAGD;;;;;GAKG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnC,MAAM,EAAE;IACN,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,GACA,iBAAiB,GAAG,SAAS,CAa/B;AAGD,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,EACnC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI;;;mBA2Gb,iBAAiB,SACnB;QAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,sBAAsB,CAAC,EAAE,OAAO,CAAA;KAAE,KAC3E,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;;;;;EA+EtC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAuBzC"}
|
|
@@ -147,56 +147,43 @@ export function useTradeItemStock(connectionId, keyOrKeys, productId) {
|
|
|
147
147
|
supabase.removeChannel(channel);
|
|
148
148
|
};
|
|
149
149
|
}, [connectionId, keys, primaryKey, refetch, resolvedConnectionId, resolvedKey]);
|
|
150
|
-
const save = useCallback(async (values) => {
|
|
150
|
+
const save = useCallback(async (values, opts = {}) => {
|
|
151
151
|
let savedKey = resolvedKey ?? primaryKey;
|
|
152
152
|
let savedConnectionId = resolvedConnectionId ?? connectionId;
|
|
153
|
-
if (productId) {
|
|
154
|
-
|
|
155
|
-
p_product_id: productId,
|
|
156
|
-
p_physical_stock: values.physical_stock,
|
|
157
|
-
p_minimum_stock: values.minimum_stock,
|
|
158
|
-
p_incoming_stock: values.incoming_stock,
|
|
159
|
-
// Notitie altijd expliciet meegeven (huidige of nieuwe waarde); minimum
|
|
160
|
-
// en inkomend komen uit de geladen rij, dus nooit stil 0.
|
|
161
|
-
p_notes: values.notes ?? null,
|
|
162
|
-
p_change_source: "MANUAL_STOCK",
|
|
163
|
-
});
|
|
164
|
-
if (rpcError)
|
|
165
|
-
throw new Error(rpcError.message);
|
|
166
|
-
const saved = Array.isArray(data) ? data[0] : null;
|
|
167
|
-
savedKey = saved?.trade_item_key ?? savedKey;
|
|
168
|
-
savedConnectionId = saved?.connection_id ?? savedConnectionId;
|
|
169
|
-
setResolvedKey(savedKey);
|
|
170
|
-
setResolvedConnectionId(savedConnectionId);
|
|
171
|
-
}
|
|
172
|
-
else {
|
|
173
|
-
if (!connectionId || !primaryKey) {
|
|
174
|
-
throw new Error("Ontbrekende connectie of artikel-key");
|
|
175
|
-
}
|
|
176
|
-
const userRes = await supabase.auth.getUser();
|
|
177
|
-
const userId = userRes.data.user?.id;
|
|
178
|
-
if (!userId)
|
|
179
|
-
throw new Error("Niet ingelogd");
|
|
180
|
-
const { error: upsertError } = await supabase
|
|
181
|
-
.from("trade_item_stock")
|
|
182
|
-
.upsert({
|
|
183
|
-
user_id: userId,
|
|
184
|
-
connection_id: connectionId,
|
|
185
|
-
trade_item_key: primaryKey,
|
|
186
|
-
physical_stock: values.physical_stock,
|
|
187
|
-
minimum_stock: values.minimum_stock,
|
|
188
|
-
reserved_stock: values.reserved_stock,
|
|
189
|
-
incoming_stock: values.incoming_stock,
|
|
190
|
-
notes: values.notes,
|
|
191
|
-
}, { onConflict: "connection_id,trade_item_key" });
|
|
192
|
-
if (upsertError)
|
|
193
|
-
throw new Error(upsertError.message);
|
|
153
|
+
if (!productId && (!connectionId || !primaryKey)) {
|
|
154
|
+
throw new Error("Ontbrekende connectie of artikel-key");
|
|
194
155
|
}
|
|
156
|
+
// 1.0.140: één opslagfunctie; het antwoord bevestigt dat de database
|
|
157
|
+
// heeft opgeslagen. Notitie per wijziging gaat alleen naar de geschiedenis.
|
|
158
|
+
const { data, error: rpcError } = await supabase.rpc("voorraad_voorraad_opslaan", {
|
|
159
|
+
p_product_id: productId ?? null,
|
|
160
|
+
p_connection_id: productId ? null : connectionId,
|
|
161
|
+
p_trade_item_key: productId ? null : primaryKey,
|
|
162
|
+
p_physical_stock: values.physical_stock,
|
|
163
|
+
p_minimum_stock: values.minimum_stock,
|
|
164
|
+
p_incoming_stock: values.incoming_stock,
|
|
165
|
+
p_wijziging_notitie: opts.wijzigingNotitie?.trim() || null,
|
|
166
|
+
p_artikel_notitie: values.notes ?? null,
|
|
167
|
+
p_artikel_notitie_wijzigen: !!opts.artikelNotitieWijzigen,
|
|
168
|
+
});
|
|
169
|
+
if (rpcError)
|
|
170
|
+
throw rpcError;
|
|
171
|
+
const result = (data ?? null);
|
|
172
|
+
if (!result || result.opgeslagen !== true)
|
|
173
|
+
throw new Error("Opslaan niet bevestigd");
|
|
174
|
+
savedKey = result.trade_item_key ?? savedKey;
|
|
175
|
+
savedConnectionId = result.connection_id ?? savedConnectionId;
|
|
176
|
+
setResolvedKey(savedKey);
|
|
177
|
+
setResolvedConnectionId(savedConnectionId);
|
|
195
178
|
if (!savedConnectionId || !savedKey) {
|
|
196
179
|
throw new Error("Ontbrekende connectie of artikel-key");
|
|
197
180
|
}
|
|
198
181
|
const previousPhysical = row?.physical_stock ?? 0;
|
|
199
|
-
setRow(
|
|
182
|
+
setRow({
|
|
183
|
+
...values,
|
|
184
|
+
notes: result.notes ?? null,
|
|
185
|
+
reserved_stock: Number(result.reserved_stock ?? row?.reserved_stock ?? values.reserved_stock),
|
|
186
|
+
});
|
|
200
187
|
// Verkoop = daling van de fysieke voorraad. Het bijbehorende aantal
|
|
201
188
|
// fusten wordt automatisch van de fustenvoorraad afgeschreven op basis
|
|
202
189
|
// van de primaire belading van dit artikel.
|
|
@@ -233,8 +220,9 @@ export function useTradeItemStock(connectionId, keyOrKeys, productId) {
|
|
|
233
220
|
// Engine-tabel niet bereikbaar; voorraad is lokaal wel opgeslagen.
|
|
234
221
|
}
|
|
235
222
|
})();
|
|
223
|
+
return result;
|
|
236
224
|
}, [connectionId, primaryKey, productId, resolvedConnectionId, resolvedKey, row]);
|
|
237
|
-
return { row: row ?? EMPTY, loading, save, error, resolvedKey };
|
|
225
|
+
return { row: row ?? EMPTY, loading, save, error, resolvedKey, resolvedConnectionId, refetch };
|
|
238
226
|
}
|
|
239
227
|
export async function fetchStockMap(connectionId) {
|
|
240
228
|
// Met koppeling: alleen de rijen van die koppeling. Zonder koppeling: de
|