@fayz-ai/plugin-inventory 0.8.0 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/InventoryContext.d.ts +2 -0
- package/dist/InventoryContext.d.ts.map +1 -1
- package/dist/InventoryPage.d.ts.map +1 -1
- package/dist/components/InventoryGeneralSettings.d.ts.map +1 -1
- package/dist/index.cjs +80 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +81 -26
- package/dist/index.js.map +1 -1
- package/dist/views/ProductCrudForm.d.ts.map +1 -1
- package/dist/views/ProductListView.d.ts.map +1 -1
- package/dist/views/RecipeFormView.d.ts.map +1 -1
- package/dist/views/RecipesView.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/InventoryContext.tsx +2 -0
- package/src/InventoryPage.tsx +13 -7
- package/src/components/InventoryGeneralSettings.tsx +21 -8
- package/src/index.ts +47 -0
- package/src/views/ProductCrudForm.tsx +6 -1
- package/src/views/ProductListView.tsx +1 -0
- package/src/views/RecipeFormView.tsx +6 -0
- 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<{
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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,CA2HtF;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:
|
|
1224
|
-
|
|
1225
|
-
/* @__PURE__ */ jsx(ToggleRow, { label: t2("inventory.settings.
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
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}`) }) },
|
|
@@ -2741,6 +2757,7 @@ var DEFAULT_PRODUCT_TYPES = [
|
|
|
2741
2757
|
function resolveConfig(options) {
|
|
2742
2758
|
return {
|
|
2743
2759
|
modules: {
|
|
2760
|
+
products: options?.modules?.products !== false,
|
|
2744
2761
|
recipes: options?.modules?.recipes !== false,
|
|
2745
2762
|
stockLocations: options?.modules?.stockLocations !== false,
|
|
2746
2763
|
batchTracking: options?.modules?.batchTracking ?? false
|
|
@@ -2774,6 +2791,40 @@ function createInventoryPlugin(options) {
|
|
|
2774
2791
|
{ id: "inventory", label: config.labels.pageTitle, group: config.labels.pageTitle },
|
|
2775
2792
|
...config.modules.recipes ? [{ id: "inventory.recipes", label: config.labels.recipes ?? "Recipes", group: config.labels.pageTitle }] : []
|
|
2776
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
|
+
],
|
|
2777
2828
|
navigation: [
|
|
2778
2829
|
{
|
|
2779
2830
|
section: options?.navSection ?? "main",
|
|
@@ -2788,7 +2839,11 @@ function createInventoryPlugin(options) {
|
|
|
2788
2839
|
{
|
|
2789
2840
|
path: "/inventory",
|
|
2790
2841
|
component: PageComponent,
|
|
2791
|
-
permission: { feature: "inventory", action: "read" }
|
|
2842
|
+
permission: { feature: "inventory", action: "read" },
|
|
2843
|
+
// Products live on this hand-built page, not a CRUD route, so nothing
|
|
2844
|
+
// would register them and a product the assistant just created would
|
|
2845
|
+
// resolve to no page at all.
|
|
2846
|
+
entityRoutes: ["product:sale", "product"]
|
|
2792
2847
|
}
|
|
2793
2848
|
],
|
|
2794
2849
|
widgets: [],
|