@flowselections/floriday-voorraad 1.0.139 → 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.
@@ -1 +1 @@
1
- {"version":3,"file":"StockMutationHistoryCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/StockMutationHistoryCard.tsx"],"names":[],"mappings":"AAWA,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"}
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";
@@ -122,7 +123,7 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
122
123
  }
123
124
  catch (e) {
124
125
  if (!cancelled)
125
- setError(e.message);
126
+ setError(dbFoutTekst(e, "Geschiedenis laden"));
126
127
  }
127
128
  };
128
129
  load();
@@ -135,7 +136,7 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
135
136
  supabase.removeChannel(channel);
136
137
  };
137
138
  }, [tradeItemKey, productId]);
138
- 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 && (_jsxs("div", { className: "text-sm text-destructive", children: ["Kon voorraadmutaties niet laden: ", 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) => {
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) => {
139
140
  const meta = TYPE_LABEL[r.mutation_type] ?? {
140
141
  label: r.mutation_type || "Onbekend",
141
142
  tone: "bg-slate-100 text-slate-700 border-slate-200",
@@ -1 +1 @@
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,sCAiTzF"}
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"}
@@ -4,9 +4,9 @@ import { Button, Card, CardContent, Input, Label, Textarea, toast, Dialog, Dialo
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";
9
- import { AFBOEK_REDENEN, afboekFout, floridayNaOpslaanTekst, valuesAfterSave } from "../../lib/stock-save.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";
10
10
  export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }) {
11
11
  const keys = stockKeysFor({
12
12
  tradeItemId,
@@ -18,33 +18,14 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
18
18
  const [values, setValues] = useState(row);
19
19
  const [saving, setSaving] = useState(false);
20
20
  const [touched, setTouched] = useState(false);
21
- const [hasBom, setHasBom] = useState(false);
22
21
  const [afboekOpen, setAfboekOpen] = useState(false);
23
22
  const [afboekAantal, setAfboekAantal] = useState("");
24
23
  const [afboekReden, setAfboekReden] = useState(null);
25
24
  const [afboekNotitie, setAfboekNotitie] = useState("");
26
25
  const [afboekBezig, setAfboekBezig] = useState(false);
27
- // Heeft dit artikel onderdelen (samenstelling)? Bepaalt alleen het label
28
- // in de Floriday-kaart. Produceren bestaat niet meer in deze module.
29
- useEffect(() => {
30
- let cancelled = false;
31
- (async () => {
32
- if (!productId && !tradeItemId) {
33
- setHasBom(false);
34
- return;
35
- }
36
- let query = supabase.from("product_bom_items").select("id").limit(1);
37
- query = productId
38
- ? query.eq("parent_product_id", productId)
39
- : query.eq("parent_trade_item_floriday_id", tradeItemId);
40
- const { data } = await query;
41
- if (!cancelled)
42
- setHasBom((data ?? []).length > 0);
43
- })();
44
- return () => {
45
- cancelled = true;
46
- };
47
- }, [productId, tradeItemId]);
26
+ const [wijzigingNotitie, setWijzigingNotitie] = useState("");
27
+ const [artikelNotitieOpen, setArtikelNotitieOpen] = useState(false);
28
+ const [artikelNotitieGewijzigd, setArtikelNotitieGewijzigd] = useState(false);
48
29
  // Is dit artikel de kop van een productgroep? Dan geen voorraad invoeren.
49
30
  const [isGroupHead, setIsGroupHead] = useState(false);
50
31
  useEffect(() => {
@@ -76,52 +57,36 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
76
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." }));
77
58
  }
78
59
  if (stockError) {
79
- return (_jsxs(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.", _jsxs("div", { className: "mt-1 text-xs text-muted-foreground", children: ["Details: ", String(stockError)] })] }));
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." }));
80
61
  }
81
62
  const available = Math.max(0, values.physical_stock - values.reserved_stock);
82
- // Foutmeldingen uit de database zijn soms JSON of technisch; hier maken we
83
- // er gewone taal van zodat de gebruiker weet wat er moet gebeuren.
84
- const humanError = (e) => {
85
- const raw = e?.message ?? String(e);
86
- try {
87
- const parsed = JSON.parse(raw);
88
- const msg = parsed?.message ?? parsed?.error ?? raw;
89
- if (typeof msg === "string" && msg.toLowerCase().includes("onvoldoende voorraad")) {
90
- return "Onvoldoende voorraad van een onderdeel. Pas de voorraad van het onderdeel eerst aan.";
91
- }
92
- return typeof msg === "string" ? msg : raw;
93
- }
94
- catch {
95
- return raw;
96
- }
97
- };
98
63
  // Opslaan via set_product_stock werkt ook zonder koppeling (lokale context, 1.0.107).
99
64
  const canWrite = !!productId || !!connectionId;
100
65
  const handleSave = async () => {
101
66
  if (!canWrite)
102
67
  return;
103
68
  setSaving(true);
69
+ let result = null;
70
+ let fout = null;
104
71
  try {
105
- await save(values);
106
- setValues(valuesAfterSave(true, values, row));
107
- setTouched(false);
108
- let poortOpen = null;
109
- if (tradeItemId) {
110
- const { data } = await supabase.rpc("voorraad_floriday_klaarzetten_toegestaan");
111
- poortOpen = data === true;
112
- }
113
- const extra = floridayNaOpslaanTekst(!!tradeItemId, poortOpen);
114
- toast.success(extra ? `Voorraad opgeslagen. ${extra}` : "Voorraad opgeslagen");
72
+ result = await save(values, { wijzigingNotitie, artikelNotitieWijzigen: artikelNotitieGewijzigd });
115
73
  }
116
74
  catch (e) {
117
- // Niets opgeslagen: laatst opgeslagen waarden terug in beeld.
118
- setValues(valuesAfterSave(false, values, row));
119
- setTouched(false);
120
- toast.error(`Opslaan mislukt, niets gewijzigd: ${humanError(e)}`);
75
+ fout = dbFoutTekst(e, "Opslaan");
121
76
  }
122
- finally {
123
- setSaving(false);
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);
124
88
  }
89
+ setSaving(false);
125
90
  };
126
91
  const handleAfboeken = async () => {
127
92
  const aantal = Number(afboekAantal);
@@ -145,7 +110,7 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
145
110
  p_notitie: afboekNotitie.trim() || null,
146
111
  });
147
112
  if (error)
148
- throw new Error(error.message);
113
+ throw error;
149
114
  toast.success(`${aantal} afgeboekt`);
150
115
  setAfboekOpen(false);
151
116
  setAfboekAantal("");
@@ -154,7 +119,7 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
154
119
  await refetch();
155
120
  }
156
121
  catch (e) {
157
- toast.error(`Afboeken mislukt, niets gewijzigd: ${humanError(e)}`);
122
+ toast.error(`Afboeken mislukt, niets gewijzigd. ${dbFoutTekst(e, "Afboeken")}`);
158
123
  }
159
124
  finally {
160
125
  setAfboekBezig(false);
@@ -164,5 +129,8 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
164
129
  setTouched(true);
165
130
  setValues((v) => ({ ...v, [id]: Number(e.target.value) || 0 }));
166
131
  } })] }));
167
- 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-notes", children: "Notitie (optioneel)" }), _jsx(Textarea, { id: "stock-notes", rows: 2, value: values.notes ?? "", disabled: !canWrite, onChange: (e) => setValues((v) => ({ ...v, notes: e.target.value })), placeholder: "Bijv. locatie of opmerking" })] }), _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"] })] })] }) }), _jsx(FloridaySentCard, { connectionId: connectionId, productId: productId, tradeItemId: tradeItemId, productType: isGroupHead ? "GROUP" : hasBom ? "SET" : null })] }));
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"] })] })] }) })] }));
168
136
  }
