@fayz-ai/plugin-inventory 0.1.7 → 0.8.1

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 (38) hide show
  1. package/README.md +2 -0
  2. package/dist/InventoryContext.d.ts +2 -0
  3. package/dist/InventoryContext.d.ts.map +1 -1
  4. package/dist/InventoryPage.d.ts.map +1 -1
  5. package/dist/components/InventoryGeneralSettings.d.ts.map +1 -1
  6. package/dist/data/supabase.d.ts.map +1 -1
  7. package/dist/data/tables.d.ts +10 -0
  8. package/dist/data/tables.d.ts.map +1 -0
  9. package/dist/index.cjs +111 -50
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.ts +8 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +112 -51
  14. package/dist/index.js.map +1 -1
  15. package/dist/migrations/index.d.ts +9 -0
  16. package/dist/migrations/index.d.ts.map +1 -0
  17. package/dist/views/ProductCrudForm.d.ts.map +1 -1
  18. package/dist/views/ProductListView.d.ts.map +1 -1
  19. package/dist/views/RecipeFormView.d.ts.map +1 -1
  20. package/dist/views/RecipesView.d.ts.map +1 -1
  21. package/package.json +10 -4
  22. package/src/InventoryContext.tsx +2 -0
  23. package/src/InventoryPage.tsx +13 -7
  24. package/src/README.md +1 -1
  25. package/src/components/InventoryGeneralSettings.tsx +21 -8
  26. package/src/data/supabase.ts +24 -23
  27. package/src/data/tables.ts +10 -0
  28. package/src/index.ts +43 -0
  29. package/src/migrations/000_plg_rename.sql +27 -0
  30. package/src/migrations/001_inventory_base.sql +27 -27
  31. package/src/migrations/002_recipes.sql +13 -13
  32. package/src/migrations/003_measurement_units.sql +4 -4
  33. package/src/migrations/index.ts +161 -0
  34. package/src/registries.ts +5 -5
  35. package/src/views/ProductCrudForm.tsx +6 -1
  36. package/src/views/ProductListView.tsx +1 -0
  37. package/src/views/RecipeFormView.tsx +6 -0
  38. package/src/views/RecipesView.tsx +6 -3
package/dist/index.d.ts CHANGED
@@ -20,6 +20,14 @@ export interface InventoryPluginOptions {
20
20
  recipes?: boolean;
21
21
  stockLocations?: boolean;
22
22
  batchTracking?: boolean;
23
+ /**
24
+ * The product catalogue screens (list + form). Defaults on. Turn OFF when the
25
+ * host already owns product registration elsewhere — an e-commerce app
26
+ * manages products in the shop plugin, and two competing product CRUDs is
27
+ * how the same product ends up entered twice, differently.
28
+ * Stock entry/exit/history stay available either way.
29
+ */
30
+ products?: boolean;
23
31
  };
24
32
  labels?: Partial<InventoryPluginLabels>;
25
33
  productTypes?: Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAI5E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAazD,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,cAAc,CAAC,EAAE,OAAO,CAAA;QACxB,aAAa,CAAC,EAAE,OAAO,CAAA;KACxB,CAAA;IACD,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtD,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;IACjC,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAChE;AAqDD,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,cAAc,CAqFtF;AAED,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,YAAY,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAI5E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAazD,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,cAAc,CAAC,EAAE,OAAO,CAAA;QACxB,aAAa,CAAC,EAAE,OAAO,CAAA;QACvB;;;;;;WAMG;QACH,QAAQ,CAAC,EAAE,OAAO,CAAA;KACnB,CAAA;IACD,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtD,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;IACjC,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAChE;AAsDD,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,cAAc,CAuHtF;AAED,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,YAAY,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAA"}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import React2, { useState, useMemo, useEffect } from 'react';
2
2
  import { toast, defineKpiWidget, defineCustomWidget, PageTransition, ModulePage, Button, SubpageHeader, SearchSelect, cn, DatePicker, ListView, useSaveBar, Breadcrumb, DataTable, DashboardCanvas, KpiCard, Card, CardHeader, CardTitle, CardContent } from '@fayz-ai/ui';
