@flowselections/floriday-voorraad 1.0.135 → 1.0.139

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.
Files changed (30) hide show
  1. package/dist-lib/components/voorraad/ArtikelWizard.d.ts.map +1 -1
  2. package/dist-lib/components/voorraad/ArtikelWizard.js +19 -23
  3. package/dist-lib/components/voorraad/FloridayInfoCard.d.ts.map +1 -1
  4. package/dist-lib/components/voorraad/FloridayInfoCard.js +19 -0
  5. package/dist-lib/components/voorraad/StockMutationHistoryCard.d.ts.map +1 -1
  6. package/dist-lib/components/voorraad/StockMutationHistoryCard.js +17 -3
  7. package/dist-lib/components/voorraad/VoorraadPage.d.ts.map +1 -1
  8. package/dist-lib/components/voorraad/VoorraadPage.js +2 -5
  9. package/dist-lib/components/voorraad/VoorraadStockCard.d.ts.map +1 -1
  10. package/dist-lib/components/voorraad/VoorraadStockCard.js +59 -5
  11. package/dist-lib/hooks/useTradeItemStock.d.ts +2 -0
  12. package/dist-lib/hooks/useTradeItemStock.d.ts.map +1 -1
  13. package/dist-lib/hooks/useTradeItemStock.js +4 -3
  14. package/dist-lib/integrations/supabase/types.d.ts +637 -0
  15. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  16. package/dist-lib/lib/__tests__/release-136.test.d.ts +2 -0
  17. package/dist-lib/lib/__tests__/release-136.test.d.ts.map +1 -0
  18. package/dist-lib/lib/__tests__/release-136.test.js +36 -0
  19. package/dist-lib/lib/__tests__/stock-save.test.d.ts +2 -0
  20. package/dist-lib/lib/__tests__/stock-save.test.d.ts.map +1 -0
  21. package/dist-lib/lib/__tests__/stock-save.test.js +35 -0
  22. package/dist-lib/lib/publish-status.d.ts +10 -0
  23. package/dist-lib/lib/publish-status.d.ts.map +1 -1
  24. package/dist-lib/lib/publish-status.js +18 -0
  25. package/dist-lib/lib/stock-save.d.ts +32 -0
  26. package/dist-lib/lib/stock-save.d.ts.map +1 -0
  27. package/dist-lib/lib/stock-save.js +28 -0
  28. package/package.json +2 -2
  29. package/sql/AGENTS.md +1 -0
  30. package/sql/floriday-voorraad-schema.sql +63 -10
@@ -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,+BAi4EL"}
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"}
@@ -21,7 +21,7 @@ import { useFloridayVbnRequired } from "../../hooks/useFloridayVbnRequired.js";
21
21
  import { saveFloridayVbnRequired } from "../../lib/floriday-vbn-required.functions.js";
22
22
  import { useQuery, useQueryClient } from "@tanstack/react-query";
23
23
  import { fetchFloridayWriteMode } from "../../lib/floriday-write-mode.js";
24
- import { editTargetText } from "../../lib/publish-status.js";
24
+ import { editTargetText, formSalePrice, syncAfterPublish } from "../../lib/publish-status.js";
25
25
  import { compareAfterUpdate, verifyDifferenceMessage } from "../../lib/publish-verify.js";
26
26
  import { articleCodeProblem } from "../../lib/article-code.js";
27
27
  import { useCodeList, useTradeItemFormSchema } from "../../hooks/useFloriday.js";
@@ -564,9 +564,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
564
564
  ? barcode
565
565
  : (payload.gtin ?? ""),
566
566
  // M26: de verkoopprijs van het artikel is leidend (zelfde veld als "Prijs opslaan").
567
- salePricePerPiece: Number(product.sale_price) > 0
568
- ? Number(product.sale_price)
569
- : Number(customFields.sale_price ?? customFields.salePrice ?? payload.salePricePerPiece) || 0,
567
+ salePricePerPiece: formSalePrice(product.sale_price, customFields.sale_price ?? customFields.salePrice ?? payload.salePricePerPiece),
570
568
  // Voorkeur: werkelijke voorraad uit trade_item_stock.
571
569
  // Fallback: opgeslagen payload-waarde. `products.quantity` wordt