@@ -40,7 +40,10 @@ 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) => Promise<void>;
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;
46
49
  resolvedConnectionId: string | null;
@@ -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;;;mBA0GR,iBAAiB;;;;;EA6FnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAuBzC"}
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,57 +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
- const { data, error: rpcError } = await supabase.rpc("set_product_stock", {
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
- // Gereserveerd wordt berekend uit de orders; nooit vanuit dit scherm schrijven.
189
- incoming_stock: values.incoming_stock,
190
- notes: values.notes,
191
- change_source: "MANUAL_STOCK",
192
- }, { onConflict: "connection_id,trade_item_key" });
193
- if (upsertError)
194
- throw new Error(upsertError.message);
153
+ if (!productId && (!connectionId || !primaryKey)) {
154
+ throw new Error("Ontbrekende connectie of artikel-key");
195
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);
196
178
  if (!savedConnectionId || !savedKey) {
197
179
  throw new Error("Ontbrekende connectie of artikel-key");
198
180
  }
199
181
  const previousPhysical = row?.physical_stock ?? 0;
200
- setRow({ ...values, reserved_stock: row?.reserved_stock ?? values.reserved_stock });
182
+ setRow({
183
+ ...values,
184
+ notes: result.notes ?? null,
185
+ reserved_stock: Number(result.reserved_stock ?? row?.reserved_stock ?? values.reserved_stock),
186
+ });
201
187
  // Verkoop = daling van de fysieke voorraad. Het bijbehorende aantal