3
- import { createPluginContext, dedup, useModuleNavigation, createViewRouter, ModuleActionBar, SettingsGroup, ToggleRow, CrudListView, CrudFormPage, formatCurrency } from '@fayz-ai/saas';
3
+ import { createPluginContext, dedup, useModuleNavigation, createViewRouter, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, CrudListView, useLimitGuard, CrudFormPage, formatCurrency, PermissionGate, invalidateLimit } from '@fayz-ai/saas';
4
4
  import { registerTranslations, createSafeDataProvider, useTranslation, getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core';
5
5
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
6
  import { Package, Ruler, MapPin, Check, ChevronRight, ArrowDownRight, ArrowUpRight, RefreshCw, ArrowRightLeft, Trash2, Plus, BookOpen, Layers, Clock } from 'lucide-react';
@@ -167,7 +167,8 @@ function ProductListView({ onNew, onEdit }) {
167
167
  onFacetChange: (_field, value) => setTypeFilter(value),
168
168
  onNew,
169
169
  addLabel: t2("inventory.productList.newProduct"),
170
- onRowClick: (row) => onEdit?.(row.id)
170
+ onRowClick: (row) => onEdit?.(row.id),
171
+ feature: "inventory"
171
172
  }
172
173
  );
173
174
  }
@@ -176,6 +177,7 @@ function ProductCrudForm({ editId, onSaved }) {
176
177
  const { productTypes, currency } = useInventoryConfig();
177
178
  const provider = useInventoryProvider();
178
179
  const createProduct = useInventoryStore((s) => s.createProduct);
180
+ const guardProducts = useLimitGuard("products");
179
181
  const isEdit = !!editId;
180
182
  const [initialData, setInitialData] = React2.useState(isEdit ? null : {});
181
183
  const [headerName, setHeaderName] = React2.useState("");
@@ -227,7 +229,9 @@ function ProductCrudForm({ editId, onSaved }) {
227
229
  if (isEdit && editId) {
228
230
  await provider.updateProduct(editId, payload);
229
231
  } else {
232
+ if (await guardProducts() === "blocked") return;
230
233
  await createProduct(payload);
234
+ invalidateLimit("products");
231
235
  }
232
236
  onSaved?.();
233
237
  }
@@ -803,11 +807,11 @@ function RecipesView({ onNew, onView }) {
803
807
  {
804
808
  title: t2("inventory.recipes.title"),
805
809
  subtitle: t2("inventory.recipes.productionFormulas", { count: String(recipes.length) }),
806
- actions: onNew && /* @__PURE__ */ jsxs("button", { onClick: onNew, className: "inline-flex items-center gap-1.5 rounded-lg bg-primary border border-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors", children: [
810
+ actions: onNew && /* @__PURE__ */ jsx(PermissionGate, { feature: "inventory", action: "create", children: /* @__PURE__ */ jsxs("button", { onClick: onNew, className: "inline-flex items-center gap-1.5 rounded-lg bg-primary border border-primary px-3 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 shadow-button-primary active:shadow-button-inset transition-colors", children: [
807
811
  /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
808
812
  " ",
809
813
  t2("inventory.recipes.newRecipe")
810
- ] })
814
+ ] }) })
811
815
  }
812
816
  ),
813
817
  recipesLoading ? /* @__PURE__ */ jsx(RecipeSkeleton, {}) : recipes.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center rounded-lg border-2 border-dashed border-muted", children: [
@@ -866,6 +870,7 @@ function RecipeFormView({ onSaved }) {
866
870
  const t2 = useTranslation();
867
871
  const provider = useInventoryProvider();
868
872
  const createRecipe = useInventoryStore((s) => s.createRecipe);
873
+ const guardRecipes = useLimitGuard("recipes");
869
874
  const [name, setName] = useState("");
870
875
  const [description, setDescription] = useState("");
871
876
  const [productId, setProductId] = useState("");
@@ -889,6 +894,7 @@ function RecipeFormView({ onSaved }) {
889
894
  toast.error(t2("common.formIncomplete"));
890
895
  return;
891
896
  }
897
+ if (await guardRecipes() === "blocked") return;
892
898
  setSaving(true);
893
899
  try {
894
900
  const recipe = await createRecipe({
@@ -906,6 +912,7 @@ function RecipeFormView({ onSaved }) {
906
912
  notes: i.notes || void 0
907
913
  }))
908
914
  });
915
+ invalidateLimit("recipes");
909
916
  onSaved?.(recipe.id);
910
917
  } finally {
911
918
  setSaving(false);
@@ -1216,28 +1223,31 @@ function RecipeDetailView({ recipeId, onBack }) {
1216
1223
  ] })