572
570
  // bewust NIET meer gelezen — die staat structureel op 0.
@@ -824,9 +822,8 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
824
822
  if (r.price_group_id) {
825
823
  groupPrices[r.price_group_id] = Number(r.price_per_piece);
826
824
  }
827
- else if (!(d.salePricePerPiece > 0)) {
828
- next.salePricePerPiece = Number(r.price_per_piece);
829
- }
825
+ // 1.0.136: de basisprijs komt NOOIT uit trade_item_prices; die volgt
826
+ // products.sale_price en wordt nooit teruggeschreven naar het artikel.
830
827
  }
831
828
  next.priceGroupPrices = groupPrices;
832
829
  return next;
@@ -1210,7 +1207,9 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1210
1207
  // Zijn de productregels (schema) nog niet binnen? Dan publiceren we niet,
1211
1208
  // maar bewaren we de wijziging ALTIJD lokaal en markeren we hem als
1212
1209
  // "nog niet doorgegeven aan Floriday".
1213
- if (wantsPublish && !schemaQuery.isSuccess) {
1210
+ // 1.0.136: alleen bij AANMAKEN. Een update van een bestaand Floriday-artikel
1211
+ // heeft de productregels niet nodig en mag daar nooit op vastlopen.
1212
+ if (wantsPublish && !schemaQuery.isSuccess && !isEditingPublished) {
1214
1213
  setSubmitting(true);
1215
1214
  try {
1216
1215
  const localId = await upsertLocalTradeItem({
@@ -1668,18 +1667,14 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1668
1667
  await reloadPublishJob(jobId);
1669
1668
  if (status === "failed" && verifyMismatch) {
1670
1669
  if (draftId) {
1671
- await markLocalSyncStatus(draftId, {
1672
- dirty: true,
1673
- last_error: verifyMismatch,
1674
- }).catch(() => { });
1670
+ await markLocalSyncStatus(draftId, syncAfterPublish(status, verifyMismatch)).catch(() => { });
1675
1671
  }
1676
1672
  toast.error("Wijziging niet aangekomen in Floriday", { description: verifyMismatch });
1677
1673
  }
1678
1674
  else if (status === "succeeded") {
1679
1675
  if (draftId) {
1680
1676
  await markLocalSyncStatus(draftId, {
1681
- dirty: false,
1682
- last_error: null,
1677
+ ...syncAfterPublish(status, null),
1683
1678
  last_synced_at: new Date().toISOString(),
1684
1679
  }).catch(() => { });
1685
1680
  }
@@ -1690,8 +1685,7 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1690
1685
  else if (status === "succeeded_with_warnings") {
1691
1686
  if (draftId) {
1692
1687
  await markLocalSyncStatus(draftId, {
1693
- dirty: false,
1694
- last_error: null,
1688
+ ...syncAfterPublish(status, null),
1695
1689
  last_synced_at: new Date().toISOString(),
1696
1690
  }).catch(() => { });
1697
1691
  }
@@ -1914,13 +1908,15 @@ export function ArtikelWizard({ draftId: initialDraftId, tradeItemId, productId,
1914
1908
  }
1915
1909
  }, [persistAndPush, data]);
1916
1910
  if (connLoading || draftLoading || publishedLoading || productLoading) {
1917
- return (_jsx("main", { className: "p-6", children: _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
1918
- ? "Artikel laden..."
1919
- : draftLoading
1920
- ? "Lokaal artikel laden..."
1921
- : productLoading
1922
- ? "Product laden..."
1923
- : "Floriday-koppeling laden..."] }) }));
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..."] })] }));
1924
1920
  }
1925
1921
  if (!activeConnection) {
1926
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":"FloridayInfoCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayInfoCard.tsx"],"names":[],"mappings":"AAYA,UAAU,KAAK;IACb,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,8EAA8E;IAC9E,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,CAAC;CACH;AA6DD,wBAAgB,gBAAgB,CAAC,EAAE,mBAAmB,EAAE,KAAK,EAAE,EAAE,KAAK,sCAoRrE"}
1
+ {"version":3,"file":"FloridayInfoCard.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/FloridayInfoCard.tsx"],"names":[],"mappings":"AAaA,UAAU,KAAK;IACb,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,8EAA8E;IAC9E,KAAK,CAAC,EAAE;QACN,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACtB,CAAC;CACH;AA6DD,wBAAgB,gBAAgB,CAAC,EAAE,mBAAmB,EAAE,KAAK,EAAE,EAAE,KAAK,sCAoSrE"}
@@ -5,6 +5,7 @@ import { AlertTriangle, ChevronDown, ChevronRight, ExternalLink } from "lucide-r
5
5
  import { supabase } from "../../integrations/supabase/client.js";
6
6
  import { floridayInfoFromCache, } from "../../lib/floriday-info.js";
7
7
  import { useLoadCarriers } from "../../hooks/useLoadCarriers.js";
8
+ import { gatewayReadFn } from "../../lib/floriday-gateway.functions.js";
8
9
  const floridayLogo = "/flowselections-assets/Voorraad/floriday-logo.png";
9
10
  function formatDate(iso) {
10
11
  if (!iso)
@@ -47,6 +48,24 @@ export function FloridayInfoCard({ floridayTradeItemId, local }) {
47
48
  setNotFound(false);
48
49
  (async () => {
49
50
  try {
51
+ // 1.0.136: eerst live uit Floriday (via floriday-get), zodat een net
52
+ // verstuurde wijziging direct zichtbaar is; de kopie is alleen terugval.
53
+ try {
54
+ const live = (await gatewayReadFn({
55
+ data: { path: `/suppliers-api-2026v1/trade-items/${floridayTradeItemId}` },
56
+ }));
57
+ if (cancelled)
58
+ return;
59
+ if (live?.data && typeof live.data === "object") {
60
+ setInfo(floridayInfoFromCache({ ...live.data, id: floridayTradeItemId }));
61
+ setNotFound(false);
62
+ setActivePhoto(0);
63
+ return;
64
+ }
65
+ }
66
+ catch {
67
+ /* terugval op de kopie */
68
+ }
50
69
  const { data, error } = await supabase
51
70
  .from("floriday_trade_items_cache")
52
71
  .select("data,floriday_id")
@@ -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;AA+CD,wBAAgB,wBAAwB,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,EAAE,KAAK,+BAyN1E"}
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"}
@@ -16,6 +16,7 @@ const TYPE_LABEL = {
16
16
  reservation: { label: "Reservering", tone: "bg-blue-100 text-blue-800 border-blue-200" },
17
17
  reservation_release: { label: "Reservering vrijgegeven", tone: "bg-blue-100 text-blue-800 border-blue-200" },
18
18
  reservation_skipped: { label: "Reservering overgeslagen", tone: "bg-amber-100 text-amber-800 border-amber-200" },
19
+ manual_writeoff: { label: "Afgeboekt (met reden)", tone: "bg-rose-100 text-rose-800 border-rose-200" },
19
20
  manual_correction: { label: "Handmatige correctie", tone: "bg-slate-100 text-slate-700 border-slate-200" },
20
21
  order_sync: { label: "Verwerking verkooporders", tone: "bg-indigo-100 text-indigo-800 border-indigo-200" },
21
22
  import: { label: "Import", tone: "bg-slate-100 text-slate-700 border-slate-200" },
@@ -69,7 +70,7 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
69
70
  }
70
71
  const { data, error } = await supabase
71
72
  .from("stock_mutation_log")
72
- .select("id,created_at,mutation_type,qty_delta,physical_before,physical_after,reserved_before,reserved_after,reason,order_id")
73
+ .select("id,created_at,mutation_type,qty_delta,physical_before,physical_after,reserved_before,reserved_after,reason,order_id,created_by")
73
74
  .in("trade_item_key", [...keys])
74
75
  .order("created_at", { ascending: false })
75
76
  .limit(100);
@@ -90,6 +91,18 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
90
91
  orderNumbers.set(String(o.id), String(o.order_number ?? ""));
91
92
  }
92
93
  }
94
+ // Wie: namen los ophalen uit profiles (geen databaserelatie).
95
+ const userIds = Array.from(new Set((data ?? []).map((r) => r.created_by).filter(Boolean)));
96
+ const userNames = new Map();
97
+ if (userIds.length > 0) {
98
+ const { data: profs } = await supabase
99
+ .from("profiles")
100
+ .select("id, display_name, email")
101
+ .in("id", userIds);
102
+ for (const p of (profs ?? [])) {
103
+ userNames.set(String(p.id), String(p.display_name || p.email || ""));
104
+ }
105
+ }
93
106
  if (cancelled)
94
107
  return;
95
108
  setRows((data ?? []).map((r) => ({
@@ -104,6 +117,7 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
104
117
  reason: r.reason ?? null,
105
118
  order_id: r.order_id ?? null,
106
119
  order_number: r.order_id ? orderNumbers.get(String(r.order_id)) ?? null : null,
120
+ created_by_name: r.created_by ? userNames.get(String(r.created_by)) || "Onbekende gebruiker" : null,
107
121
  })));
108
122
  }
109
123
  catch (e) {
@@ -121,7 +135,7 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
121
135
  supabase.removeChannel(channel);
122
136
  };
123
137
  }, [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 && (_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: "Reden" })] }) }), _jsx("tbody", { children: (expanded ? rows : rows.slice(0, 5)).map((r) => {
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) => {
125
139
  const meta = TYPE_LABEL[r.mutation_type] ?? {
126
140
  label: r.mutation_type || "Onbekend",
127
141
  tone: "bg-slate-100 text-slate-700 border-slate-200",
@@ -132,6 +146,6 @@ export function StockMutationHistoryCard({ tradeItemKey, productId }) {
132
146
  const resChanged = r.reserved_before !== null &&
133
147
  r.reserved_after !== null &&
134
148
  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));
149
+ 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
150
  }) })] }), 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
