@fayz-ai/plugin-inventory 0.16.0 → 0.16.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.
- package/dist/data/registries.d.ts.map +1 -1
- package/dist/index.js +180 -164
- package/dist/index.js.map +1 -1
- package/dist/views/ProductCrudForm.d.ts +3 -1
- package/dist/views/ProductCrudForm.d.ts.map +1 -1
- package/dist/views/ProductDetailView.d.ts +20 -0
- package/dist/views/ProductDetailView.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registries.d.ts","sourceRoot":"","sources":["../../src/data/registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"registries.d.ts","sourceRoot":"","sources":["../../src/data/registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAsGtD,eAAO,MAAM,mBAAmB,EAAE,iBAAiB,EAuClD,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { toast, defineKpiWidget, defineCustomWidget, PageTransition, ComingSoon, ModulePage, Button, FilterPanel, FilterPanelField, RequiredFilter, FilterMenuRow, SubpageHeader, SearchSelect, cn, DatePicker, InlineLink, ListView, Modal, ModalContent, Card, Input, DataTable, SegmentedControl, ConfirmDialog, Badge, DraftCommitBar, Switch, TooltipProvider, PageHeaderActions, DashboardCanvas, Thumbnail,
|
|
1
|
+
import React14, { useMemo, useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
+
import { toast, defineKpiWidget, defineCustomWidget, PageTransition, ComingSoon, ModulePage, Button, FilterPanel, FilterPanelField, RequiredFilter, FilterMenuRow, SubpageHeader, SearchSelect, cn, DatePicker, InlineLink, ListView, Modal, ModalContent, Card, Input, DataTable, SegmentedControl, ConfirmDialog, Badge, DraftCommitBar, Switch, TooltipProvider, PageHeaderActions, DashboardCanvas, Thumbnail, ModalHeader, ModalTitle, ModalBody, Checkbox, ModalFooter, AiActionButton, ModalDescription, Tooltip, TooltipTrigger, TooltipContent, CardContent, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, StockEntryForm, Skeleton, CurrencyInput, openPrintWindow, printDocument, createXlsxReport, useSaveBar, Dropdown, DropdownTrigger, DropdownContent, DropdownItem, DropdownSeparator, KpiCard, CardHeader, CardTitle } from '@fayz-ai/ui';
|
|
3
3
|
import { createPluginContext, UNIT_DETAIL_ZONES, PluginSettingsPanel, dedup, RelatedListTab, useModuleNavigation, usePluginOnboardingGate, PluginOnboardingGateSkeleton, createViewRouter, RegistryCrudView, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, useOrganizationStore, usePermissionOptional, CrudListView, useLimitGuard, invalidateRelationOptions, CrudFormSkeleton, CrudFormPage, CrudDetailPage, formatCurrency, resolveDocumentOpenerFor, useRememberedList, usePersistedState, useScrollMemory, useAgentSurface, PageActions, useOfferedPlugins, invalidateLimit, RecordPickerModal } from '@fayz-ai/admin';
|
|
4
4
|
import { recordRailFields, RECORD_GROUP, activeFacet, createSafeDataProvider, registerTranslations, tl, countByTenant, useTranslation, getActiveUnitId, createPendingCommand, emitDataChanged, getSupabaseClientOptional, getActiveTenantId, errorMessage } from '@fayz-ai/core';
|
|
5
5
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
|
-
import { Trash2, ArrowRightLeft, RefreshCw, ArrowUpRight, ArrowDownRight, Package, Ruler, MapPin, Loader2, Check, ChevronRight, CircleSlash, CircleCheck, Boxes, Layers, CircleDashed, Search, ListFilter, CalendarRange, PackageCheck, ClipboardCheck, AlertTriangle, Plus,
|
|
6
|
+
import { Trash2, ArrowRightLeft, RefreshCw, ArrowUpRight, ArrowDownRight, Package, Ruler, MapPin, Loader2, Check, ChevronRight, CircleSlash, CircleCheck, Boxes, Layers, CircleDashed, Search, ListFilter, CalendarRange, PackageCheck, ClipboardCheck, AlertTriangle, Plus, Sparkles, X, Upload, Building2, Utensils, Clock, Printer, Pencil, Star, Undo2, ArrowUp, ArrowDown, MoreVertical, PackageX } from 'lucide-react';
|
|
7
7
|
import { createStore as createStore$1 } from 'zustand/vanilla';
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
@@ -1767,7 +1767,7 @@ function inventoryModuleDefaults(verticalId) {
|
|
|
1767
1767
|
}
|
|
1768
1768
|
function useInventoryHouseModules() {
|
|
1769
1769
|
const verticalId = useOrganizationStore((state) => state.currentOrg?.verticalId);
|
|
1770
|
-
const defaults =
|
|
1770
|
+
const defaults = React14.useMemo(() => inventoryModuleDefaults(verticalId), [verticalId]);
|
|
1771
1771
|
return useTenantPluginSettings("inventory", defaults);
|
|
1772
1772
|
}
|
|
1773
1773
|
var INVENTORY_DEFAULTS = {
|
|
@@ -1834,145 +1834,6 @@ function useHasMenu() {
|
|
|
1834
1834
|
function useHasRecipes() {
|
|
1835
1835
|
return useInventoryHouseModules().get("recipes");
|
|
1836
1836
|
}
|
|
1837
|
-
function ProductCrudForm({ editId, onSaved }) {
|
|
1838
|
-
const t2 = useTranslation();
|
|
1839
|
-
const { productTypes, currency } = useInventoryConfig();
|
|
1840
|
-
const provider = useInventoryProvider();
|
|
1841
|
-
const createProduct = useInventoryStore((s) => s.createProduct);
|
|
1842
|
-
const guardProducts = useLimitGuard("products");
|
|
1843
|
-
const isEdit = !!editId;
|
|
1844
|
-
const [initialData, setInitialData] = React6.useState(isEdit ? null : {});
|
|
1845
|
-
const [headerName, setHeaderName] = React6.useState("");
|
|
1846
|
-
const [referenceDataReady, setReferenceDataReady] = React6.useState(false);
|
|
1847
|
-
React6.useEffect(() => {
|
|
1848
|
-
let cancelled = false;
|
|
1849
|
-
(async () => {
|
|
1850
|
-
try {
|
|
1851
|
-
await provider.ensureReferenceData?.();
|
|
1852
|
-
invalidateRelationOptions();
|
|
1853
|
-
} catch (error) {
|
|
1854
|
-
console.warn("[inventory] Could not prepare reference data", error);
|
|
1855
|
-
} finally {
|
|
1856
|
-
if (!cancelled) setReferenceDataReady(true);
|
|
1857
|
-
}
|
|
1858
|
-
})();
|
|
1859
|
-
return () => {
|
|
1860
|
-
cancelled = true;
|
|
1861
|
-
};
|
|
1862
|
-
}, [provider]);
|
|
1863
|
-
React6.useEffect(() => {
|
|
1864
|
-
if (!editId) return;
|
|
1865
|
-
let cancelled = false;
|
|
1866
|
-
(async () => {
|
|
1867
|
-
const p = await provider.getProductById(editId);
|
|
1868
|
-
if (cancelled) return;
|
|
1869
|
-
setHeaderName(p?.name ?? "");
|
|
1870
|
-
setInitialData(p ? flattenProduct(p) : {});
|
|
1871
|
-
})();
|
|
1872
|
-
return () => {
|
|
1873
|
-
cancelled = true;
|
|
1874
|
-
};
|
|
1875
|
-
}, [editId]);
|
|
1876
|
-
const hasMenu = useHasMenu();
|
|
1877
|
-
const hasRecipes = useHasRecipes();
|
|
1878
|
-
const entity = React6.useMemo(
|
|
1879
|
-
() => buildProductEntity(t2, productTypes, currency, void 0, provider, true, hasMenu, hasRecipes),
|
|
1880
|
-
[t2, productTypes, currency, provider, hasMenu, hasRecipes]
|
|
1881
|
-
);
|
|
1882
|
-
async function save(values) {
|
|
1883
|
-
const name = String(values.name ?? "").trim();
|
|
1884
|
-
if (!name) {
|
|
1885
|
-
toast.error(t2("common.formIncomplete"));
|
|
1886
|
-
throw new Error("name required");
|
|
1887
|
-
}
|
|
1888
|
-
const classification = {
|
|
1889
|
-
isIngredient: values.isIngredient === true,
|
|
1890
|
-
isForSale: values.isForSale === true,
|
|
1891
|
-
isIntermediate: values.isIntermediate === true,
|
|
1892
|
-
isAsset: values.isAsset === true,
|
|
1893
|
-
isProduced: values.isProduced === true,
|
|
1894
|
-
isResale: values.isResale === true,
|
|
1895
|
-
trackByUnitLabel: values.trackByUnitLabel === true
|
|
1896
|
-
};
|
|
1897
|
-
if (!Object.values(classification).some(Boolean)) {
|
|
1898
|
-
toast.error(t2("inventory.productForm.classificationRequired"));
|
|
1899
|
-
throw new Error("classification required");
|
|
1900
|
-
}
|
|
1901
|
-
const productType = values.productType ?? productTypes[0]?.value;
|
|
1902
|
-
const payload = {
|
|
1903
|
-
classification,
|
|
1904
|
-
extras: {
|
|
1905
|
-
commissionPercent: Number(values.commissionPercent) || 0,
|
|
1906
|
-
chartAccountId: values.chartAccountId ?? "",
|
|
1907
|
-
contentPerUnit: values.contentPerUnit === "" ? void 0 : Number(values.contentPerUnit) || void 0,
|
|
1908
|
-
contentUnitId: values.contentUnitId ?? "",
|
|
1909
|
-
avgPurchasePrice: Number(values.avgPurchasePrice) || void 0,
|
|
1910
|
-
professionalResalePrice: Number(values.professionalResalePrice) || void 0,
|
|
1911
|
-
requireBatchForStock: values.requireBatchForStock === true,
|
|
1912
|
-
requireExpiryForStock: values.requireExpiryForStock === true
|
|
1913
|
-
},
|
|
1914
|
-
...toFichas(values),
|
|
1915
|
-
purchaseUnitId: values.purchaseUnitId ?? "",
|
|
1916
|
-
conversionFactor: Number(values.conversionFactor) || 1,
|
|
1917
|
-
name,
|
|
1918
|
-
sku: values.sku || void 0,
|
|
1919
|
-
barcode: values.barcode || void 0,
|
|
1920
|
-
brand: values.brand || void 0,
|
|
1921
|
-
productType,
|
|
1922
|
-
costPrice: Number(values.costPrice) || 0,
|
|
1923
|
-
// Switching a menu item back to an input must clear the old commercial
|
|
1924
|
-
// price as well as hide it; otherwise reports would keep treating the
|
|
1925
|
-
// ingredient as sellable even though the form no longer showed why.
|
|
1926
|
-
salePrice: classification.isForSale ? Number(values.salePrice) || void 0 : 0,
|
|
1927
|
-
minQuantity: Number(values.minQuantity) || 0,
|
|
1928
|
-
maxQuantity: Number(values.maxQuantity) || void 0,
|
|
1929
|
-
description: values.description || void 0,
|
|
1930
|
-
// Always sent, even when empty: an empty string clears the link, and
|
|
1931
|
-
// `undefined` would leave the previous value in place on edit.
|
|
1932
|
-
categoryId: values.categoryId ?? "",
|
|
1933
|
-
measurementUnitId: values.measurementUnitId ?? "",
|
|
1934
|
-
supplierId: values.supplierId ?? "",
|
|
1935
|
-
defaultLocationId: values.defaultLocationId ?? ""
|
|
1936
|
-
};
|
|
1937
|
-
const imageFile = typeof File !== "undefined" && values._imageFile instanceof File ? values._imageFile : void 0;
|
|
1938
|
-
if (isEdit && editId) {
|
|
1939
|
-
await provider.updateProduct(editId, payload);
|
|
1940
|
-
if (imageFile && provider.uploadProductImage) await provider.uploadProductImage(editId, imageFile);
|
|
1941
|
-
} else {
|
|
1942
|
-
if (await guardProducts() === "blocked") return;
|
|
1943
|
-
const created = await createProduct(payload);
|
|
1944
|
-
if (imageFile && provider.uploadProductImage) {
|
|
1945
|
-
try {
|
|
1946
|
-
await provider.uploadProductImage(created.id, imageFile);
|
|
1947
|
-
} catch (error) {
|
|
1948
|
-
toast.error("Produto criado, mas a foto n\xE3o foi enviada.", {
|
|
1949
|
-
description: errorMessage(error)
|
|
1950
|
-
});
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
invalidateLimit("products");
|
|
1954
|
-
}
|
|
1955
|
-
onSaved?.();
|
|
1956
|
-
}
|
|
1957
|
-
const title = isEdit ? headerName || t2("inventory.productForm.editProduct") : t2("inventory.productForm.newProduct");
|
|
1958
|
-
const subtitle = isEdit ? void 0 : t2("inventory.productForm.addToCatalog");
|
|
1959
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
1960
|
-
/* @__PURE__ */ jsx(SubpageHeader, { title, subtitle, onBack: onSaved, parentLabel: t2("inventory.nav.products") }),
|
|
1961
|
-
(!initialData || !referenceDataReady) && /* @__PURE__ */ jsx(CrudFormSkeleton, { hideBreadcrumb: true }),
|
|
1962
|
-
initialData && referenceDataReady && /* @__PURE__ */ jsx(
|
|
1963
|
-
CrudFormPage,
|
|
1964
|
-
{
|
|
1965
|
-
entityDef: entity,
|
|
1966
|
-
mode: isEdit ? "edit" : "create",
|
|
1967
|
-
initialData,
|
|
1968
|
-
onSubmit: save,
|
|
1969
|
-
onCancel: () => onSaved?.(),
|
|
1970
|
-
namePlural: t2("inventory.nav.products"),
|
|
1971
|
-
hideBreadcrumb: true
|
|
1972
|
-
}
|
|
1973
|
-
)
|
|
1974
|
-
] });
|
|
1975
|
-
}
|
|
1976
1837
|
|
|
1977
1838
|
// src/lib/recipe-units.ts
|
|
1978
1839
|
var FAMILY_BY_ABBR = {
|
|
@@ -3137,13 +2998,14 @@ var SHEET_LAYOUT = `
|
|
|
3137
2998
|
.rs-comp{grid-template-columns:minmax(0,1fr) minmax(0,1fr) 9rem auto}
|
|
3138
2999
|
.rs-comp>*:not(.rs-comp-menu){grid-column:auto}
|
|
3139
3000
|
.rs-comp>.rs-comp-menu{grid-column:4}
|
|
3001
|
+
.rs-line,.rs-line-head{display:grid;grid-template-columns:minmax(0,2fr) 170px minmax(0,1.2fr) 96px 36px;align-items:center;gap:.5rem;border:0;padding:0}
|
|
3002
|
+
.rs-line>*{grid-column:auto !important}
|
|
3003
|
+
.rs-line>:nth-child(4){justify-content:flex-end}
|
|
3140
3004
|
}
|
|
3141
3005
|
@container (min-width: 60rem){
|
|
3142
3006
|
.rs-head{grid-template-columns:minmax(0,2.2fr) repeat(4,minmax(0,1fr))}
|
|
3143
3007
|
.rs-head>.rs-name{grid-column:auto}
|
|
3144
|
-
.rs-line,.rs-line-head{
|
|
3145
|
-
.rs-line>*{grid-column:auto !important}
|
|
3146
|
-
.rs-line>:nth-child(4){justify-content:flex-end}
|
|
3008
|
+
.rs-line,.rs-line-head{grid-template-columns:minmax(0,2fr) 200px minmax(0,1.4fr) 120px 36px;gap:.75rem}
|
|
3147
3009
|
.rs-out,.rs-out-head{display:grid;grid-template-columns:minmax(0,2fr) 150px 110px 160px 100px 36px;align-items:center;gap:.75rem;border:0;padding:0}
|
|
3148
3010
|
}
|
|
3149
3011
|
`;
|
|
@@ -3557,6 +3419,19 @@ function DetailSkeleton() {
|
|
|
3557
3419
|
/* @__PURE__ */ jsx("div", { className: "rounded-card border p-4 space-y-2", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx("div", { className: "h-3.5 rounded bg-muted/30 animate-pulse" }, i)) })
|
|
3558
3420
|
] });
|
|
3559
3421
|
}
|
|
3422
|
+
function productRecordTabs({ entity, flat, hasMenu, hasRecipes, withComplements, initialTab, contributedTabs, t: t2, reload }) {
|
|
3423
|
+
const extra = [];
|
|
3424
|
+
if (hasMenu && flat?.isForSale && withComplements) {
|
|
3425
|
+
extra.push({ id: "complements", label: t2("inventory.complements.tab"), icon: "ListPlus", component: ProductComplementsTab });
|
|
3426
|
+
}
|
|
3427
|
+
if (hasRecipes && (flat?.isProduced || initialTab === "recipe")) {
|
|
3428
|
+
extra.push({ id: "recipe", label: t2("inventory.productRecipe.tab"), icon: "ChefHat", component: ProductRecipeTab, props: { onChanged: reload } });
|
|
3429
|
+
}
|
|
3430
|
+
const tabs = entity.detailTabs ?? [];
|
|
3431
|
+
const stockAt = tabs.findIndex((tab) => tab.id === "stock");
|
|
3432
|
+
const own = stockAt < 0 ? [...tabs, ...extra] : [...tabs.slice(0, stockAt), ...extra, ...tabs.slice(stockAt)];
|
|
3433
|
+
return [...own, ...contributedTabs];
|
|
3434
|
+
}
|
|
3560
3435
|
function ProductDetailView({ productId, initialTab, onBack, onEdit }) {
|
|
3561
3436
|
const t2 = useTranslation();
|
|
3562
3437
|
const { currency, productTypes, productDetailTabs: contributedTabs } = useInventoryConfig();
|
|
@@ -3593,17 +3468,7 @@ function ProductDetailView({ productId, initialTab, onBack, onEdit }) {
|
|
|
3593
3468
|
}, [provider, productId, reload, flat]);
|
|
3594
3469
|
const [photoOpen, setPhotoOpen] = useState(false);
|
|
3595
3470
|
const entityForDetail = useMemo(() => {
|
|
3596
|
-
const
|
|
3597
|
-
if (hasMenu && flat?.isForSale && provider.getProductOptionGroups) {
|
|
3598
|
-
extra.push({ id: "complements", label: t2("inventory.complements.tab"), icon: "ListPlus", component: ProductComplementsTab });
|
|
3599
|
-
}
|
|
3600
|
-
if (hasRecipes && (flat?.isProduced || initialTab === "recipe")) {
|
|
3601
|
-
extra.push({ id: "recipe", label: t2("inventory.productRecipe.tab"), icon: "ChefHat", component: ProductRecipeTab, props: { onChanged: reload } });
|
|
3602
|
-
}
|
|
3603
|
-
const tabs = entity.detailTabs ?? [];
|
|
3604
|
-
const stockAt = tabs.findIndex((tab) => tab.id === "stock");
|
|
3605
|
-
const own = stockAt < 0 ? [...tabs, ...extra] : [...tabs.slice(0, stockAt), ...extra, ...tabs.slice(stockAt)];
|
|
3606
|
-
const detailTabs = [...own, ...contributedTabs];
|
|
3471
|
+
const detailTabs = productRecordTabs({ entity, flat, hasMenu, hasRecipes, withComplements: Boolean(provider.getProductOptionGroups), initialTab, contributedTabs, t: t2, reload });
|
|
3607
3472
|
return {
|
|
3608
3473
|
...entity,
|
|
3609
3474
|
detailTabs,
|
|
@@ -3649,6 +3514,154 @@ function ProductDetailView({ productId, initialTab, onBack, onEdit }) {
|
|
|
3649
3514
|
photoOpen && /* @__PURE__ */ jsx(GenerateImageDialog, { product, onClose: () => setPhotoOpen(false), onApplied: reload })
|
|
3650
3515
|
] });
|
|
3651
3516
|
}
|
|
3517
|
+
function ProductCrudForm({ editId, onSaved, onOpenTab }) {
|
|
3518
|
+
const t2 = useTranslation();
|
|
3519
|
+
const { productTypes, currency, productDetailTabs: contributedTabs } = useInventoryConfig();
|
|
3520
|
+
const provider = useInventoryProvider();
|
|
3521
|
+
const createProduct = useInventoryStore((s) => s.createProduct);
|
|
3522
|
+
const guardProducts = useLimitGuard("products");
|
|
3523
|
+
const isEdit = !!editId;
|
|
3524
|
+
const [initialData, setInitialData] = React14.useState(isEdit ? null : {});
|
|
3525
|
+
const [headerName, setHeaderName] = React14.useState("");
|
|
3526
|
+
const [referenceDataReady, setReferenceDataReady] = React14.useState(false);
|
|
3527
|
+
React14.useEffect(() => {
|
|
3528
|
+
let cancelled = false;
|
|
3529
|
+
(async () => {
|
|
3530
|
+
try {
|
|
3531
|
+
await provider.ensureReferenceData?.();
|
|
3532
|
+
invalidateRelationOptions();
|
|
3533
|
+
} catch (error) {
|
|
3534
|
+
console.warn("[inventory] Could not prepare reference data", error);
|
|
3535
|
+
} finally {
|
|
3536
|
+
if (!cancelled) setReferenceDataReady(true);
|
|
3537
|
+
}
|
|
3538
|
+
})();
|
|
3539
|
+
return () => {
|
|
3540
|
+
cancelled = true;
|
|
3541
|
+
};
|
|
3542
|
+
}, [provider]);
|
|
3543
|
+
React14.useEffect(() => {
|
|
3544
|
+
if (!editId) return;
|
|
3545
|
+
let cancelled = false;
|
|
3546
|
+
(async () => {
|
|
3547
|
+
const p = await provider.getProductById(editId);
|
|
3548
|
+
if (cancelled) return;
|
|
3549
|
+
setHeaderName(p?.name ?? "");
|
|
3550
|
+
setInitialData(p ? flattenProduct(p) : {});
|
|
3551
|
+
})();
|
|
3552
|
+
return () => {
|
|
3553
|
+
cancelled = true;
|
|
3554
|
+
};
|
|
3555
|
+
}, [editId]);
|
|
3556
|
+
const hasMenu = useHasMenu();
|
|
3557
|
+
const hasRecipes = useHasRecipes();
|
|
3558
|
+
const entity = React14.useMemo(
|
|
3559
|
+
() => buildProductEntity(t2, productTypes, currency, void 0, provider, true, hasMenu, hasRecipes),
|
|
3560
|
+
[t2, productTypes, currency, provider, hasMenu, hasRecipes]
|
|
3561
|
+
);
|
|
3562
|
+
async function save(values) {
|
|
3563
|
+
const name = String(values.name ?? "").trim();
|
|
3564
|
+
if (!name) {
|
|
3565
|
+
toast.error(t2("common.formIncomplete"));
|
|
3566
|
+
throw new Error("name required");
|
|
3567
|
+
}
|
|
3568
|
+
const classification = {
|
|
3569
|
+
isIngredient: values.isIngredient === true,
|
|
3570
|
+
isForSale: values.isForSale === true,
|
|
3571
|
+
isIntermediate: values.isIntermediate === true,
|
|
3572
|
+
isAsset: values.isAsset === true,
|
|
3573
|
+
isProduced: values.isProduced === true,
|
|
3574
|
+
isResale: values.isResale === true,
|
|
3575
|
+
trackByUnitLabel: values.trackByUnitLabel === true
|
|
3576
|
+
};
|
|
3577
|
+
if (!Object.values(classification).some(Boolean)) {
|
|
3578
|
+
toast.error(t2("inventory.productForm.classificationRequired"));
|
|
3579
|
+
throw new Error("classification required");
|
|
3580
|
+
}
|
|
3581
|
+
const productType = values.productType ?? productTypes[0]?.value;
|
|
3582
|
+
const payload = {
|
|
3583
|
+
classification,
|
|
3584
|
+
extras: {
|
|
3585
|
+
commissionPercent: Number(values.commissionPercent) || 0,
|
|
3586
|
+
chartAccountId: values.chartAccountId ?? "",
|
|
3587
|
+
contentPerUnit: values.contentPerUnit === "" ? void 0 : Number(values.contentPerUnit) || void 0,
|
|
3588
|
+
contentUnitId: values.contentUnitId ?? "",
|
|
3589
|
+
avgPurchasePrice: Number(values.avgPurchasePrice) || void 0,
|
|
3590
|
+
professionalResalePrice: Number(values.professionalResalePrice) || void 0,
|
|
3591
|
+
requireBatchForStock: values.requireBatchForStock === true,
|
|
3592
|
+
requireExpiryForStock: values.requireExpiryForStock === true
|
|
3593
|
+
},
|
|
3594
|
+
...toFichas(values),
|
|
3595
|
+
purchaseUnitId: values.purchaseUnitId ?? "",
|
|
3596
|
+
conversionFactor: Number(values.conversionFactor) || 1,
|
|
3597
|
+
name,
|
|
3598
|
+
sku: values.sku || void 0,
|
|
3599
|
+
barcode: values.barcode || void 0,
|
|
3600
|
+
brand: values.brand || void 0,
|
|
3601
|
+
productType,
|
|
3602
|
+
costPrice: Number(values.costPrice) || 0,
|
|
3603
|
+
// Switching a menu item back to an input must clear the old commercial
|
|
3604
|
+
// price as well as hide it; otherwise reports would keep treating the
|
|
3605
|
+
// ingredient as sellable even though the form no longer showed why.
|
|
3606
|
+
salePrice: classification.isForSale ? Number(values.salePrice) || void 0 : 0,
|
|
3607
|
+
minQuantity: Number(values.minQuantity) || 0,
|
|
3608
|
+
maxQuantity: Number(values.maxQuantity) || void 0,
|
|
3609
|
+
description: values.description || void 0,
|
|
3610
|
+
// Always sent, even when empty: an empty string clears the link, and
|
|
3611
|
+
// `undefined` would leave the previous value in place on edit.
|
|
3612
|
+
categoryId: values.categoryId ?? "",
|
|
3613
|
+
measurementUnitId: values.measurementUnitId ?? "",
|
|
3614
|
+
supplierId: values.supplierId ?? "",
|
|
3615
|
+
defaultLocationId: values.defaultLocationId ?? ""
|
|
3616
|
+
};
|
|
3617
|
+
const imageFile = typeof File !== "undefined" && values._imageFile instanceof File ? values._imageFile : void 0;
|
|
3618
|
+
if (isEdit && editId) {
|
|
3619
|
+
await provider.updateProduct(editId, payload);
|
|
3620
|
+
if (imageFile && provider.uploadProductImage) await provider.uploadProductImage(editId, imageFile);
|
|
3621
|
+
} else {
|
|
3622
|
+
if (await guardProducts() === "blocked") return;
|
|
3623
|
+
const created = await createProduct(payload);
|
|
3624
|
+
if (imageFile && provider.uploadProductImage) {
|
|
3625
|
+
try {
|
|
3626
|
+
await provider.uploadProductImage(created.id, imageFile);
|
|
3627
|
+
} catch (error) {
|
|
3628
|
+
toast.error("Produto criado, mas a foto n\xE3o foi enviada.", {
|
|
3629
|
+
description: errorMessage(error)
|
|
3630
|
+
});
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
invalidateLimit("products");
|
|
3634
|
+
}
|
|
3635
|
+
onSaved?.();
|
|
3636
|
+
}
|
|
3637
|
+
const formEntity = React14.useMemo(() => {
|
|
3638
|
+
if (!isEdit || !initialData) return entity;
|
|
3639
|
+
return {
|
|
3640
|
+
...entity,
|
|
3641
|
+
detailTabs: productRecordTabs({ entity, flat: initialData, hasMenu, hasRecipes, withComplements: Boolean(provider.getProductOptionGroups), contributedTabs, t: t2 })
|
|
3642
|
+
};
|
|
3643
|
+
}, [entity, isEdit, initialData, hasMenu, hasRecipes, provider, contributedTabs, t2]);
|
|
3644
|
+
const ready = Boolean(initialData) && referenceDataReady;
|
|
3645
|
+
const title = isEdit ? headerName || t2("inventory.productForm.editProduct") : t2("inventory.productForm.newProduct");
|
|
3646
|
+
const subtitle = isEdit ? void 0 : t2("inventory.productForm.addToCatalog");
|
|
3647
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
|
|
3648
|
+
(!isEdit || !ready) && /* @__PURE__ */ jsx(SubpageHeader, { title, subtitle, onBack: onSaved, parentLabel: t2("inventory.nav.products") }),
|
|
3649
|
+
!ready && /* @__PURE__ */ jsx(CrudFormSkeleton, { twoColumn: true, tabs: isEdit }),
|
|
3650
|
+
ready && /* @__PURE__ */ jsx(
|
|
3651
|
+
CrudFormPage,
|
|
3652
|
+
{
|
|
3653
|
+
entityDef: formEntity,
|
|
3654
|
+
mode: isEdit ? "edit" : "create",
|
|
3655
|
+
initialData,
|
|
3656
|
+
onSubmit: save,
|
|
3657
|
+
onCancel: () => onSaved?.(),
|
|
3658
|
+
namePlural: t2("inventory.nav.products"),
|
|
3659
|
+
hideBreadcrumb: !isEdit,
|
|
3660
|
+
onRecordTab: isEdit ? onOpenTab : void 0
|
|
3661
|
+
}
|
|
3662
|
+
)
|
|
3663
|
+
] });
|
|
3664
|
+
}
|
|
3652
3665
|
function signedQuantity(q) {
|
|
3653
3666
|
const n = Number(q) || 0;
|
|
3654
3667
|
return n > 0 ? `+${n}` : String(n);
|
|
@@ -5838,7 +5851,7 @@ function parseQuantity(raw) {
|
|
|
5838
5851
|
if (!Number.isFinite(parsed) || parsed < 0) return void 0;
|
|
5839
5852
|
return parsed;
|
|
5840
5853
|
}
|
|
5841
|
-
var CountedField =
|
|
5854
|
+
var CountedField = React14.memo(function CountedField2({
|
|
5842
5855
|
entry,
|
|
5843
5856
|
value,
|
|
5844
5857
|
onChange,
|
|
@@ -6628,7 +6641,7 @@ function InventoryPage({ config: appConfig, provider, store, registries }) {
|
|
|
6628
6641
|
"products-recipe": 1,
|
|
6629
6642
|
settings: 1
|
|
6630
6643
|
}, "dashboard");
|
|
6631
|
-
const checkExistingData =
|
|
6644
|
+
const checkExistingData = React14.useCallback(
|
|
6632
6645
|
async () => (await provider.getProducts({ pageSize: 1 })).total > 0,
|
|
6633
6646
|
[provider]
|
|
6634
6647
|
);
|
|
@@ -6641,7 +6654,7 @@ function InventoryPage({ config: appConfig, provider, store, registries }) {
|
|
|
6641
6654
|
temperature: houseModules.get("temperature"),
|
|
6642
6655
|
hiddenViews: houseModules.get("hiddenViews")
|
|
6643
6656
|
});
|
|
6644
|
-
const completeOnboarding =
|
|
6657
|
+
const completeOnboarding = React14.useCallback(async () => {
|
|
6645
6658
|
await provider.ensureReferenceData?.();
|
|
6646
6659
|
onboarding.complete();
|
|
6647
6660
|
}, [provider, onboarding.complete]);
|
|
@@ -6704,7 +6717,7 @@ function InventoryPage({ config: appConfig, provider, store, registries }) {
|
|
|
6704
6717
|
{ id: "products", render: () => /* @__PURE__ */ jsx(ProductListView, { onNew: () => navigate("products-new"), onOpen: (id) => navigate(`products-detail:${id}`) }) },
|
|
6705
6718
|
{ id: "products-list", render: () => /* @__PURE__ */ jsx(ProductListView, { onNew: () => navigate("products-new"), onOpen: (id) => navigate(`products-detail:${id}`) }) },
|
|
6706
6719
|
{ id: "products-new", render: () => /* @__PURE__ */ jsx(ProductCrudForm, { onSaved: () => navigate("products-list") }) },
|
|
6707
|
-
{ id: "products-edit", render: ({ id }) => /* @__PURE__ */ jsx(ProductCrudForm, { editId: id, onSaved: () => navigate(`products-detail:${id}`) }) },
|
|
6720
|
+
{ id: "products-edit", render: ({ id }) => /* @__PURE__ */ jsx(ProductCrudForm, { editId: id, onSaved: () => navigate(`products-detail:${id}`), onOpenTab: (tab) => navigate(`products-${tab}:${id}`) }) },
|
|
6708
6721
|
{ id: "products-detail", render: ({ id }) => /* @__PURE__ */ jsx(ProductDetailView, { productId: id, onBack: () => navigate("products-list"), onEdit: () => navigate(`products-edit:${id}`) }) },
|
|
6709
6722
|
// Every tab writes `/products/<id>/<tab>` into the URL, so each of those
|
|
6710
6723
|
// links has to come back to the same page with that tab open. Only
|
|
@@ -9972,6 +9985,9 @@ var supplierEntity = {
|
|
|
9972
9985
|
// office that invoices, the warehouse the goods leave from). `layout:
|
|
9973
9986
|
// 'person'` gives the record an "Addresses" tab backed by public.addresses,
|
|
9974
9987
|
// which is the same relation clients and patients already use.
|
|
9988
|
+
// Declares the supplier as someone the house pays, which is what keeps
|
|
9989
|
+
// the "Bank accounts" section on the record.
|
|
9990
|
+
{ key: "payeeAccount", label: "Bank account", type: "bank", showInTable: false, showInDetail: false },
|
|
9975
9991
|
{ key: "notes", label: "Notes", type: "textarea" },
|
|
9976
9992
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
9977
9993
|
],
|
|
@@ -21466,7 +21482,7 @@ function createInventoryPlugin(options) {
|
|
|
21466
21482
|
const provider = options?.dataProvider ?? createInventoryDataProvider();
|
|
21467
21483
|
const store = createInventoryStore(provider);
|
|
21468
21484
|
const dashboardWidgets = createInventoryDashboardWidgets({ config, provider, store });
|
|
21469
|
-
const PageComponent = () =>
|
|
21485
|
+
const PageComponent = () => React14.createElement(InventoryPage, { config, provider, store, registries: inventoryRegistries });
|
|
21470
21486
|
return {
|
|
21471
21487
|
id: "inventory",
|
|
21472
21488
|
// Como esta conta refaz o módulo com as opções DELA. Ver PluginRebuild:
|
|
@@ -21635,9 +21651,9 @@ function createInventoryPlugin(options) {
|
|
|
21635
21651
|
// Categories, Units and Suppliers all existed in code and appeared
|
|
21636
21652
|
// nowhere. financial and crm already render through it.
|
|
21637
21653
|
component: (() => {
|
|
21638
|
-
const Tab = () =>
|
|
21654
|
+
const Tab = () => React14.createElement(PluginSettingsPanel, {
|
|
21639
21655
|
title: config.labels.pageTitle,
|
|
21640
|
-
generalSettings:
|
|
21656
|
+
generalSettings: React14.createElement(InventoryGeneralSettings, { config }),
|
|
21641
21657
|
registries: inventoryRegistries,
|
|
21642
21658
|
hostPluginId: "inventory",
|
|
21643
21659
|
routeBase: "/settings/inventory"
|