1217
1224
  ] });
1218
1225
  }
1226
+ var INVENTORY_DEFAULTS = {
1227
+ lowStockAlerts: true,
1228
+ requireReason: true,
1229
+ autoDeduct: false,
1230
+ requireSku: false,
1231
+ allowNegative: false,
1232
+ lowStockEmail: false,
1233
+ expiryWarnings: true
1234
+ };
1219
1235
  function InventoryGeneralSettings() {
1220
1236
  const t2 = useTranslation();
1237
+ const s = useTenantPluginSettings("inventory", INVENTORY_DEFAULTS);
1221
1238
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1222
1239
  /* @__PURE__ */ jsxs(SettingsGroup, { title: t2("inventory.settings.stockManagement"), description: t2("inventory.settings.stockManagementDesc"), children: [
1223
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.lowStockAlerts"), description: t2("inventory.settings.lowStockAlertsDesc"), checked: true, onChange: () => {
1224
- } }),
1225
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.requireReason"), description: t2("inventory.settings.requireReasonDesc"), checked: true, onChange: () => {
1226
- } }),
1227
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.autoDeduct"), description: t2("inventory.settings.autoDeductDesc"), checked: false, onChange: () => {
1228
- } })
1240
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.lowStockAlerts"), description: t2("inventory.settings.lowStockAlertsDesc"), checked: s.get("lowStockAlerts"), onChange: (v) => s.set("lowStockAlerts", v) }),
1241
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.requireReason"), description: t2("inventory.settings.requireReasonDesc"), checked: s.get("requireReason"), onChange: (v) => s.set("requireReason", v) }),
1242
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.autoDeduct"), description: t2("inventory.settings.autoDeductDesc"), checked: s.get("autoDeduct"), onChange: (v) => s.set("autoDeduct", v) })
1229
1243
  ] }),
1230
1244
  /* @__PURE__ */ jsxs(SettingsGroup, { title: t2("inventory.settings.products"), description: t2("inventory.settings.productsDesc"), children: [
1231
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.requireSku"), description: t2("inventory.settings.requireSkuDesc"), checked: false, onChange: () => {
1232
- } }),
1233
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.allowNegative"), description: t2("inventory.settings.allowNegativeDesc"), checked: false, onChange: () => {
1234
- } })
1245
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.requireSku"), description: t2("inventory.settings.requireSkuDesc"), checked: s.get("requireSku"), onChange: (v) => s.set("requireSku", v) }),
1246
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.allowNegative"), description: t2("inventory.settings.allowNegativeDesc"), checked: s.get("allowNegative"), onChange: (v) => s.set("allowNegative", v) })
1235
1247
  ] }),