151
  }
@@ -1 +1 @@
1
- {"version":3,"file":"VoorraadPage.d.ts","sourceRoot":"","sources":["../../../src/components/voorraad/VoorraadPage.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AAqF3D;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,GAAG,mBAAmB,GAAG,aAAa,CAAC,CAAC;;;;;;;EAgBzG;AAED,wBAAgB,YAAY,gCA0W3B"}
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, useAuthState, } from "@flowselections/core";
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(FloridayLeadingSummary, { connectionId: activeId ?? null }), auth.isAdmin && _jsx(FloridayTakeoverPanel, { connectionId: activeId ?? null }), auth.isAdmin && _jsx(StockPushMonitor, { connectionId: activeId ?? null }), _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) => {
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":"AAgBA,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,sCA4MzF"}
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,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
7
  import { FloridaySentCard } from "./FloridaySentCard.js";
8
8
  import { LOCAL_STOCK_TEXT } from "../../lib/stock-scope.js";
9
+ import { AFBOEK_REDENEN, afboekFout, floridayNaOpslaanTekst, valuesAfterSave } from "../../lib/stock-save.js";
9
10
  export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productId }) {
10
11
  const keys = stockKeysFor({
11
12
  tradeItemId,
@@ -13,11 +14,16 @@ 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
21
  const [hasBom, setHasBom] = useState(false);
22
+ const [afboekOpen, setAfboekOpen] = useState(false);
23
+ const [afboekAantal, setAfboekAantal] = useState("");
24
+ const [afboekReden, setAfboekReden] = useState(null);
25
+ const [afboekNotitie, setAfboekNotitie] = useState("");
26
+ const [afboekBezig, setAfboekBezig] = useState(false);
21
27
  // Heeft dit artikel onderdelen (samenstelling)? Bepaalt alleen het label
22
28
  // in de Floriday-kaart. Produceren bestaat niet meer in deze module.
23
29
  useEffect(() => {
@@ -97,18 +103,66 @@ export function VoorraadStockCard({ connectionId, tradeItemId, draftId, productI
97
103
  setSaving(true);
98
104
  try {
99
105
  await save(values);
100
- toast.success("Voorraad gecorrigeerd");
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");
101
115
  }
102
116
  catch (e) {
103
- toast.error(`Opslaan mislukt: ${humanError(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)}`);
104
121
  }
105
122
  finally {
106
123
  setSaving(false);
107
124
  }
108
125
  };
126
+ const handleAfboeken = async () => {
127
+ const aantal = Number(afboekAantal);
128
+ const fout = afboekFout(aantal, afboekReden, Number(row.physical_stock));
129
+ if (fout) {
130
+ toast.error(fout);
131
+ return;
132
+ }
133
+ const key2 = resolvedKey ?? key;
134
+ if (!key2) {
135
+ toast.error("Sla eerst de voorraad op voordat je afboekt.");
136
+ return;
137
+ }
138
+ setAfboekBezig(true);
139
+ try {
140
+ const { error } = await supabase.rpc("voorraad_afboeken", {
141
+ p_connection_id: resolvedConnectionId ?? connectionId,
142
+ p_trade_item_key: key2,
143
+ p_aantal: aantal,
144
+ p_reden: afboekReden,
145
+ p_notitie: afboekNotitie.trim() || null,
146
+ });
147
+ if (error)
148
+ throw new Error(error.message);
149
+ toast.success(`${aantal} afgeboekt`);
150
+ setAfboekOpen(false);
151
+ setAfboekAantal("");
152
+ setAfboekReden(null);
153
+ setAfboekNotitie("");
154
+ await refetch();
155
+ }
156
+ catch (e) {
157
+ toast.error(`Afboeken mislukt, niets gewijzigd: ${humanError(e)}`);
158
+ }
159
+ finally {
160
+ setAfboekBezig(false);
161
+ }
162
+ };
109
163
  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
164
  setTouched(true);
111
165
  setValues((v) => ({ ...v, [id]: Number(e.target.value) || 0 }));
112
166
  } })] }));
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"), numField("reserved_stock", "Gereserveerd"), 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 })), _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(FloridaySentCard, { connectionId: connectionId, productId: productId, tradeItemId: tradeItemId, productType: isGroupHead ? "GROUP" : hasBom ? "SET" : null })] }));
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 })] }));
114
168
  }