202
188
  // fusten wordt automatisch van de fustenvoorraad afgeschreven op basis
203
189
  // van de primaire belading van dit artikel.
@@ -234,6 +220,7 @@ export function useTradeItemStock(connectionId, keyOrKeys, productId) {
234
220
  // Engine-tabel niet bereikbaar; voorraad is lokaal wel opgeslagen.
235
221
  }
236
222
  })();
223
+ return result;
237
224
  }, [connectionId, primaryKey, productId, resolvedConnectionId, resolvedKey, row]);
238
225
  return { row: row ?? EMPTY, loading, save, error, resolvedKey, resolvedConnectionId, refetch };
239
226
  }
@@ -32233,6 +32233,14 @@ export type Database = {
32233
32233
  p_location_id: string;
32234
32234
  };
32235
32235
  Returns: Json;
32236
+ } | {
32237
+ Args: {
32238
+ p_floriday_location_id: string;
32239
+ p_gln: string;
32240
+ p_location_id: string;
32241
+ p_source: string;
32242
+ };
32243
+ Returns: Json;
32236
32244
  };
32237
32245
  afh_assert_ingelogd: {
32238
32246
  Args: never;
@@ -35149,6 +35157,16 @@ export type Database = {
35149
35157
  };
35150
35158
  Returns: boolean;
35151
35159
  };
35160
+ klanten_adres_sleutel: {
35161
+ Args: {
35162
+ p_country: string;
35163
+ p_house: string;
35164
+ p_line: string;
35165
+ p_postal: string;
35166
+ p_street: string;
35167
+ };
35168
+ Returns: string;
35169
+ };
35152
35170
  klanten_auto_promote_status: {
35153
35171
  Args: never;
35154
35172
  Returns: Json;
@@ -35426,6 +35444,13 @@ export type Database = {
35426
35444
  Args: never;
35427
35445
  Returns: Json;
35428
35446
  };
35447
+ list_available_glns_for_customer: {
35448
+ Args: {
35449
+ p_customer_id: string;
35450
+ p_location_id?: string;
35451
+ };
35452
+ Returns: Json;
35453
+ };
35429
35454
  list_medewerkers: {
35430
35455
  Args: never;
35431
35456
  Returns: {
@@ -43275,6 +43300,17 @@ export type Database = {
43275
43300
  };
43276
43301
  Returns: Json;
43277
43302
  };
43303
+ save_delivery_location_with_glns: {
43304
+ Args: {
43305
+ p_add: Json;
43306
+ p_fields: Json;
43307
+ p_location_id: string;
43308
+ p_make_primary: boolean;
43309
+ p_primary_gln: string;
43310
+ p_remove: string[];
43311
+ };
43312
+ Returns: Json;
43313
+ };
43278
43314
  save_product_group: {
43279
43315
  Args: {
43280
43316
  p_description: string;
@@ -45475,6 +45511,12 @@ export type Database = {
45475
45511
  };
45476
45512
  Returns: Json;
45477
45513
  };
45514
+ voorraad_correctie_1140: {
45515
+ Args: {
45516
+ p_uitvoeren?: boolean;
45517
+ };
45518
+ Returns: Json;
45519
+ };
45478
45520
  voorraad_correctie_status: {
45479
45521
  Args: {
45480
45522
  p_correctie?: string;
@@ -45638,6 +45680,20 @@ export type Database = {
45638
45680
  };
45639
45681
  Returns: undefined;
45640
45682
  };
45683
+ voorraad_voorraad_opslaan: {
45684
+ Args: {
45685
+ p_artikel_notitie?: string;
45686
+ p_artikel_notitie_wijzigen?: boolean;
45687
+ p_connection_id: string;
45688
+ p_incoming_stock: number;
45689
+ p_minimum_stock: number;
45690
+ p_physical_stock: number;
45691
+ p_product_id: string;
45692
+ p_trade_item_key: string;
45693
+ p_wijziging_notitie?: string;
45694
+ };
45695
+ Returns: Json;
45696
+ };
45641
45697
  voorraad_week_is_past: {
45642
45698
  Args: {
45643
45699
  p_week: number;