1236
1248
  /* @__PURE__ */ jsxs(SettingsGroup, { title: t2("inventory.settings.notifications"), description: t2("inventory.settings.notificationsDesc"), children: [
1237
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.lowStockEmail"), description: t2("inventory.settings.lowStockEmailDesc"), checked: false, onChange: () => {
1238
- } }),
1239
- /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.expiryWarnings"), description: t2("inventory.settings.expiryWarningsDesc"), checked: true, onChange: () => {
1240
- } })
1249
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.lowStockEmail"), description: t2("inventory.settings.lowStockEmailDesc"), checked: s.get("lowStockEmail"), onChange: (v) => s.set("lowStockEmail", v) }),
1250
+ /* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.expiryWarnings"), description: t2("inventory.settings.expiryWarningsDesc"), checked: s.get("expiryWarnings"), onChange: (v) => s.set("expiryWarnings", v) })
1241
1251
  ] })
1242
1252
  ] });
1243
1253
  }
@@ -1279,7 +1289,7 @@ function InventoryOnboarding({ onComplete }) {
1279
1289
  function buildNav(config, view, navigate, t2) {
1280
1290
  const items = [
1281
1291
  { id: "dashboard", label: t2("inventory.nav.dashboard"), icon: "BarChart3", active: view === "dashboard", onClick: () => navigate("dashboard") },
1282
- {
1292
+ ...config.modules.products ? [{
1283
1293
  id: "products",
1284
1294
  label: t2("inventory.nav.products"),
1285
1295
  icon: "Package",
@@ -1288,7 +1298,7 @@ function buildNav(config, view, navigate, t2) {
1288
1298
  { id: "products-new", label: t2("inventory.nav.new"), active: view === "products-new", onClick: () => navigate("products-new") },
1289
1299
  { id: "products-list", label: t2("inventory.nav.list"), active: view === "products-list", onClick: () => navigate("products-list") }
1290
1300
  ]
1291
- },
1301
+ }] : [],
1292
1302
  {
1293
1303
  id: "stock",
1294
1304
  label: t2("inventory.nav.stock"),
@@ -1339,13 +1349,15 @@ function InventoryPage({ config, provider, store, registries }) {
1339
1349
  const nav = buildNav(config, view, navigate, t2);
1340
1350
  const quickActions = useMemo(() => {
1341
1351
  const actions = [
1342
- {
1352
+ // Dropped with the catalogue module: offering "new product" while the
1353
+ // screen is gone would dead-end the user.
1354
+ ...config.modules.products ? [{
1343
1355
  id: "new-product",
1344
1356
  label: t2("inventory.quickActions.newProduct"),
1345
1357
  icon: "Package",
1346
1358
  description: t2("inventory.quickActions.newProductDesc"),
1347
1359
  action: () => navigate("products-new")
1348
- },
1360
+ }] : [],
1349
1361
  {
1350
1362
  id: "stock-entry",
1351
1363
  label: t2("inventory.quickActions.stockEntry"),
@@ -1382,9 +1394,13 @@ function InventoryPage({ config, provider, store, registries }) {
1382
1394
  ] }) }) });
1383
1395
  }
1384
1396
  const renderView = createViewRouter([
1385
- { id: "products-list", render: () => /* @__PURE__ */ jsx(ProductListView, { onNew: () => navigate("products-new"), onEdit: (id) => navigate(`products-edit:${id}`) }) },
1386
- { id: "products-new", render: () => /* @__PURE__ */ jsx(ProductCrudForm, { onSaved: () => navigate("products-list") }) },
1387
- { id: "products-edit", render: ({ id }) => /* @__PURE__ */ jsx(ProductCrudForm, { editId: id, onSaved: () => navigate("products-list") }) },
1397
+ // Routes go with the module, not just the nav entry: leaving them mounted
1398
+ // keeps a second product form reachable by URL.
1399
+ ...config.modules.products ? [
1400
+ { id: "products-list", render: () => /* @__PURE__ */ jsx(ProductListView, { onNew: () => navigate("products-new"), onEdit: (id) => navigate(`products-edit:${id}`) }) },
1401
+ { id: "products-new", render: () => /* @__PURE__ */ jsx(ProductCrudForm, { onSaved: () => navigate("products-list") }) },
1402
+ { id: "products-edit", render: ({ id }) => /* @__PURE__ */ jsx(ProductCrudForm, { editId: id, onSaved: () => navigate("products-list") }) }
1403
+ ] : [],
1388
1404
  { id: "stock-entry", render: () => /* @__PURE__ */ jsx(StockMovementView, { defaultType: "entry", onSaved: () => navigate("stock-history") }) },