@@ -43,6 +43,8 @@ export declare function useTradeItemStock(connectionId: string | null, keyOrKeys
43
43
  save: (values: TradeItemStockRow) => Promise<void>;
44
44
  error: string | null;
45
45
  resolvedKey: string | null;
46
+ resolvedConnectionId: string | null;
47
+ refetch: () => Promise<void>;
46
48
  };
47
49
  export declare function fetchStockMap(connectionId: string | null): Promise<Map<string, TradeItemStockRow>>;
48
50
  //# 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;;;mBA0GR,iBAAiB;;;EA4FnC;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;;;mBA0GR,iBAAiB;;;;;EA6FnC;AAED,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAuBzC"}
@@ -185,9 +185,10 @@ export function useTradeItemStock(connectionId, keyOrKeys, productId) {
185
185
  trade_item_key: primaryKey,
186
186
  physical_stock: values.physical_stock,
187
187
  minimum_stock: values.minimum_stock,
188
- reserved_stock: values.reserved_stock,
188
+ // Gereserveerd wordt berekend uit de orders; nooit vanuit dit scherm schrijven.
189
189
  incoming_stock: values.incoming_stock,
190
190
  notes: values.notes,
191
+ change_source: "MANUAL_STOCK",
191
192
  }, { onConflict: "connection_id,trade_item_key" });
192
193
  if (upsertError)
193
194
  throw new Error(upsertError.message);
@@ -196,7 +197,7 @@ export function useTradeItemStock(connectionId, keyOrKeys, productId) {
196
197
  throw new Error("Ontbrekende connectie of artikel-key");
197
198
  }
198
199
  const previousPhysical = row?.physical_stock ?? 0;
199
- setRow(values);
200
+ setRow({ ...values, reserved_stock: row?.reserved_stock ?? values.reserved_stock });
200
201
  // Verkoop = daling van de fysieke voorraad. Het bijbehorende aantal
201
202
  // fusten wordt automatisch van de fustenvoorraad afgeschreven op basis
202
203
  // van de primaire belading van dit artikel.
@@ -234,7 +235,7 @@ export function useTradeItemStock(connectionId, keyOrKeys, productId) {
234
235
  }
235
236
  })();
236
237
  }, [connectionId, primaryKey, productId, resolvedConnectionId, resolvedKey, row]);
237
- return { row: row ?? EMPTY, loading, save, error, resolvedKey };
238
+ return { row: row ?? EMPTY, loading, save, error, resolvedKey, resolvedConnectionId, refetch };
238
239
  }
239
240
  export async function fetchStockMap(connectionId) {
240
241
  // Met koppeling: alleen de rijen van die koppeling. Zonder koppeling: de