1389
1405
  { id: "stock-exit", render: () => /* @__PURE__ */ jsx(StockMovementView, { defaultType: "exit", onSaved: () => navigate("stock-history") }) },
1390
1406
  { id: "stock-history", render: () => /* @__PURE__ */ jsx(MovementHistoryView, { onViewDetail: (id) => navigate(`stock-detail:${id}`) }) },
@@ -1725,6 +1741,17 @@ function createMockInventoryProvider() {
1725
1741
  return provider;
1726
1742
  }
1727
1743
 
1744
+ // src/data/tables.ts
1745
+ var T = {
1746
+ stockLocations: "plg_inventory_stock_locations",
1747
+ stockMovements: "plg_inventory_stock_movements",
1748
+ stockPositions: "plg_inventory_stock_positions",
1749
+ recipes: "plg_inventory_recipes",
1750
+ recipeIngredients: "plg_inventory_recipe_ingredients",
1751
+ measurementUnits: "plg_inventory_measurement_units",
1752
+ productCategories: "plg_inventory_product_categories"
1753
+ };
1754
+
1728
1755
  // src/data/supabase.ts
1729
1756
  function getTenantId() {
1730
1757
  return getActiveTenantId();
@@ -1773,10 +1800,10 @@ function createSupabaseInventoryProvider() {
1773
1800
  function getClients() {
1774
1801
  const supabase = getSupabaseClientOptional();
1775
1802
  if (!supabase) throw new Error("Supabase not initialized");
1776
- return { core: supabase.schema("saas_core"), pub: supabase };
1803
+ return { core: supabase, pub: supabase };
1777
1804
  }
1778
1805
  const provider = {
1779
- // --- Products (saas_core.products) ---
1806
+ // --- Products (public.products) ---
1780
1807
  async getProducts(query) {
1781
1808
  const { core} = getClients();
1782
1809
  let qb = core.from("products").select("*", { count: "exact" });
@@ -1867,7 +1894,7 @@ function createSupabaseInventoryProvider() {
1867
1894
  if (m.destinationLocationId && !m.destinationLocationName) locationIds.add(m.destinationLocationId);
1868
1895
  }
1869
1896
  if (locationIds.size > 0) {
1870
- const { data: locs } = await pub.from("stock_locations").select("id, name").in("id", [...locationIds]);
1897
+ const { data: locs } = await pub.from(T.stockLocations).select("id, name").in("id", [...locationIds]);
1871
1898
  const locMap = new Map((locs ?? []).map((l) => [l.id, l.name]));
1872
1899
  for (const m of movements) {
1873
1900
  if (m.stockLocationId && !m.stockLocationName) m.stockLocationName = locMap.get(m.stockLocationId);
@@ -1888,7 +1915,7 @@ function createSupabaseInventoryProvider() {
1888
1915
  total_cost: unitCost * input.quantity,
1889
1916
  movement_date: input.movementDate ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
1890
1917
  };
1891
- const { data, error } = await pub.from("stock_movements").insert(row).select().single();
1918
+ const { data, error } = await pub.from(T.stockMovements).insert(row).select().single();
1892
1919
  if (error) throw new Error(error.message);
1893
1920
  const { data: product } = await core.from("products").select("id, name, stock").eq("id", input.productId).single();
1894
1921
  if (product) {
@@ -1900,11 +1927,11 @@ function createSupabaseInventoryProvider() {
1900
1927
  if (input.stockLocationId) {
1901
1928
  try {
1902
1929
  const posDelta = input.movementType === "entry" ? input.quantity : -input.quantity;
1903
- const { data: existing } = await pub.from("stock_positions").select("id, quantity").eq("product_id", input.productId).eq("stock_location_id", input.stockLocationId).maybeSingle();
1930
+ const { data: existing } = await pub.from(T.stockPositions).select("id, quantity").eq("product_id", input.productId).eq("stock_location_id", input.stockLocationId).maybeSingle();
1904
1931
  if (existing) {
1905
- await pub.from("stock_positions").update({ quantity: (existing.quantity ?? 0) + posDelta, unit_cost: input.unitCost ?? 0 }).eq("id", existing.id);
1932
+ await pub.from(T.stockPositions).update({ quantity: (existing.quantity ?? 0) + posDelta, unit_cost: input.unitCost ?? 0 }).eq("id", existing.id);
1906
1933
  } else {
1907
- await pub.from("stock_positions").insert({
1934
+ await pub.from(T.stockPositions).insert({
1908
1935
  tenant_id: tenantId,
1909
1936
  product_id: input.productId,
1910
1937
  stock_location_id: input.stockLocationId,
@@ -1915,11 +1942,11 @@ function createSupabaseInventoryProvider() {
1915
1942
  });
1916
1943
  }
1917
1944
  if (input.movementType === "transfer" && input.destinationLocationId) {
1918
- const { data: destExisting } = await pub.from("stock_positions").select("id, quantity").eq("product_id", input.productId).eq("stock_location_id", input.destinationLocationId).maybeSingle();
1945
+ const { data: destExisting } = await pub.from(T.stockPositions).select("id, quantity").eq("product_id", input.productId).eq("stock_location_id", input.destinationLocationId).maybeSingle();
1919
1946
  if (destExisting) {
1920
- await pub.from("stock_positions").update({ quantity: (destExisting.quantity ?? 0) + input.quantity, unit_cost: input.unitCost ?? 0 }).eq("id", destExisting.id);
1947
+ await pub.from(T.stockPositions).update({ quantity: (destExisting.quantity ?? 0) + input.quantity, unit_cost: input.unitCost ?? 0 }).eq("id", destExisting.id);
1921
1948
  } else {
1922
- await pub.from("stock_positions").insert({
1949
+ await pub.from(T.stockPositions).insert({
1923
1950
  tenant_id: tenantId,
1924
1951
  product_id: input.productId,
1925
1952
  stock_location_id: input.destinationLocationId,
@@ -1937,11 +1964,11 @@ function createSupabaseInventoryProvider() {
1937
1964
  const movement = snakeToCamel(data);
1938
1965
  movement.productName = product?.name;
1939
1966
  if (input.stockLocationId) {
1940
- const { data: loc } = await pub.from("stock_locations").select("name").eq("id", input.stockLocationId).single();
1967
+ const { data: loc } = await pub.from(T.stockLocations).select("name").eq("id", input.stockLocationId).single();
1941
1968
  movement.stockLocationName = loc?.name;
1942
1969
  }
1943
1970
  if (input.destinationLocationId) {
1944
- const { data: loc } = await pub.from("stock_locations").select("name").eq("id", input.destinationLocationId).single();
1971
+ const { data: loc } = await pub.from(T.stockLocations).select("name").eq("id", input.destinationLocationId).single();
1945
1972
  movement.destinationLocationName = loc?.name;
1946
1973
  }
1947
1974
  return movement;
@@ -1949,44 +1976,44 @@ function createSupabaseInventoryProvider() {
1949
1976
  // --- Stock Positions ---
1950
1977
  async getPositions(productId) {
1951
1978
  const { pub } = getClients();
1952
- const { data } = await pub.from("stock_positions").select("*").eq("product_id", productId);
1979
+ const { data } = await pub.from(T.stockPositions).select("*").eq("product_id", productId);
1953
1980
  return (data ?? []).map((r) => snakeToCamel(r));
1954
1981
  },
1955
1982
  // --- Stock Locations ---
1956
1983
  async getLocations() {
1957
1984
  const { pub } = getClients();
1958
- const { data } = await pub.from("stock_locations").select("*").eq("is_active", true).order("name");
1985
+ const { data } = await pub.from(T.stockLocations).select("*").eq("is_active", true).order("name");
1959
1986
  return (data ?? []).map((r) => snakeToCamel(r));
1960
1987
  },
1961
1988
  async createLocation(input) {
1962
1989
  const { pub } = getClients();
1963
1990
  const tenantId = getTenantId();
1964
- const { data } = await pub.from("stock_locations").insert({ ...camelToSnake(input), tenant_id: tenantId }).select().single();
1991
+ const { data } = await pub.from(T.stockLocations).insert({ ...camelToSnake(input), tenant_id: tenantId }).select().single();
1965
1992
  return snakeToCamel(data);
1966
1993
  },
1967
1994
  // --- Recipes ---
1968
1995
  async getRecipes() {
1969
1996
  const { pub } = getClients();
1970
- const { data } = await pub.from("recipes").select("*").eq("is_active", true).order("name");
1997
+ const { data } = await pub.from(T.recipes).select("*").eq("is_active", true).order("name");
1971
1998
  return (data ?? []).map((r) => snakeToCamel(r));
1972
1999
  },
1973
2000
  async getRecipeById(id) {
1974
2001
  const { pub } = getClients();
1975
- const { data } = await pub.from("recipes").select("*").eq("id", id).single();
2002
+ const { data } = await pub.from(T.recipes).select("*").eq("id", id).single();
1976
2003
  return data ? snakeToCamel(data) : null;
1977
2004
  },
1978
2005
  async getRecipeIngredients(recipeId) {
1979
2006
  const { pub } = getClients();
1980
- const { data } = await pub.from("recipe_ingredients").select("*").eq("recipe_id", recipeId).order("display_order");
2007
+ const { data } = await pub.from(T.recipeIngredients).select("*").eq("recipe_id", recipeId).order("display_order");
1981
2008
  return (data ?? []).map((r) => snakeToCamel(r));
1982
2009
  },
1983
2010
  async createRecipe(input) {
1984
2011
  const { pub } = getClients();
1985
2012
  const tenantId = getTenantId();
1986
2013
  const { ingredients, ...recipeData } = input;
1987
- const { data: recipe } = await pub.from("recipes").insert({ ...camelToSnake(recipeData), tenant_id: tenantId }).select().single();
2014
+ const { data: recipe } = await pub.from(T.recipes).insert({ ...camelToSnake(recipeData), tenant_id: tenantId }).select().single();
1988
2015
  if (recipe && ingredients.length > 0) {
1989
- await pub.from("recipe_ingredients").insert(
2016
+ await pub.from(T.recipeIngredients).insert(
1990
2017
  ingredients.map((ing) => ({ ...camelToSnake(ing), recipe_id: recipe.id, tenant_id: tenantId }))
1991
2018
  );
1992
2019
  }
@@ -2002,7 +2029,7 @@ function createSupabaseInventoryProvider() {
2002
2029
  const totalValue = items.reduce((sum, p) => sum + (p.stock ?? 0) * (p.price ?? 0), 0);
2003
2030
  const weekAgo = /* @__PURE__ */ new Date();
2004
2031
  weekAgo.setDate(weekAgo.getDate() - 7);
2005
- const { data: movements } = await pub.from("stock_movements").select("movement_type").gte("movement_date", weekAgo.toISOString().slice(0, 10));
2032
+ const { data: movements } = await pub.from(T.stockMovements).select("movement_type").gte("movement_date", weekAgo.toISOString().slice(0, 10));
2006
2033
  const movs = movements ?? [];
2007
2034
  const movementsByType = { entry: 0, exit: 0, adjustment: 0, transfer: 0, loss: 0 };
2008
2035
  for (const m of movs) movementsByType[m.movement_type]++;
@@ -2125,8 +2152,7 @@ var supplierEntity = {
2125
2152
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
2126
2153
  ],
2127
2154
  data: {
2128
- table: "persons",
2129
- schema: "saas_core",
2155
+ table: "people",
2130
2156
  tenantScoped: true,
2131
2157
  filters: { kind: "supplier" },
2132
2158
  defaults: { kind: "supplier" }
@@ -2143,7 +2169,7 @@ var measurementUnitEntity = {
2143
2169
  { key: "abbreviation", label: "Abbreviation", type: "text", required: true, showInTable: true },
2144
2170
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
2145
2171
  ],
2146
- data: { table: "measurement_units", tenantScoped: true }
2172
+ data: { table: T.measurementUnits, tenantScoped: true }
2147
2173
  };
2148
2174
  var productCategoryEntity = {
2149
2175
  name: "Product Category",
@@ -2156,7 +2182,7 @@ var productCategoryEntity = {
2156
2182
  { key: "parentId", label: "Parent", type: "text", showInTable: false },
2157
2183
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
2158
2184
  ],
2159
- data: { table: "product_categories", tenantScoped: true }
2185
+ data: { table: T.productCategories, tenantScoped: true }
2160
2186
  };
2161
2187
  var stockLocationEntity = {
2162
2188
  name: "Stock Location",
@@ -2169,7 +2195,7 @@ var stockLocationEntity = {
2169
2195
  { key: "description", label: "Description", type: "textarea", showInTable: true },
2170
2196
  { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
2171
2197
  ],
2172
- data: { table: "stock_locations", tenantScoped: true }
2198
+ data: { table: T.stockLocations, tenantScoped: true }
2173
2199
  };
2174
2200
  var inventoryRegistries = [
2175
2201
  // --- Editable ---
@@ -2731,6 +2757,7 @@ var DEFAULT_PRODUCT_TYPES = [
2731
2757
  function resolveConfig(options) {
2732
2758
  return {
2733
2759
  modules: {
2760
+ products: options?.modules?.products !== false,
2734
2761
  recipes: options?.modules?.recipes !== false,
2735
2762
  stockLocations: options?.modules?.stockLocations !== false,
2736
2763
  batchTracking: options?.modules?.batchTracking ?? false
@@ -2764,6 +2791,40 @@ function createInventoryPlugin(options) {
2764
2791
  { id: "inventory", label: config.labels.pageTitle, group: config.labels.pageTitle },
2765
2792
  ...config.modules.recipes ? [{ id: "inventory.recipes", label: config.labels.recipes ?? "Recipes", group: config.labels.pageTitle }] : []
2766
2793
  ],
2794
+ queryEntities: [
2795
+ {
2796
+ key: "inventory:products",
2797
+ writable: true,
2798
+ entity: {
2799
+ name: "Product",
2800
+ namePlural: "Products",
2801
+ icon: "Package",
2802
+ limitKey: "products",
2803
+ permission: { feature: "inventory", action: "read" },
2804
+ fields: [
2805
+ { key: "name", label: "Name", type: "text", required: true, searchable: true },
2806
+ { key: "kind", label: "Kind (sale/ingredient/asset)", type: "text" },
2807
+ { key: "price", label: "Price", type: "number" },
2808
+ { key: "cost", label: "Cost", type: "number" },
2809
+ { key: "sku", label: "SKU", type: "text", searchable: true },
2810
+ { key: "isActive", label: "Active", type: "boolean" },
2811
+ { key: "createdAt", label: "Created at", type: "text" }
2812
+ ],
2813
+ data: {
2814
+ table: "products",
2815
+ tenantScoped: true,
2816
+ archetype: "product",
2817
+ archetypeKind: "sale",
2818
+ searchColumns: ["name", "sku"],
2819
+ defaults: { kind: "sale", status: "active", is_active: true }
2820
+ }
2821
+ }
2822
+ }
2823
+ ],
2824
+ declaredLimits: [
2825
+ { key: "products", label: "Products", table: "products" },
2826
+ ...config.modules.recipes ? [{ key: "recipes", label: config.labels.recipes ?? "Recipes", table: "plg_inventory_recipes" }] : []
2827
+ ],
2767
2828
  navigation: [
2768
2829
  {
2769
2830
  section: options?.navSection ?? "main",