@fayz-ai/plugin-inventory 0.13.2 → 0.13.5
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/context.d.ts +12 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/registries.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/types.d.ts +3 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1234 -127
- package/dist/index.js.map +1 -1
- package/dist/lib/stock-health.d.ts +14 -0
- package/dist/lib/stock-health.d.ts.map +1 -0
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/types.d.ts +36 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/GapScreens.d.ts +14 -0
- package/dist/views/GapScreens.d.ts.map +1 -0
- package/dist/views/InventoryPage.d.ts.map +1 -1
- package/dist/views/MovementHistoryView.d.ts.map +1 -1
- package/dist/views/ProductListView.d.ts.map +1 -1
- package/dist/views/StockCountSessionView.d.ts.map +1 -1
- package/dist/views/StockCountsView.d.ts.map +1 -1
- package/dist/views/StockPanelView.d.ts +3 -0
- package/dist/views/StockPanelView.d.ts.map +1 -0
- package/dist/views/productEntity.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React3, { useMemo, useState, useEffect, useCallback, useRef } from 'react';
|
|
2
|
-
import { toast, defineKpiWidget, defineCustomWidget, PageTransition, ModulePage, Button, SubpageHeader, SearchSelect, cn, DatePicker, ListView, ConfirmDialog, Badge, DraftCommitBar, Switch, useSaveBar,
|
|
3
|
-
import { createPluginContext, PluginSettingsPanel, dedup, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, PluginOnboardingGateSkeleton, createViewRouter, ModuleActionBar, SettingsGroup, ToggleRow, usePermissionOptional, CrudListView, useLimitGuard, invalidateRelationOptions, CrudFormSkeleton, CrudFormPage, CrudDetailPage, formatCurrency, useAgentSurface, askAI, PermissionGate, QuickActionsButton, invalidateLimit } from '@fayz-ai/admin';
|
|
4
|
-
import { createSafeDataProvider, registerTranslations, countByTenant, useTranslation, getActiveUnitId, createPendingCommand, errorMessage, useDataChanged, getSupabaseClientOptional, getActiveTenantId } from '@fayz-ai/core';
|
|
2
|
+
import { toast, defineKpiWidget, defineCustomWidget, PageTransition, ComingSoon, ModulePage, Button, FilterPanel, FilterPanelField, RequiredFilter, FilterMenuRow, SubpageHeader, SearchSelect, cn, DatePicker, ListView, Card, Input, DataTable, SegmentedControl, ConfirmDialog, Badge, DraftCommitBar, Switch, useSaveBar, DashboardCanvas, Thumbnail, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, ModalFooter, StockEntryForm, KpiCard, CardHeader, CardTitle, CardContent, Skeleton } from '@fayz-ai/ui';
|
|
3
|
+
import { createPluginContext, PluginSettingsPanel, dedup, useTenantPluginSettings, useModuleNavigation, usePluginOnboardingGate, PluginOnboardingGateSkeleton, createViewRouter, RegistryCrudView, ModuleActionBar, SettingsGroup, ToggleRow, usePermissionOptional, CrudListView, useLimitGuard, invalidateRelationOptions, CrudFormSkeleton, CrudFormPage, CrudDetailPage, formatCurrency, useAgentSurface, askAI, PermissionGate, QuickActionsButton, invalidateLimit } from '@fayz-ai/admin';
|
|
4
|
+
import { recordRailFields, RECORD_GROUP, activeFacet, createSafeDataProvider, registerTranslations, countByTenant, useTranslation, getActiveUnitId, createPendingCommand, errorMessage, useDataChanged, getSupabaseClientOptional, getActiveTenantId } 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, ListFilter, CalendarRange, PackageCheck, ClipboardCheck, AlertTriangle, Plus, BookOpen,
|
|
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, BookOpen, Clock, Coins, Building2, Undo2, PackageX } from 'lucide-react';
|
|
7
7
|
import { createStore as createStore$1 } from 'zustand/vanilla';
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
@@ -554,13 +554,22 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
|
|
|
554
554
|
relation: { table: T.stockLocations, labelField: "name" },
|
|
555
555
|
placeholder: t2("inventory.productForm.optional"),
|
|
556
556
|
showInTable: false
|
|
557
|
-
}
|
|
557
|
+
},
|
|
558
|
+
// O rail de todo cadastro. `isActive` já era LIDO na lista (a linha
|
|
559
|
+
// aparece esmaecida) e não tinha campo: dava para ver que um produto
|
|
560
|
+
// estava inativo e não dava para reativá-lo.
|
|
561
|
+
...recordRailFields({ tagsTable: "products", activeLabel: t2("common.active") })
|
|
558
562
|
];
|
|
559
563
|
return {
|
|
560
564
|
name: t2("inventory.stock.product"),
|
|
561
565
|
namePlural: t2("inventory.nav.products"),
|
|
562
566
|
icon: "Package",
|
|
563
567
|
displayField: "name",
|
|
568
|
+
// MOSTRAR e TROCAR são coisas diferentes. `image` só existe quando há
|
|
569
|
+
// provider, porque é ele que sabe enviar o arquivo — mas sem `imageField` a
|
|
570
|
+
// página do produto ficava sem foto mesmo com a foto na linha, enquanto a
|
|
571
|
+
// lista a desenhava. O campo responde por desenhar; `image`, por substituir.
|
|
572
|
+
imageField: "imageUrl",
|
|
564
573
|
image: provider?.uploadProductImage ? productImage(provider) : void 0,
|
|
565
574
|
fields,
|
|
566
575
|
fieldGroups: [
|
|
@@ -568,9 +577,15 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
|
|
|
568
577
|
{ id: "classification", label: t2("inventory.productForm.classification"), description: t2("inventory.productForm.classificationDesc"), columns: 1 },
|
|
569
578
|
{ id: "pricing", label: t2("inventory.productForm.pricing"), columns: 3 },
|
|
570
579
|
{ id: "stock", label: t2("inventory.productForm.stockLevels"), description: t2("inventory.productForm.stockLevelsDesc"), columns: 2 },
|
|
571
|
-
{ id: "organization", label: t2("inventory.productForm.organization"), description: t2("inventory.productForm.organizationDesc"), columns: 2 }
|
|
580
|
+
{ id: "organization", label: t2("inventory.productForm.organization"), description: t2("inventory.productForm.organizationDesc"), columns: 2 },
|
|
581
|
+
{ ...RECORD_GROUP }
|
|
582
|
+
],
|
|
583
|
+
// Duas facetas: o TIPO (o que a coisa é) e a SITUAÇÃO (se vale). A
|
|
584
|
+
// segunda abre em «Ativos», como toda lista de cadastro.
|
|
585
|
+
facets: [
|
|
586
|
+
{ field: "productType", allLabel: t2("crud.list.allFacet") },
|
|
587
|
+
activeFacet({ active: t2("common.active"), inactive: t2("common.inactive"), all: t2("crud.list.allFacet") })
|
|
572
588
|
],
|
|
573
|
-
facets: [{ field: "productType", allLabel: t2("crud.list.allFacet") }],
|
|
574
589
|
detailTabs: [
|
|
575
590
|
{
|
|
576
591
|
id: "stock",
|
|
@@ -590,21 +605,53 @@ function ProductListView({ onNew, onOpen }) {
|
|
|
590
605
|
const { currency, productTypes } = useInventoryConfig();
|
|
591
606
|
const products2 = useInventoryStore((s) => s.products);
|
|
592
607
|
const productsTotal = useInventoryStore((s) => s.productsTotal);
|
|
593
|
-
useInventoryStore((s) => s.productsLoading);
|
|
608
|
+
const productsLoading = useInventoryStore((s) => s.productsLoading);
|
|
594
609
|
const fetchProducts = useInventoryStore((s) => s.fetchProducts);
|
|
595
610
|
const [search, setSearch] = useState("");
|
|
596
611
|
const [typeFilter, setTypeFilter] = useState();
|
|
597
612
|
const [activeFilter, setActiveFilter] = useState();
|
|
613
|
+
const [categoryId, setCategoryId] = useState("");
|
|
614
|
+
const [categories, setCategories] = useState([]);
|
|
615
|
+
useEffect(() => {
|
|
616
|
+
let vivo = true;
|
|
617
|
+
provider.getCategories().then((rows) => {
|
|
618
|
+
if (vivo) setCategories(rows);
|
|
619
|
+
}).catch(() => {
|
|
620
|
+
});
|
|
621
|
+
return () => {
|
|
622
|
+
vivo = false;
|
|
623
|
+
};
|
|
624
|
+
}, [provider]);
|
|
598
625
|
const [page, setPage] = useState(1);
|
|
599
626
|
const [loadedOnce, setLoadedOnce] = useState(false);
|
|
600
627
|
const [bulkBusy, setBulkBusy] = useState(false);
|
|
628
|
+
const rascunho = useMemo(
|
|
629
|
+
() => ({ productType: typeFilter, categoryId, isActive: activeFilter }),
|
|
630
|
+
[typeFilter, categoryId, activeFilter]
|
|
631
|
+
);
|
|
632
|
+
const [emVigor, setEmVigor] = useState(rascunho);
|
|
633
|
+
const filtrosPendentes = useMemo(
|
|
634
|
+
() => JSON.stringify(rascunho) !== JSON.stringify(emVigor),
|
|
635
|
+
[rascunho, emVigor]
|
|
636
|
+
);
|
|
637
|
+
const aplicarFiltros = useCallback(() => {
|
|
638
|
+
setPage(1);
|
|
639
|
+
setEmVigor(() => rascunho);
|
|
640
|
+
}, [rascunho]);
|
|
641
|
+
const limparFiltros = () => {
|
|
642
|
+
setTypeFilter(void 0);
|
|
643
|
+
setCategoryId("");
|
|
644
|
+
setActiveFilter(void 0);
|
|
645
|
+
};
|
|
646
|
+
const filtrosAtivos = (typeFilter ? 1 : 0) + (categoryId ? 1 : 0) + (activeFilter !== void 0 ? 1 : 0);
|
|
601
647
|
const query = useMemo(() => ({
|
|
602
|
-
productType:
|
|
648
|
+
productType: emVigor.productType,
|
|
649
|
+
categoryId: emVigor.categoryId || void 0,
|
|
603
650
|
search: search || void 0,
|
|
604
|
-
isActive:
|
|
651
|
+
isActive: emVigor.isActive === void 0 ? void 0 : emVigor.isActive === "true",
|
|
605
652
|
page,
|
|
606
653
|
pageSize: PAGE_SIZE
|
|
607
|
-
}), [
|
|
654
|
+
}), [emVigor, search, page]);
|
|
608
655
|
useEffect(() => {
|
|
609
656
|
let active = true;
|
|
610
657
|
Promise.resolve(fetchProducts(query)).finally(() => {
|
|
@@ -615,22 +662,6 @@ function ProductListView({ onNew, onOpen }) {
|
|
|
615
662
|
};
|
|
616
663
|
}, [query]);
|
|
617
664
|
const entity = useMemo(() => buildProductEntity(t2, productTypes, currency), [t2, productTypes, currency]);
|
|
618
|
-
const facets = useMemo(() => [
|
|
619
|
-
{
|
|
620
|
-
field: "productType",
|
|
621
|
-
allLabel: t2("crud.list.allFacet"),
|
|
622
|
-
options: productTypes.map((p) => ({ value: p.value, label: p.label }))
|
|
623
|
-
},
|
|
624
|
-
{
|
|
625
|
-
field: "isActive",
|
|
626
|
-
label: t2("inventory.productList.filterStatus"),
|
|
627
|
-
allLabel: t2("inventory.productList.filterAllStatuses"),
|
|
628
|
-
options: [
|
|
629
|
-
{ value: "true", label: t2("inventory.productList.filterActive") },
|
|
630
|
-
{ value: "false", label: t2("inventory.productList.filterInactive") }
|
|
631
|
-
]
|
|
632
|
-
}
|
|
633
|
-
], [productTypes, t2]);
|
|
634
665
|
const runBulk = useCallback(async (rows, isActive) => {
|
|
635
666
|
const targets = rows.filter((p) => (p.isActive ?? true) !== isActive);
|
|
636
667
|
if (targets.length === 0) {
|
|
@@ -674,46 +705,122 @@ function ProductListView({ onNew, onOpen }) {
|
|
|
674
705
|
}
|
|
675
706
|
];
|
|
676
707
|
}, [can, bulkBusy, runBulk, t2]);
|
|
677
|
-
|
|
678
|
-
|
|
708
|
+
const filtros = /* @__PURE__ */ jsxs(
|
|
709
|
+
FilterPanel,
|
|
679
710
|
{
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
711
|
+
title: t2("inventory.filters.title"),
|
|
712
|
+
dirty: filtrosPendentes,
|
|
713
|
+
onApply: aplicarFiltros,
|
|
714
|
+
applyLabel: t2("inventory.filters.apply"),
|
|
715
|
+
pendingTitle: t2("inventory.filters.pending"),
|
|
716
|
+
pendingHint: t2("inventory.filters.pendingHint"),
|
|
717
|
+
onClear: filtrosAtivos > 0 ? limparFiltros : void 0,
|
|
718
|
+
clearLabel: t2("inventory.filters.clear"),
|
|
719
|
+
busy: productsLoading,
|
|
720
|
+
children: [
|
|
721
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.kind"), children: /* @__PURE__ */ jsx(
|
|
722
|
+
RequiredFilter,
|
|
723
|
+
{
|
|
724
|
+
label: t2("inventory.filters.kind"),
|
|
725
|
+
value: productTypes.find((p) => p.value === typeFilter)?.label ?? t2("inventory.filters.allKinds"),
|
|
726
|
+
icon: Boxes,
|
|
727
|
+
className: "h-9 w-full justify-start",
|
|
728
|
+
align: "start",
|
|
729
|
+
children: (close) => /* @__PURE__ */ jsxs("div", { className: "max-h-72 space-y-0.5 overflow-y-auto", children: [
|
|
730
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: !typeFilter, onClick: () => {
|
|
731
|
+
setTypeFilter(void 0);
|
|
732
|
+
close();
|
|
733
|
+
}, children: t2("inventory.filters.allKinds") }),
|
|
734
|
+
productTypes.map((p) => /* @__PURE__ */ jsx(FilterMenuRow, { active: typeFilter === p.value, onClick: () => {
|
|
735
|
+
setTypeFilter(p.value);
|
|
736
|
+
close();
|
|
737
|
+
}, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: p.label }) }, p.value))
|
|
738
|
+
] })
|
|
739
|
+
}
|
|
740
|
+
) }),
|
|
741
|
+
categories.length > 0 && /* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.category"), children: /* @__PURE__ */ jsx(
|
|
742
|
+
RequiredFilter,
|
|
743
|
+
{
|
|
744
|
+
label: t2("inventory.filters.category"),
|
|
745
|
+
value: categories.find((c) => c.id === categoryId)?.name ?? t2("inventory.filters.allCategories"),
|
|
746
|
+
icon: Layers,
|
|
747
|
+
className: "h-9 w-full justify-start",
|
|
748
|
+
align: "start",
|
|
749
|
+
children: (close) => /* @__PURE__ */ jsxs("div", { className: "max-h-72 space-y-0.5 overflow-y-auto", children: [
|
|
750
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: !categoryId, onClick: () => {
|
|
751
|
+
setCategoryId("");
|
|
752
|
+
close();
|
|
753
|
+
}, children: t2("inventory.filters.allCategories") }),
|
|
754
|
+
categories.map((c) => /* @__PURE__ */ jsx(FilterMenuRow, { active: categoryId === c.id, onClick: () => {
|
|
755
|
+
setCategoryId(c.id);
|
|
756
|
+
close();
|
|
757
|
+
}, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: c.name }) }, c.id))
|
|
758
|
+
] })
|
|
759
|
+
}
|
|
760
|
+
) }),
|
|
761
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.status"), children: /* @__PURE__ */ jsx(
|
|
762
|
+
RequiredFilter,
|
|
763
|
+
{
|
|
764
|
+
label: t2("inventory.filters.status"),
|
|
765
|
+
value: activeFilter === void 0 ? t2("inventory.filters.allStatuses") : activeFilter === "true" ? t2("inventory.productList.active") : t2("inventory.productList.inactive"),
|
|
766
|
+
icon: CircleCheck,
|
|
767
|
+
className: "h-9 w-full justify-start",
|
|
768
|
+
align: "start",
|
|
769
|
+
children: (close) => /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
|
|
770
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: activeFilter === void 0, onClick: () => {
|
|
771
|
+
setActiveFilter(void 0);
|
|
772
|
+
close();
|
|
773
|
+
}, children: t2("inventory.filters.allStatuses") }),
|
|
774
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: activeFilter === "true", onClick: () => {
|
|
775
|
+
setActiveFilter("true");
|
|
776
|
+
close();
|
|
777
|
+
}, children: t2("inventory.productList.active") }),
|
|
778
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: activeFilter === "false", onClick: () => {
|
|
779
|
+
setActiveFilter("false");
|
|
780
|
+
close();
|
|
781
|
+
}, children: t2("inventory.productList.inactive") })
|
|
782
|
+
] })
|
|
783
|
+
}
|
|
784
|
+
) })
|
|
785
|
+
]
|
|
715
786
|
}
|
|
716
787
|
);
|
|
788
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
789
|
+
filtros,
|
|
790
|
+
/* @__PURE__ */ jsx(
|
|
791
|
+
CrudListView,
|
|
792
|
+
{
|
|
793
|
+
entityDef: entity,
|
|
794
|
+
items: loadedOnce ? products2 : null,
|
|
795
|
+
total: productsTotal,
|
|
796
|
+
search,
|
|
797
|
+
onSearchChange: (value) => {
|
|
798
|
+
setPage(1);
|
|
799
|
+
setSearch(value);
|
|
800
|
+
},
|
|
801
|
+
searchPlaceholder: t2("inventory.productList.searchPlaceholder"),
|
|
802
|
+
page,
|
|
803
|
+
pageSize: PAGE_SIZE,
|
|
804
|
+
onPageChange: setPage,
|
|
805
|
+
bulkActions,
|
|
806
|
+
onNew,
|
|
807
|
+
aiCreate: {
|
|
808
|
+
hint: t2("inventory.productList.askPlaceholder"),
|
|
809
|
+
description: t2("inventory.productList.aiHint"),
|
|
810
|
+
intro: {
|
|
811
|
+
role: "operations",
|
|
812
|
+
persona: t2("inventory.productList.askPersona"),
|
|
813
|
+
pitch: t2("inventory.productList.askPitch"),
|
|
814
|
+
description: t2("inventory.productList.askDescription"),
|
|
815
|
+
suggestions: [0, 1, 2].map((i) => ({ label: t2(`inventory.productList.askSuggestion.${i}`) }))
|
|
816
|
+
}
|
|
817
|
+
},
|
|
818
|
+
addLabel: t2("inventory.productList.newProduct"),
|
|
819
|
+
onRowClick: (row) => onOpen?.(row.id),
|
|
820
|
+
feature: "inventory"
|
|
821
|
+
}
|
|
822
|
+
)
|
|
823
|
+
] });
|
|
717
824
|
}
|
|
718
825
|
function ProductCrudForm({ editId, onSaved }) {
|
|
719
826
|
const t2 = useTranslation();
|
|
@@ -1423,10 +1530,49 @@ function MovementHistoryView({ onViewDetail, onNew, onNewExit } = {}) {
|
|
|
1423
1530
|
const movements2 = useInventoryStore((s) => s.movements);
|
|
1424
1531
|
const movementsLoading = useInventoryStore((s) => s.movementsLoading);
|
|
1425
1532
|
const fetchMovements = useInventoryStore((s) => s.fetchMovements);
|
|
1533
|
+
const locations = useInventoryStore((s) => s.locations);
|
|
1534
|
+
const fetchLocations = useInventoryStore((s) => s.fetchLocations);
|
|
1535
|
+
useEffect(() => {
|
|
1536
|
+
void fetchLocations();
|
|
1537
|
+
}, [fetchLocations]);
|
|
1426
1538
|
const [search, setSearch] = useState("");
|
|
1539
|
+
const hoje = () => {
|
|
1540
|
+
const d = /* @__PURE__ */ new Date();
|
|
1541
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
1542
|
+
};
|
|
1543
|
+
const trintaDiasAtras = useMemo(() => {
|
|
1544
|
+
const d = /* @__PURE__ */ new Date();
|
|
1545
|
+
d.setDate(d.getDate() - 29);
|
|
1546
|
+
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
|
|
1547
|
+
}, []);
|
|
1548
|
+
const [tipos, setTipos] = useState([]);
|
|
1549
|
+
const [locationId, setLocationId] = useState("");
|
|
1550
|
+
const [dateFrom, setDateFrom] = useState(trintaDiasAtras);
|
|
1551
|
+
const [dateTo, setDateTo] = useState(hoje);
|
|
1552
|
+
const rascunho = useMemo(
|
|
1553
|
+
() => ({ tipos, locationId, from: dateFrom, to: dateTo }),
|
|
1554
|
+
[tipos, locationId, dateFrom, dateTo]
|
|
1555
|
+
);
|
|
1556
|
+
const [emVigor, setEmVigor] = useState(rascunho);
|
|
1557
|
+
const filtrosPendentes = useMemo(
|
|
1558
|
+
() => JSON.stringify(rascunho) !== JSON.stringify(emVigor),
|
|
1559
|
+
[rascunho, emVigor]
|
|
1560
|
+
);
|
|
1561
|
+
const aplicarFiltros = useCallback(() => setEmVigor(() => rascunho), [rascunho]);
|
|
1562
|
+
const limparFiltros = () => {
|
|
1563
|
+
setTipos([]);
|
|
1564
|
+
setLocationId("");
|
|
1565
|
+
setDateFrom(trintaDiasAtras);
|
|
1566
|
+
setDateTo(hoje());
|
|
1567
|
+
};
|
|
1427
1568
|
useEffect(() => {
|
|
1428
|
-
fetchMovements({
|
|
1429
|
-
|
|
1569
|
+
fetchMovements({
|
|
1570
|
+
search: search || void 0,
|
|
1571
|
+
movementType: emVigor.tipos.length > 0 ? emVigor.tipos : void 0,
|
|
1572
|
+
stockLocationId: emVigor.locationId || void 0,
|
|
1573
|
+
dateRange: { from: emVigor.from, to: emVigor.to }
|
|
1574
|
+
});
|
|
1575
|
+
}, [search, emVigor]);
|
|
1430
1576
|
const columns = useMemo(() => [
|
|
1431
1577
|
{
|
|
1432
1578
|
accessorKey: "movementDate",
|
|
@@ -1514,6 +1660,69 @@ function MovementHistoryView({ onViewDetail, onNew, onNewExit } = {}) {
|
|
|
1514
1660
|
], [currency, t2]);
|
|
1515
1661
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1516
1662
|
/* @__PURE__ */ jsx(SubpageHeader, { title: t2("inventory.history.title"), subtitle: t2("inventory.history.subtitle") }),
|
|
1663
|
+
/* @__PURE__ */ jsxs(
|
|
1664
|
+
FilterPanel,
|
|
1665
|
+
{
|
|
1666
|
+
title: t2("inventory.filters.title"),
|
|
1667
|
+
dirty: filtrosPendentes,
|
|
1668
|
+
onApply: aplicarFiltros,
|
|
1669
|
+
applyLabel: t2("inventory.filters.apply"),
|
|
1670
|
+
pendingTitle: t2("inventory.filters.pending"),
|
|
1671
|
+
pendingHint: t2("inventory.filters.pendingHint"),
|
|
1672
|
+
onClear: tipos.length > 0 || locationId ? limparFiltros : void 0,
|
|
1673
|
+
clearLabel: t2("inventory.filters.clear"),
|
|
1674
|
+
busy: movementsLoading,
|
|
1675
|
+
children: [
|
|
1676
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.history.columnType"), children: /* @__PURE__ */ jsx(
|
|
1677
|
+
RequiredFilter,
|
|
1678
|
+
{
|
|
1679
|
+
label: t2("inventory.history.columnType"),
|
|
1680
|
+
value: tipos.length === 0 ? t2("inventory.filters.allTypes") : tipos.length === 1 ? movementTypeLabel(tipos[0], t2) : t2("crud.list.selectedMany", { count: tipos.length }),
|
|
1681
|
+
icon: CircleDashed,
|
|
1682
|
+
className: "h-9 w-full justify-start",
|
|
1683
|
+
align: "start",
|
|
1684
|
+
children: /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
|
|
1685
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: tipos.length === 0, onClick: () => setTipos([]), children: t2("inventory.filters.allTypes") }),
|
|
1686
|
+
MOVEMENT_TYPES.map((spec) => {
|
|
1687
|
+
const on = tipos.includes(spec.value);
|
|
1688
|
+
return /* @__PURE__ */ jsx(
|
|
1689
|
+
FilterMenuRow,
|
|
1690
|
+
{
|
|
1691
|
+
active: on,
|
|
1692
|
+
onClick: () => setTipos(on ? tipos.filter((v) => v !== spec.value) : [...tipos, spec.value]),
|
|
1693
|
+
children: /* @__PURE__ */ jsx("span", { className: "truncate", children: t2(spec.labelKey) })
|
|
1694
|
+
},
|
|
1695
|
+
spec.value
|
|
1696
|
+
);
|
|
1697
|
+
})
|
|
1698
|
+
] })
|
|
1699
|
+
}
|
|
1700
|
+
) }),
|
|
1701
|
+
locations.length > 0 && /* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.location"), children: /* @__PURE__ */ jsx(
|
|
1702
|
+
RequiredFilter,
|
|
1703
|
+
{
|
|
1704
|
+
label: t2("inventory.filters.location"),
|
|
1705
|
+
value: locations.find((l) => l.id === locationId)?.name ?? t2("inventory.filters.allLocations"),
|
|
1706
|
+
icon: MapPin,
|
|
1707
|
+
className: "h-9 w-full justify-start",
|
|
1708
|
+
align: "start",
|
|
1709
|
+
children: (close) => /* @__PURE__ */ jsxs("div", { className: "max-h-72 space-y-0.5 overflow-y-auto", children: [
|
|
1710
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: !locationId, onClick: () => {
|
|
1711
|
+
setLocationId("");
|
|
1712
|
+
close();
|
|
1713
|
+
}, children: t2("inventory.filters.allLocations") }),
|
|
1714
|
+
locations.map((l) => /* @__PURE__ */ jsx(FilterMenuRow, { active: locationId === l.id, onClick: () => {
|
|
1715
|
+
setLocationId(l.id);
|
|
1716
|
+
close();
|
|
1717
|
+
}, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: l.name }) }, l.id))
|
|
1718
|
+
] })
|
|
1719
|
+
}
|
|
1720
|
+
) }),
|
|
1721
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.from"), children: /* @__PURE__ */ jsx(DatePicker, { variant: "input", format: "numeric", className: "w-full", value: dateFrom, onChange: (v) => v && setDateFrom(v) }) }),
|
|
1722
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.to"), children: /* @__PURE__ */ jsx(DatePicker, { variant: "input", format: "numeric", className: "w-full", value: dateTo, onChange: (v) => v && setDateTo(v) }) })
|
|
1723
|
+
]
|
|
1724
|
+
}
|
|
1725
|
+
),
|
|
1517
1726
|
/* @__PURE__ */ jsx(
|
|
1518
1727
|
ListView,
|
|
1519
1728
|
{
|
|
@@ -1538,6 +1747,290 @@ function MovementHistoryView({ onViewDetail, onNew, onNewExit } = {}) {
|
|
|
1538
1747
|
)
|
|
1539
1748
|
] });
|
|
1540
1749
|
}
|
|
1750
|
+
var HEALTH_ORDER = ["negative", "below_min", "zero", "ok"];
|
|
1751
|
+
var HEALTH_TONE = {
|
|
1752
|
+
negative: "bg-destructive-soft text-destructive-soft-foreground",
|
|
1753
|
+
below_min: "bg-warning-soft text-warning-soft-foreground",
|
|
1754
|
+
zero: "bg-muted text-muted-foreground",
|
|
1755
|
+
ok: "bg-success-soft text-success-soft-foreground"
|
|
1756
|
+
};
|
|
1757
|
+
var EXPIRY_OPTIONS = ["all", "expired", "le30", "le60", "le90", "none"];
|
|
1758
|
+
function StockPanelView() {
|
|
1759
|
+
const t2 = useTranslation();
|
|
1760
|
+
const provider = useInventoryProvider();
|
|
1761
|
+
const { currency } = useInventoryConfig();
|
|
1762
|
+
const locations = useInventoryStore((s) => s.locations);
|
|
1763
|
+
const fetchLocations = useInventoryStore((s) => s.fetchLocations);
|
|
1764
|
+
const [categories, setCategories] = useState([]);
|
|
1765
|
+
const [rows, setRows] = useState([]);
|
|
1766
|
+
const [loading, setLoading] = useState(false);
|
|
1767
|
+
const [suportado, setSuportado] = useState(true);
|
|
1768
|
+
useEffect(() => {
|
|
1769
|
+
void fetchLocations();
|
|
1770
|
+
}, [fetchLocations]);
|
|
1771
|
+
useEffect(() => {
|
|
1772
|
+
let vivo = true;
|
|
1773
|
+
provider.getCategories().then((c) => {
|
|
1774
|
+
if (vivo) setCategories(c);
|
|
1775
|
+
}).catch(() => {
|
|
1776
|
+
});
|
|
1777
|
+
return () => {
|
|
1778
|
+
vivo = false;
|
|
1779
|
+
};
|
|
1780
|
+
}, [provider]);
|
|
1781
|
+
const [locationId, setLocationId] = useState("");
|
|
1782
|
+
const [categoryId, setCategoryId] = useState("");
|
|
1783
|
+
const [produto, setProduto] = useState("");
|
|
1784
|
+
const [lote, setLote] = useState("");
|
|
1785
|
+
const [validade, setValidade] = useState("all");
|
|
1786
|
+
const [saude, setSaude] = useState([]);
|
|
1787
|
+
const rascunho = useMemo(
|
|
1788
|
+
() => ({ locationId, categoryId, produto: produto.trim(), lote: lote.trim(), validade, saude }),
|
|
1789
|
+
[locationId, categoryId, produto, lote, validade, saude]
|
|
1790
|
+
);
|
|
1791
|
+
const [emVigor, setEmVigor] = useState(rascunho);
|
|
1792
|
+
const filtrosPendentes = useMemo(
|
|
1793
|
+
() => JSON.stringify(rascunho) !== JSON.stringify(emVigor),
|
|
1794
|
+
[rascunho, emVigor]
|
|
1795
|
+
);
|
|
1796
|
+
const aplicarFiltros = useCallback(() => setEmVigor(() => rascunho), [rascunho]);
|
|
1797
|
+
const limparFiltros = () => {
|
|
1798
|
+
setLocationId("");
|
|
1799
|
+
setCategoryId("");
|
|
1800
|
+
setProduto("");
|
|
1801
|
+
setLote("");
|
|
1802
|
+
setValidade("all");
|
|
1803
|
+
setSaude([]);
|
|
1804
|
+
};
|
|
1805
|
+
const filtrosAtivos = (locationId ? 1 : 0) + (categoryId ? 1 : 0) + (produto.trim() ? 1 : 0) + (lote.trim() ? 1 : 0) + (validade !== "all" ? 1 : 0) + (saude.length > 0 ? 1 : 0);
|
|
1806
|
+
useEffect(() => {
|
|
1807
|
+
if (!provider.getStockPanel) {
|
|
1808
|
+
setSuportado(false);
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
let vivo = true;
|
|
1812
|
+
setLoading(true);
|
|
1813
|
+
provider.getStockPanel({
|
|
1814
|
+
stockLocationId: emVigor.locationId || void 0,
|
|
1815
|
+
categoryId: emVigor.categoryId || void 0,
|
|
1816
|
+
productSearch: emVigor.produto || void 0,
|
|
1817
|
+
batch: emVigor.lote || void 0,
|
|
1818
|
+
expiry: emVigor.validade,
|
|
1819
|
+
status: emVigor.saude.length > 0 ? emVigor.saude : void 0
|
|
1820
|
+
}).then((data) => {
|
|
1821
|
+
if (vivo) setRows(data);
|
|
1822
|
+
}).catch(() => {
|
|
1823
|
+
if (vivo) setRows([]);
|
|
1824
|
+
}).finally(() => {
|
|
1825
|
+
if (vivo) setLoading(false);
|
|
1826
|
+
});
|
|
1827
|
+
return () => {
|
|
1828
|
+
vivo = false;
|
|
1829
|
+
};
|
|
1830
|
+
}, [provider, emVigor]);
|
|
1831
|
+
const totais = useMemo(() => ({
|
|
1832
|
+
linhas: rows.length,
|
|
1833
|
+
valor: rows.reduce((soma, r) => soma + r.totalValue, 0)
|
|
1834
|
+
}), [rows]);
|
|
1835
|
+
const columns = useMemo(() => [
|
|
1836
|
+
{
|
|
1837
|
+
accessorKey: "productName",
|
|
1838
|
+
header: t2("inventory.panel.columnProduct"),
|
|
1839
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
1840
|
+
/* @__PURE__ */ jsx("p", { className: "truncate font-medium", children: row.original.productName }),
|
|
1841
|
+
row.original.categoryName && /* @__PURE__ */ jsx("p", { className: "truncate text-[10px] text-muted-foreground", children: row.original.categoryName })
|
|
1842
|
+
] })
|
|
1843
|
+
},
|
|
1844
|
+
{
|
|
1845
|
+
accessorKey: "stockLocationName",
|
|
1846
|
+
header: t2("inventory.panel.columnLocation"),
|
|
1847
|
+
cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: getValue() ?? "\u2014" })
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
accessorKey: "batchNumber",
|
|
1851
|
+
header: t2("inventory.panel.columnBatch"),
|
|
1852
|
+
cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-muted-foreground", children: getValue() ?? "\u2014" })
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
accessorKey: "expirationDate",
|
|
1856
|
+
header: t2("inventory.panel.columnExpiry"),
|
|
1857
|
+
cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: getValue() ?? "\u2014" })
|
|
1858
|
+
},
|
|
1859
|
+
{
|
|
1860
|
+
accessorKey: "quantity",
|
|
1861
|
+
header: t2("inventory.panel.columnQuantity"),
|
|
1862
|
+
enableSorting: true,
|
|
1863
|
+
cell: ({ row }) => /* @__PURE__ */ jsxs("span", { className: "block text-right tabular-nums", children: [
|
|
1864
|
+
row.original.quantity,
|
|
1865
|
+
row.original.minQuantity > 0 && /* @__PURE__ */ jsxs("span", { className: "ml-1 text-[10px] text-muted-foreground", children: [
|
|
1866
|
+
"/ ",
|
|
1867
|
+
row.original.minQuantity
|
|
1868
|
+
] })
|
|
1869
|
+
] })
|
|
1870
|
+
},
|
|
1871
|
+
{
|
|
1872
|
+
accessorKey: "totalValue",
|
|
1873
|
+
header: t2("inventory.panel.columnValue"),
|
|
1874
|
+
enableSorting: true,
|
|
1875
|
+
cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "block text-right tabular-nums", children: formatCurrency(Number(getValue()) || 0, currency) })
|
|
1876
|
+
},
|
|
1877
|
+
{
|
|
1878
|
+
accessorKey: "health",
|
|
1879
|
+
header: t2("inventory.filters.stockState"),
|
|
1880
|
+
cell: ({ getValue }) => {
|
|
1881
|
+
const h = getValue();
|
|
1882
|
+
return /* @__PURE__ */ jsx("span", { className: `inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${HEALTH_TONE[h]}`, children: t2(`inventory.filters.health.${h}`) });
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
], [currency, t2]);
|
|
1886
|
+
if (!suportado) {
|
|
1887
|
+
return /* @__PURE__ */ jsxs(Card, { className: "py-16 text-center", children: [
|
|
1888
|
+
/* @__PURE__ */ jsx(Boxes, { className: "mx-auto mb-2 h-8 w-8 text-muted-foreground/30" }),
|
|
1889
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t2("inventory.panel.unsupported") })
|
|
1890
|
+
] });
|
|
1891
|
+
}
|
|
1892
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
1893
|
+
/* @__PURE__ */ jsxs(
|
|
1894
|
+
FilterPanel,
|
|
1895
|
+
{
|
|
1896
|
+
title: t2("inventory.filters.title"),
|
|
1897
|
+
dirty: filtrosPendentes,
|
|
1898
|
+
onApply: aplicarFiltros,
|
|
1899
|
+
applyLabel: t2("inventory.filters.apply"),
|
|
1900
|
+
pendingTitle: t2("inventory.filters.pending"),
|
|
1901
|
+
pendingHint: t2("inventory.filters.pendingHint"),
|
|
1902
|
+
onClear: filtrosAtivos > 0 ? limparFiltros : void 0,
|
|
1903
|
+
clearLabel: t2("inventory.filters.clear"),
|
|
1904
|
+
busy: loading,
|
|
1905
|
+
moreLabel: t2("inventory.filters.more"),
|
|
1906
|
+
lessLabel: t2("inventory.filters.less"),
|
|
1907
|
+
advancedCount: (lote.trim() ? 1 : 0) + (validade !== "all" ? 1 : 0),
|
|
1908
|
+
advanced: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1909
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.panel.columnBatch"), children: /* @__PURE__ */ jsx(
|
|
1910
|
+
Input,
|
|
1911
|
+
{
|
|
1912
|
+
value: lote,
|
|
1913
|
+
onChange: (e) => setLote(e.target.value),
|
|
1914
|
+
onKeyDown: (e) => {
|
|
1915
|
+
if (e.key === "Enter") aplicarFiltros();
|
|
1916
|
+
},
|
|
1917
|
+
placeholder: t2("inventory.filters.batchPlaceholder"),
|
|
1918
|
+
className: "h-9"
|
|
1919
|
+
}
|
|
1920
|
+
) }),
|
|
1921
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.expiry"), children: /* @__PURE__ */ jsx(
|
|
1922
|
+
RequiredFilter,
|
|
1923
|
+
{
|
|
1924
|
+
label: t2("inventory.filters.expiry"),
|
|
1925
|
+
value: t2(`inventory.filters.expiry.${validade}`),
|
|
1926
|
+
icon: CircleDashed,
|
|
1927
|
+
className: "h-9 w-full justify-start",
|
|
1928
|
+
align: "start",
|
|
1929
|
+
children: (close) => /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: EXPIRY_OPTIONS.map((op) => /* @__PURE__ */ jsx(FilterMenuRow, { active: validade === op, onClick: () => {
|
|
1930
|
+
setValidade(op);
|
|
1931
|
+
close();
|
|
1932
|
+
}, children: t2(`inventory.filters.expiry.${op}`) }, op)) })
|
|
1933
|
+
}
|
|
1934
|
+
) })
|
|
1935
|
+
] }),
|
|
1936
|
+
children: [
|
|
1937
|
+
locations.length > 0 && /* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.location"), children: /* @__PURE__ */ jsx(
|
|
1938
|
+
RequiredFilter,
|
|
1939
|
+
{
|
|
1940
|
+
label: t2("inventory.filters.location"),
|
|
1941
|
+
value: locations.find((l) => l.id === locationId)?.name ?? t2("inventory.filters.allLocations"),
|
|
1942
|
+
icon: MapPin,
|
|
1943
|
+
className: "h-9 w-full justify-start",
|
|
1944
|
+
align: "start",
|
|
1945
|
+
children: (close) => /* @__PURE__ */ jsxs("div", { className: "max-h-72 space-y-0.5 overflow-y-auto", children: [
|
|
1946
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: !locationId, onClick: () => {
|
|
1947
|
+
setLocationId("");
|
|
1948
|
+
close();
|
|
1949
|
+
}, children: t2("inventory.filters.allLocations") }),
|
|
1950
|
+
locations.map((l) => /* @__PURE__ */ jsx(FilterMenuRow, { active: locationId === l.id, onClick: () => {
|
|
1951
|
+
setLocationId(l.id);
|
|
1952
|
+
close();
|
|
1953
|
+
}, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: l.name }) }, l.id))
|
|
1954
|
+
] })
|
|
1955
|
+
}
|
|
1956
|
+
) }),
|
|
1957
|
+
categories.length > 0 && /* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.category"), children: /* @__PURE__ */ jsx(
|
|
1958
|
+
RequiredFilter,
|
|
1959
|
+
{
|
|
1960
|
+
label: t2("inventory.filters.category"),
|
|
1961
|
+
value: categories.find((c) => c.id === categoryId)?.name ?? t2("inventory.filters.allCategories"),
|
|
1962
|
+
icon: Layers,
|
|
1963
|
+
className: "h-9 w-full justify-start",
|
|
1964
|
+
align: "start",
|
|
1965
|
+
children: (close) => /* @__PURE__ */ jsxs("div", { className: "max-h-72 space-y-0.5 overflow-y-auto", children: [
|
|
1966
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: !categoryId, onClick: () => {
|
|
1967
|
+
setCategoryId("");
|
|
1968
|
+
close();
|
|
1969
|
+
}, children: t2("inventory.filters.allCategories") }),
|
|
1970
|
+
categories.map((c) => /* @__PURE__ */ jsx(FilterMenuRow, { active: categoryId === c.id, onClick: () => {
|
|
1971
|
+
setCategoryId(c.id);
|
|
1972
|
+
close();
|
|
1973
|
+
}, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: c.name }) }, c.id))
|
|
1974
|
+
] })
|
|
1975
|
+
}
|
|
1976
|
+
) }),
|
|
1977
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.stockState"), className: "sm:col-span-2", children: /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1.5", children: HEALTH_ORDER.map((h) => {
|
|
1978
|
+
const on = saude.includes(h);
|
|
1979
|
+
return /* @__PURE__ */ jsx(
|
|
1980
|
+
"button",
|
|
1981
|
+
{
|
|
1982
|
+
type: "button",
|
|
1983
|
+
"aria-pressed": on,
|
|
1984
|
+
onClick: () => setSaude(on ? saude.filter((v) => v !== h) : [...saude, h]),
|
|
1985
|
+
className: `rounded-full border px-2.5 py-1 text-xs transition-colors ${on ? "border-primary bg-primary/10 text-primary" : "border-input bg-muted/40 text-muted-foreground hover:bg-muted"}`,
|
|
1986
|
+
children: t2(`inventory.filters.health.${h}`)
|
|
1987
|
+
},
|
|
1988
|
+
h
|
|
1989
|
+
);
|
|
1990
|
+
}) }) })
|
|
1991
|
+
]
|
|
1992
|
+
}
|
|
1993
|
+
),
|
|
1994
|
+
/* @__PURE__ */ jsxs(Card, { className: "relative px-3 py-1.5", children: [
|
|
1995
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
|
|
1996
|
+
/* @__PURE__ */ jsx(
|
|
1997
|
+
"input",
|
|
1998
|
+
{
|
|
1999
|
+
type: "text",
|
|
2000
|
+
value: produto,
|
|
2001
|
+
onChange: (e) => setProduto(e.target.value),
|
|
2002
|
+
onKeyDown: (e) => {
|
|
2003
|
+
if (e.key === "Enter") aplicarFiltros();
|
|
2004
|
+
},
|
|
2005
|
+
placeholder: t2("inventory.filters.productPlaceholder"),
|
|
2006
|
+
"aria-label": t2("inventory.filters.productPlaceholder"),
|
|
2007
|
+
className: "w-full bg-transparent py-1.5 pl-8 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none"
|
|
2008
|
+
}
|
|
2009
|
+
)
|
|
2010
|
+
] }),
|
|
2011
|
+
/* @__PURE__ */ jsx(
|
|
2012
|
+
DataTable,
|
|
2013
|
+
{
|
|
2014
|
+
columns,
|
|
2015
|
+
data: loading ? [] : rows,
|
|
2016
|
+
loading,
|
|
2017
|
+
emptyMessage: t2("inventory.panel.empty")
|
|
2018
|
+
}
|
|
2019
|
+
),
|
|
2020
|
+
rows.length > 0 && !loading && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-6 rounded-lg border bg-muted/30 px-5 py-3 text-sm", children: [
|
|
2021
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2022
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("inventory.panel.totalLines") }),
|
|
2023
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold tabular-nums", children: totais.linhas })
|
|
2024
|
+
] }),
|
|
2025
|
+
/* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-border" }),
|
|
2026
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2027
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t2("inventory.panel.totalValue") }),
|
|
2028
|
+
/* @__PURE__ */ jsx("span", { className: "font-semibold tabular-nums", children: formatCurrency(totais.valor, currency) })
|
|
2029
|
+
] })
|
|
2030
|
+
] })
|
|
2031
|
+
] });
|
|
2032
|
+
}
|
|
2033
|
+
var COUNT_STATUSES = ["open", "counting", "closed", "cancelled"];
|
|
1541
2034
|
var STATUS_TONE = {
|
|
1542
2035
|
open: "bg-muted text-muted-foreground",
|
|
1543
2036
|
counting: "bg-info-soft text-info-soft-foreground",
|
|
@@ -1594,6 +2087,35 @@ function StockCountsView({ onNew, onOpen }) {
|
|
|
1594
2087
|
const [sessions, setSessions] = useState([]);
|
|
1595
2088
|
const [loadedOnce, setLoadedOnce] = useState(false);
|
|
1596
2089
|
const [search, setSearch] = useState("");
|
|
2090
|
+
const [locationId, setLocationId] = useState("");
|
|
2091
|
+
const [categoryId, setCategoryId] = useState("");
|
|
2092
|
+
const [statusFilter, setStatusFilter] = useState([]);
|
|
2093
|
+
const [dateFrom, setDateFrom] = useState("");
|
|
2094
|
+
const [dateTo, setDateTo] = useState("");
|
|
2095
|
+
const locais = useMemo(() => {
|
|
2096
|
+
const vistos = /* @__PURE__ */ new Map();
|
|
2097
|
+
for (const s of sessions) {
|
|
2098
|
+
if (s.stockLocationId && !vistos.has(s.stockLocationId)) {
|
|
2099
|
+
vistos.set(s.stockLocationId, s.stockLocationName ?? s.stockLocationId);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
return [...vistos].map(([id, name]) => ({ id, name })).sort((a, b) => a.name.localeCompare(b.name, "pt-BR"));
|
|
2103
|
+
}, [sessions]);
|
|
2104
|
+
const categorias = useMemo(() => {
|
|
2105
|
+
const vistos = /* @__PURE__ */ new Map();
|
|
2106
|
+
for (const s of sessions) {
|
|
2107
|
+
if (s.categoryId && !vistos.has(s.categoryId)) vistos.set(s.categoryId, s.categoryName ?? s.categoryId);
|
|
2108
|
+
}
|
|
2109
|
+
return [...vistos].map(([id, name]) => ({ id, name })).sort((a, b) => a.name.localeCompare(b.name, "pt-BR"));
|
|
2110
|
+
}, [sessions]);
|
|
2111
|
+
const filtrosAtivos = (locationId ? 1 : 0) + (categoryId ? 1 : 0) + (statusFilter.length > 0 ? 1 : 0) + (dateFrom ? 1 : 0) + (dateTo ? 1 : 0);
|
|
2112
|
+
const limparFiltros = () => {
|
|
2113
|
+
setLocationId("");
|
|
2114
|
+
setCategoryId("");
|
|
2115
|
+
setStatusFilter([]);
|
|
2116
|
+
setDateFrom("");
|
|
2117
|
+
setDateTo("");
|
|
2118
|
+
};
|
|
1597
2119
|
const load = useCallback(async () => {
|
|
1598
2120
|
setSessions(await provider.getCountSessions());
|
|
1599
2121
|
setLoadedOnce(true);
|
|
@@ -1603,29 +2125,114 @@ function StockCountsView({ onNew, onOpen }) {
|
|
|
1603
2125
|
}, [load]);
|
|
1604
2126
|
const rows = useMemo(() => {
|
|
1605
2127
|
const term = search.trim().toLowerCase();
|
|
1606
|
-
return sessions.map((session) => ({
|
|
2128
|
+
return sessions.filter((session) => !locationId || session.stockLocationId === locationId).filter((session) => !categoryId || session.categoryId === categoryId).filter((session) => statusFilter.length === 0 || statusFilter.includes(session.status)).filter((session) => !dateFrom || (session.openedAt ?? "").slice(0, 10) >= dateFrom).filter((session) => !dateTo || (session.openedAt ?? "").slice(0, 10) <= dateTo).map((session) => ({
|
|
1607
2129
|
...session,
|
|
1608
2130
|
label: session.reference || `${session.stockLocationName ?? ""} \xB7 ${session.openedAt.slice(0, 10)}`.trim()
|
|
1609
2131
|
})).filter((row) => !term || row.label.toLowerCase().includes(term));
|
|
1610
|
-
}, [sessions, search]);
|
|
2132
|
+
}, [sessions, search, locationId, categoryId, statusFilter, dateFrom, dateTo]);
|
|
1611
2133
|
const entity = useMemo(() => buildCountEntity(t2), [t2]);
|
|
1612
|
-
|
|
1613
|
-
|
|
2134
|
+
const filtros = /* @__PURE__ */ jsxs(
|
|
2135
|
+
FilterPanel,
|
|
1614
2136
|
{
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
2137
|
+
title: t2("inventory.filters.title"),
|
|
2138
|
+
dirty: false,
|
|
2139
|
+
onApply: () => {
|
|
2140
|
+
},
|
|
2141
|
+
applyLabel: t2("inventory.filters.apply"),
|
|
2142
|
+
pendingTitle: t2("inventory.filters.pending"),
|
|
2143
|
+
onClear: filtrosAtivos > 0 ? limparFiltros : void 0,
|
|
2144
|
+
clearLabel: t2("inventory.filters.clear"),
|
|
2145
|
+
children: [
|
|
2146
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.status"), children: /* @__PURE__ */ jsx(
|
|
2147
|
+
RequiredFilter,
|
|
2148
|
+
{
|
|
2149
|
+
label: t2("inventory.filters.status"),
|
|
2150
|
+
value: statusFilter.length === 0 ? t2("inventory.filters.allStatuses") : statusFilter.length === 1 ? t2(`inventory.counts.status.${statusFilter[0]}`) : t2("crud.list.selectedMany", { count: statusFilter.length }),
|
|
2151
|
+
icon: CircleDashed,
|
|
2152
|
+
className: "h-9 w-full justify-start",
|
|
2153
|
+
align: "start",
|
|
2154
|
+
children: /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
|
|
2155
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: statusFilter.length === 0, onClick: () => setStatusFilter([]), children: t2("inventory.filters.allStatuses") }),
|
|
2156
|
+
COUNT_STATUSES.map((st) => {
|
|
2157
|
+
const on = statusFilter.includes(st);
|
|
2158
|
+
return /* @__PURE__ */ jsx(
|
|
2159
|
+
FilterMenuRow,
|
|
2160
|
+
{
|
|
2161
|
+
active: on,
|
|
2162
|
+
onClick: () => setStatusFilter(on ? statusFilter.filter((v) => v !== st) : [...statusFilter, st]),
|
|
2163
|
+
children: /* @__PURE__ */ jsx("span", { className: "truncate", children: t2(`inventory.counts.status.${st}`) })
|
|
2164
|
+
},
|
|
2165
|
+
st
|
|
2166
|
+
);
|
|
2167
|
+
})
|
|
2168
|
+
] })
|
|
2169
|
+
}
|
|
2170
|
+
) }),
|
|
2171
|
+
locais.length > 1 && /* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.location"), children: /* @__PURE__ */ jsx(
|
|
2172
|
+
RequiredFilter,
|
|
2173
|
+
{
|
|
2174
|
+
label: t2("inventory.filters.location"),
|
|
2175
|
+
value: locais.find((l) => l.id === locationId)?.name ?? t2("inventory.filters.allLocations"),
|
|
2176
|
+
icon: MapPin,
|
|
2177
|
+
className: "h-9 w-full justify-start",
|
|
2178
|
+
align: "start",
|
|
2179
|
+
children: (close) => /* @__PURE__ */ jsxs("div", { className: "max-h-72 space-y-0.5 overflow-y-auto", children: [
|
|
2180
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: !locationId, onClick: () => {
|
|
2181
|
+
setLocationId("");
|
|
2182
|
+
close();
|
|
2183
|
+
}, children: t2("inventory.filters.allLocations") }),
|
|
2184
|
+
locais.map((l) => /* @__PURE__ */ jsx(FilterMenuRow, { active: locationId === l.id, onClick: () => {
|
|
2185
|
+
setLocationId(l.id);
|
|
2186
|
+
close();
|
|
2187
|
+
}, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: l.name }) }, l.id))
|
|
2188
|
+
] })
|
|
2189
|
+
}
|
|
2190
|
+
) }),
|
|
2191
|
+
categorias.length > 1 && /* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.category"), children: /* @__PURE__ */ jsx(
|
|
2192
|
+
RequiredFilter,
|
|
2193
|
+
{
|
|
2194
|
+
label: t2("inventory.filters.category"),
|
|
2195
|
+
value: categorias.find((c) => c.id === categoryId)?.name ?? t2("inventory.filters.allCategories"),
|
|
2196
|
+
icon: Layers,
|
|
2197
|
+
className: "h-9 w-full justify-start",
|
|
2198
|
+
align: "start",
|
|
2199
|
+
children: (close) => /* @__PURE__ */ jsxs("div", { className: "max-h-72 space-y-0.5 overflow-y-auto", children: [
|
|
2200
|
+
/* @__PURE__ */ jsx(FilterMenuRow, { active: !categoryId, onClick: () => {
|
|
2201
|
+
setCategoryId("");
|
|
2202
|
+
close();
|
|
2203
|
+
}, children: t2("inventory.filters.allCategories") }),
|
|
2204
|
+
categorias.map((c) => /* @__PURE__ */ jsx(FilterMenuRow, { active: categoryId === c.id, onClick: () => {
|
|
2205
|
+
setCategoryId(c.id);
|
|
2206
|
+
close();
|
|
2207
|
+
}, children: /* @__PURE__ */ jsx("span", { className: "truncate", children: c.name }) }, c.id))
|
|
2208
|
+
] })
|
|
2209
|
+
}
|
|
2210
|
+
) }),
|
|
2211
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.from"), children: /* @__PURE__ */ jsx(DatePicker, { variant: "input", format: "numeric", className: "w-full", value: dateFrom, onChange: (v) => setDateFrom(v ?? "") }) }),
|
|
2212
|
+
/* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.to"), children: /* @__PURE__ */ jsx(DatePicker, { variant: "input", format: "numeric", className: "w-full", value: dateTo, onChange: (v) => setDateTo(v ?? "") }) })
|
|
2213
|
+
]
|
|
1627
2214
|
}
|
|
1628
2215
|
);
|
|
2216
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
2217
|
+
filtros,
|
|
2218
|
+
/* @__PURE__ */ jsx(
|
|
2219
|
+
CrudListView,
|
|
2220
|
+
{
|
|
2221
|
+
entityDef: entity,
|
|
2222
|
+
items: loadedOnce ? rows : null,
|
|
2223
|
+
total: rows.length,
|
|
2224
|
+
search,
|
|
2225
|
+
onSearchChange: setSearch,
|
|
2226
|
+
searchPlaceholder: t2("inventory.counts.searchPlaceholder"),
|
|
2227
|
+
onNew,
|
|
2228
|
+
addLabel: t2("inventory.counts.newCount"),
|
|
2229
|
+
onRowClick: (row) => onOpen?.(row.id),
|
|
2230
|
+
emptyMessage: t2("inventory.counts.empty"),
|
|
2231
|
+
onRefresh: () => load(),
|
|
2232
|
+
feature: "inventory"
|
|
2233
|
+
}
|
|
2234
|
+
)
|
|
2235
|
+
] });
|
|
1629
2236
|
}
|
|
1630
2237
|
var FIELD = "w-full mt-1 rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring";
|
|
1631
2238
|
function StockCountFormView({ onOpened, onCancel }) {
|
|
@@ -1800,6 +2407,17 @@ function StockCountSessionView({ sessionId, onBack }) {
|
|
|
1800
2407
|
void load();
|
|
1801
2408
|
}, [load]);
|
|
1802
2409
|
const summary = useMemo(() => summarizeCount(items), [items]);
|
|
2410
|
+
const [situacao, setSituacao] = useState("all");
|
|
2411
|
+
const [buscaItem, setBuscaItem] = useState("");
|
|
2412
|
+
const itensVisiveis = useMemo(() => {
|
|
2413
|
+
const termo = buscaItem.trim().toLocaleLowerCase("pt-BR");
|
|
2414
|
+
return items.filter((item) => {
|
|
2415
|
+
if (situacao === "pending") return item.countedQuantity === void 0;
|
|
2416
|
+
if (situacao === "counted") return item.countedQuantity !== void 0;
|
|
2417
|
+
if (situacao === "divergent") return lineVariance(item) !== 0;
|
|
2418
|
+
return true;
|
|
2419
|
+
}).filter((item) => !termo || (item.productName ?? "").toLocaleLowerCase("pt-BR").includes(termo) || (item.productSku ?? "").toLocaleLowerCase("pt-BR").includes(termo));
|
|
2420
|
+
}, [items, situacao, buscaItem]);
|
|
1803
2421
|
const closed = session?.status === "closed" || session?.status === "cancelled";
|
|
1804
2422
|
const hideVariance = (session?.blind ?? false) && !closed;
|
|
1805
2423
|
async function commit(item, raw) {
|
|
@@ -1911,8 +2529,49 @@ function StockCountSessionView({ sessionId, onBack }) {
|
|
|
1911
2529
|
/* @__PURE__ */ jsx(Button, { size: "sm", onClick: requestClose, children: t2("inventory.counts.close") })
|
|
1912
2530
|
] })
|
|
1913
2531
|
] }),
|
|
2532
|
+
/* @__PURE__ */ jsx(
|
|
2533
|
+
FilterPanel,
|
|
2534
|
+
{
|
|
2535
|
+
title: t2("inventory.filters.title"),
|
|
2536
|
+
dirty: false,
|
|
2537
|
+
onApply: () => {
|
|
2538
|
+
},
|
|
2539
|
+
applyLabel: t2("inventory.filters.apply"),
|
|
2540
|
+
pendingTitle: t2("inventory.filters.pending"),
|
|
2541
|
+
onClear: situacao !== "all" ? () => setSituacao("all") : void 0,
|
|
2542
|
+
clearLabel: t2("inventory.filters.clear"),
|
|
2543
|
+
children: /* @__PURE__ */ jsx(FilterPanelField, { label: t2("inventory.filters.countState"), className: "sm:col-span-2", children: /* @__PURE__ */ jsx(
|
|
2544
|
+
SegmentedControl,
|
|
2545
|
+
{
|
|
2546
|
+
"aria-label": t2("inventory.filters.countState"),
|
|
2547
|
+
value: situacao,
|
|
2548
|
+
onChange: (v) => setSituacao(v),
|
|
2549
|
+
options: [
|
|
2550
|
+
{ value: "all", label: t2("inventory.filters.countAll") },
|
|
2551
|
+
{ value: "pending", label: t2("inventory.filters.countPending") },
|
|
2552
|
+
{ value: "counted", label: t2("inventory.filters.countDone") },
|
|
2553
|
+
{ value: "divergent", label: t2("inventory.counts.divergent") }
|
|
2554
|
+
]
|
|
2555
|
+
}
|
|
2556
|
+
) })
|
|
2557
|
+
}
|
|
2558
|
+
),
|
|
2559
|
+
/* @__PURE__ */ jsxs(Card, { className: "relative px-3 py-1.5", children: [
|
|
2560
|
+
/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground" }),
|
|
2561
|
+
/* @__PURE__ */ jsx(
|
|
2562
|
+
"input",
|
|
2563
|
+
{
|
|
2564
|
+
type: "text",
|
|
2565
|
+
value: buscaItem,
|
|
2566
|
+
onChange: (e) => setBuscaItem(e.target.value),
|
|
2567
|
+
placeholder: t2("inventory.filters.itemSearchPlaceholder"),
|
|
2568
|
+
"aria-label": t2("inventory.filters.itemSearchPlaceholder"),
|
|
2569
|
+
className: "w-full bg-transparent py-1.5 pl-8 pr-3 text-sm placeholder:text-muted-foreground focus:outline-none"
|
|
2570
|
+
}
|
|
2571
|
+
)
|
|
2572
|
+
] }),
|
|
1914
2573
|
/* @__PURE__ */ jsxs("div", { className: "rounded-card border bg-card shadow-sm divide-y", children: [
|
|
1915
|
-
|
|
2574
|
+
itensVisiveis.map((item, index) => {
|
|
1916
2575
|
const variance = lineVariance(item);
|
|
1917
2576
|
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
|
|
1918
2577
|
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
@@ -1947,7 +2606,7 @@ function StockCountSessionView({ sessionId, onBack }) {
|
|
|
1947
2606
|
)
|
|
1948
2607
|
] }, item.id);
|
|
1949
2608
|
}),
|
|
1950
|
-
|
|
2609
|
+
itensVisiveis.length === 0 && /* @__PURE__ */ jsx("p", { className: "px-4 py-8 text-center text-xs text-muted-foreground", children: t2("inventory.counts.noItems") })
|
|
1951
2610
|
] }),
|
|
1952
2611
|
/* @__PURE__ */ jsx(
|
|
1953
2612
|
ConfirmDialog,
|
|
@@ -3372,6 +4031,102 @@ function UsageAuditView() {
|
|
|
3372
4031
|
)
|
|
3373
4032
|
] });
|
|
3374
4033
|
}
|
|
4034
|
+
function TransfersView() {
|
|
4035
|
+
return /* @__PURE__ */ jsx(
|
|
4036
|
+
ComingSoon,
|
|
4037
|
+
{
|
|
4038
|
+
title: "Transfer\xEAncias",
|
|
4039
|
+
summary: "Mover saldo de um local de estoque para outro \u2014 da c\xE2mara fria para o sal\xE3o, do dep\xF3sito para a cozinha \u2014 sem que isso vire entrada nem sa\xEDda.",
|
|
4040
|
+
v1Path: "Estoque \u2192 Transfer\xEAncias",
|
|
4041
|
+
measured: "7 transfer\xEAncias no V1 inteiro, nenhuma da Artorius. Eles t\xEAm 5 locais de estoque e movimentam tudo por entrada de nota.",
|
|
4042
|
+
missing: [
|
|
4043
|
+
"A transfer\xEAncia \xE9 DOIS movimentos ligados, n\xE3o um: sai de um local e entra no outro pelo mesmo custo",
|
|
4044
|
+
"O movimento de estoque do V2 j\xE1 tem `destination_location_id` \u2014 falta a tela e a regra de aprova\xE7\xE3o",
|
|
4045
|
+
"O V1 tem aprovador de transfer\xEAncia (stock_transfer_approvers); decidir se isso vem junto"
|
|
4046
|
+
]
|
|
4047
|
+
}
|
|
4048
|
+
);
|
|
4049
|
+
}
|
|
4050
|
+
function RequisitionsView() {
|
|
4051
|
+
return /* @__PURE__ */ jsx(
|
|
4052
|
+
ComingSoon,
|
|
4053
|
+
{
|
|
4054
|
+
title: "Requisi\xE7\xF5es",
|
|
4055
|
+
summary: "O pedido interno: a cozinha requisita do dep\xF3sito, algu\xE9m separa, e a baixa acontece quando sai \u2014 n\xE3o quando foi pedido.",
|
|
4056
|
+
v1Path: "Estoque \u2192 Requisi\xE7\xF5es",
|
|
4057
|
+
measured: "ZERO requisi\xE7\xF5es no V1 inteiro, em nenhum cliente. A tela existe l\xE1 e nunca foi usada.",
|
|
4058
|
+
missing: [
|
|
4059
|
+
"Antes de construir: confirmar que algu\xE9m quer. Nenhum cliente do V1 usou isto em nenhum momento",
|
|
4060
|
+
"O V1 tem sete tabelas para o assunto (requisi\xE7\xE3o, item, lote, modelo, evento, ajuste, idempot\xEAncia) e nenhuma linha",
|
|
4061
|
+
"Se vier, \xE9 fluxo de pedido\u2192separa\xE7\xE3o\u2192baixa, com o saldo reservado no meio"
|
|
4062
|
+
]
|
|
4063
|
+
}
|
|
4064
|
+
);
|
|
4065
|
+
}
|
|
4066
|
+
function StockLocationsView() {
|
|
4067
|
+
return /* @__PURE__ */ jsx(
|
|
4068
|
+
ComingSoon,
|
|
4069
|
+
{
|
|
4070
|
+
title: "Locais de Estoque",
|
|
4071
|
+
summary: "Onde a mercadoria fica: dep\xF3sito, c\xE2mara congelada, sal\xE3o. Cada movimento acontece em um local, e o saldo \xE9 por local.",
|
|
4072
|
+
v1Path: "Estoque \u2192 Locais de Estoque",
|
|
4073
|
+
measured: "A Artorius tem 5 locais no V1 e 6 j\xE1 migrados para c\xE1 (Estoque principal, C\xC2MARA CONGELADA\u2026). O dado atravessou; falta a tela para mexer nele.",
|
|
4074
|
+
missing: [
|
|
4075
|
+
"A tabela `plg_inventory_stock_locations` j\xE1 existe e j\xE1 recebeu os locais \u2014 \xE9 CRUD, n\xE3o porte de regra",
|
|
4076
|
+
"A concilia\xE7\xE3o de nota j\xE1 escolhe o local; hoje quem cria um novo tem de pedir a algu\xE9m",
|
|
4077
|
+
"Ligar local \xE0 unidade, que \xE9 o que a RLS usa para decidir quem v\xEA qual saldo"
|
|
4078
|
+
]
|
|
4079
|
+
}
|
|
4080
|
+
);
|
|
4081
|
+
}
|
|
4082
|
+
function TemperatureView() {
|
|
4083
|
+
return /* @__PURE__ */ jsx(
|
|
4084
|
+
ComingSoon,
|
|
4085
|
+
{
|
|
4086
|
+
title: "Controle de Temperatura",
|
|
4087
|
+
summary: "O registro de temperatura dos equipamentos \u2014 c\xE2mara, freezer, balc\xE3o \u2014 com a leitura de cada turno e o alerta quando sai da faixa.",
|
|
4088
|
+
v1Path: "Estoque \u2192 Controle de Temperatura",
|
|
4089
|
+
measured: "70 pontos de controle e 138 leituras no V1, nenhum da Artorius. \xC9 a vigil\xE2ncia sanit\xE1ria de quem j\xE1 foi cobrado por ela.",
|
|
4090
|
+
missing: [
|
|
4091
|
+
"Cadastrar o ponto de controle (equipamento, faixa aceit\xE1vel, quantas leituras por dia)",
|
|
4092
|
+
"A leitura do turno, com quem leu e quando \u2014 \xE9 prova para a fiscaliza\xE7\xE3o",
|
|
4093
|
+
"O alerta quando sai da faixa; sem isso o registro vira papel passado"
|
|
4094
|
+
]
|
|
4095
|
+
}
|
|
4096
|
+
);
|
|
4097
|
+
}
|
|
4098
|
+
function StockIndicatorsView() {
|
|
4099
|
+
return /* @__PURE__ */ jsx(
|
|
4100
|
+
ComingSoon,
|
|
4101
|
+
{
|
|
4102
|
+
title: "Indicadores",
|
|
4103
|
+
summary: "Ponto de pedido, estoque m\xEDnimo e m\xE1ximo por produto e por local \u2014 e o que est\xE1 abaixo do m\xEDnimo agora.",
|
|
4104
|
+
v1Path: "Estoque \u2192 Indicadores",
|
|
4105
|
+
measured: "Nenhum par\xE2metro cadastrado no V1, em nenhum cliente (`stock_center_product_params` est\xE1 vazia). A Artorius compra por h\xE1bito, n\xE3o por ponto de pedido.",
|
|
4106
|
+
missing: [
|
|
4107
|
+
"O par\xE2metro por produto \xD7 local: m\xEDnimo, m\xE1ximo, ponto de pedido",
|
|
4108
|
+
"A lista do que est\xE1 abaixo do m\xEDnimo \u2014 o painel do Estoque j\xE1 mostra \xABestoque baixo\xBB, mas com limiar chutado",
|
|
4109
|
+
"Sugest\xE3o de compra a partir disso \xE9 o passo seguinte, e \xE9 o que liga o estoque \xE0 Entrada de Notas"
|
|
4110
|
+
]
|
|
4111
|
+
}
|
|
4112
|
+
);
|
|
4113
|
+
}
|
|
4114
|
+
function BulkProductEditView() {
|
|
4115
|
+
return /* @__PURE__ */ jsx(
|
|
4116
|
+
ComingSoon,
|
|
4117
|
+
{
|
|
4118
|
+
title: "Altera\xE7\xE3o em massa",
|
|
4119
|
+
summary: "Mudar pre\xE7o, custo, categoria ou unidade de muitos produtos de uma vez, sem abrir um por um.",
|
|
4120
|
+
v1Path: "Estoque \u2192 Produtos \u2192 Altera\xE7\xE3o em massa",
|
|
4121
|
+
measured: "A Tati editou 184 produtos no V1 em quatro dias. Um por um.",
|
|
4122
|
+
missing: [
|
|
4123
|
+
"Selecionar por filtro (categoria, tipo, fornecedor) e aplicar a mudan\xE7a ao conjunto",
|
|
4124
|
+
"Pr\xE9-visualizar o que vai mudar ANTES de gravar \u2014 altera\xE7\xE3o em massa errada \xE9 dif\xEDcil de desfazer",
|
|
4125
|
+
"Registrar quem mudou o qu\xEA: \xE9 a diferen\xE7a entre corre\xE7\xE3o e acidente"
|
|
4126
|
+
]
|
|
4127
|
+
}
|
|
4128
|
+
);
|
|
4129
|
+
}
|
|
3375
4130
|
var INVENTORY_MODULE_DEFAULTS = {
|
|
3376
4131
|
recipes: true
|
|
3377
4132
|
};
|
|
@@ -3464,54 +4219,106 @@ function InventoryOnboarding({ onComplete }) {
|
|
|
3464
4219
|
}
|
|
3465
4220
|
function buildNav(config, view, navigate, t2) {
|
|
3466
4221
|
const items = [
|
|
4222
|
+
// Tudo o que se faz COM o catálogo mora aqui: a lista, como ela se agrupa
|
|
4223
|
+
// (Categorias) e como se mexe em muitos de uma vez. A primeira filha
|
|
4224
|
+
// chama-se «Produtos» e não «Lista» porque o nome de uma tela é o que ela
|
|
4225
|
+
// mostra, e «Lista» só diz o formato.
|
|
4226
|
+
// O Resumo abre o módulo: é a única tela daqui que responde sem que se peça
|
|
4227
|
+
// nada — quanto vale o estoque, quantos itens, o que está abaixo do mínimo.
|
|
3467
4228
|
{ id: "dashboard", label: t2("inventory.nav.dashboard"), icon: "BarChart3", active: view === "dashboard", onClick: () => navigate("dashboard") },
|
|
4229
|
+
// Depois dele, o catálogo: a lista, como ela se agrupa (Categorias) e como
|
|
4230
|
+
// se mexe em muitos de uma vez.
|
|
3468
4231
|
...config.modules.products ? [{
|
|
3469
4232
|
id: "products",
|
|
3470
4233
|
label: t2("inventory.nav.products"),
|
|
3471
4234
|
icon: "Package",
|
|
3472
|
-
active: view.startsWith("products"),
|
|
4235
|
+
active: view.startsWith("products") || view === "product-categories" || view === "bulk-products",
|
|
3473
4236
|
children: [
|
|
3474
4237
|
{ id: "products-new", kind: "action", label: t2("inventory.nav.new"), active: view === "products-new", onClick: () => navigate("products-new") },
|
|
3475
|
-
{ id: "products-list", label: t2("inventory.nav.
|
|
4238
|
+
{ id: "products-list", label: t2("inventory.nav.products"), defaultChild: true, active: view === "products-list" || view.startsWith("products-detail:") || view.startsWith("products-stock:"), onClick: () => navigate("products-list") },
|
|
4239
|
+
{ id: "product-categories", label: t2("registry.product-categories"), active: view === "product-categories", onClick: () => navigate("product-categories") },
|
|
4240
|
+
{ id: "bulk-products", label: "Altera\xE7\xE3o em massa", active: view === "bulk-products", onClick: () => navigate("bulk-products") }
|
|
3476
4241
|
]
|
|
3477
4242
|
}] : [],
|
|
3478
|
-
//
|
|
3479
|
-
//
|
|
3480
|
-
//
|
|
3481
|
-
//
|
|
4243
|
+
// «Inventário» é a CONTAGEM, e não um agrupamento: é assim que a palavra é
|
|
4244
|
+
// usada em quem veio do V1 — «vou fazer o inventário» quer dizer ir à
|
|
4245
|
+
// prateleira conferir. O saldo que o sistema calcula sozinho é o Resumo, e
|
|
4246
|
+
// ele abre o módulo lá em cima.
|
|
3482
4247
|
{
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
4248
|
+
// DUAS ABAS, como no V1: o Painel diz QUANTO HÁ; as Contagens dizem o
|
|
4249
|
+
// que já foi conferido. O inventário abria direto na segunda, e a
|
|
4250
|
+
// pergunta que todo mundo faz primeiro não tinha tela.
|
|
4251
|
+
id: "inventory",
|
|
4252
|
+
label: t2("inventory.nav.inventory"),
|
|
4253
|
+
icon: "ClipboardCheck",
|
|
4254
|
+
active: view === "stock-panel" || view.startsWith("stock-counts"),
|
|
3486
4255
|
children: [
|
|
3487
|
-
{ id: "stock-
|
|
3488
|
-
{ id: "stock-
|
|
3489
|
-
{ id: "stock-history", label: t2("inventory.nav.history"), defaultChild: true, active: view === "stock-history", onClick: () => navigate("stock-history") }
|
|
4256
|
+
{ id: "stock-panel", label: t2("inventory.nav.panel"), defaultChild: true, active: view === "stock-panel", onClick: () => navigate("stock-panel") },
|
|
4257
|
+
{ id: "stock-counts", label: t2("inventory.nav.counts"), active: view.startsWith("stock-counts"), onClick: () => navigate("stock-counts") }
|
|
3490
4258
|
]
|
|
3491
4259
|
},
|
|
3492
|
-
//
|
|
4260
|
+
// MOVIMENTAÇÕES É UMA LINHA SÓ, e ela leva direto para a lista.
|
|
3493
4261
|
//
|
|
3494
|
-
//
|
|
3495
|
-
//
|
|
3496
|
-
//
|
|
3497
|
-
//
|
|
3498
|
-
// tela; a pílula é o lugar de um recorte da mesma tela.
|
|
3499
|
-
//
|
|
3500
|
-
// "Nova contagem" saiu junto: era uma AÇÃO dentro de navegação, e a lista
|
|
3501
|
-
// de contagens já tem o "+" no cabeçalho dela.
|
|
4262
|
+
// Entrada e saída eram filhas «ação» da aba: um grupo que abre para
|
|
4263
|
+
// oferecer dois formulários e o destino real no meio deles. Quem clica em
|
|
4264
|
+
// «Movimentações» quer ver o que entrou e o que saiu — lançar é o botão
|
|
4265
|
+
// que a própria lista já tem no cabeçalho, e é lá que a ação pertence.
|
|
3502
4266
|
{
|
|
3503
|
-
id: "stock-
|
|
3504
|
-
label: t2("inventory.nav.
|
|
3505
|
-
icon: "
|
|
3506
|
-
active: view === "stock-
|
|
3507
|
-
onClick: () => navigate("stock-
|
|
4267
|
+
id: "stock-history",
|
|
4268
|
+
label: t2("inventory.nav.stock"),
|
|
4269
|
+
icon: "ArrowUpCircle",
|
|
4270
|
+
active: view === "stock-history" || view === "stock-entry" || view === "stock-exit",
|
|
4271
|
+
onClick: () => navigate("stock-history")
|
|
3508
4272
|
},
|
|
4273
|
+
// Auditoria de Uso fica FORA do Inventário: ela não pergunta quanto há,
|
|
4274
|
+
// pergunta se o que saiu bate com o que foi vendido. É a única tela daqui
|
|
4275
|
+
// que olha para o consumo e não para o saldo.
|
|
4276
|
+
//
|
|
4277
|
+
// "Nova contagem" não está na navegação: era uma AÇÃO dentro de um menu, e
|
|
4278
|
+
// a lista de contagens já tem o "+" no cabeçalho dela.
|
|
3509
4279
|
{
|
|
3510
4280
|
id: "stock-usage-audit",
|
|
3511
4281
|
label: t2("inventory.nav.usageAudit"),
|
|
3512
4282
|
icon: "ScanSearch",
|
|
3513
4283
|
active: view === "stock-usage-audit",
|
|
3514
4284
|
onClick: () => navigate("stock-usage-audit")
|
|
4285
|
+
},
|
|
4286
|
+
// ── O que o Estoque do V1 tem e este ainda não ────────────────────────
|
|
4287
|
+
//
|
|
4288
|
+
// Agrupadas por PERGUNTA, não por tabela. Quem procura «onde fica a câmara
|
|
4289
|
+
// fria» e quem procura «quanto tenho de mínimo» estão fazendo coisas
|
|
4290
|
+
// diferentes, e por isso Cadastros e Reposição são duas abas.
|
|
4291
|
+
//
|
|
4292
|
+
// Produção fica de fora por decisão do founder: são seis telas e um assunto
|
|
4293
|
+
// próprio, não um buraco do estoque.
|
|
4294
|
+
// «Locais» e não «Cadastros»: cadastro é o que TODA tela desta lista faz.
|
|
4295
|
+
// O que junta estas duas é o lugar físico — onde a mercadoria está e a que
|
|
4296
|
+
// temperatura ela está lá. A alteração em massa saiu daqui: ela mexe no
|
|
4297
|
+
// catálogo, não no armazém, e agora mora com os Produtos.
|
|
4298
|
+
{
|
|
4299
|
+
id: "stock-setup",
|
|
4300
|
+
label: "Locais",
|
|
4301
|
+
icon: "Warehouse",
|
|
4302
|
+
active: ["stock-locations", "temperature"].some((v) => view === v),
|
|
4303
|
+
children: [
|
|
4304
|
+
{ id: "stock-locations", label: "Locais", defaultChild: true, active: view === "stock-locations", onClick: () => navigate("stock-locations") },
|
|
4305
|
+
{ id: "temperature", label: "Temperatura", active: view === "temperature", onClick: () => navigate("temperature") }
|
|
4306
|
+
]
|
|
4307
|
+
},
|
|
4308
|
+
{
|
|
4309
|
+
// «Reposição» e não «Planejamento»: a Produção tem um Planejamento que é
|
|
4310
|
+
// plano de FABRICAÇÃO, e dois menus com o mesmo nome a uma rubrica de
|
|
4311
|
+
// distância fazem o leitor perguntar se são a mesma tela. As três aqui
|
|
4312
|
+
// respondem à mesma pergunta — o que falta e como trazer.
|
|
4313
|
+
id: "stock-planning",
|
|
4314
|
+
label: "Reposi\xE7\xE3o",
|
|
4315
|
+
icon: "Target",
|
|
4316
|
+
active: ["stock-indicators", "transfers", "requisitions"].some((v) => view === v),
|
|
4317
|
+
children: [
|
|
4318
|
+
{ id: "stock-indicators", label: "Indicadores", defaultChild: true, active: view === "stock-indicators", onClick: () => navigate("stock-indicators") },
|
|
4319
|
+
{ id: "transfers", label: "Transfer\xEAncias", active: view === "transfers", onClick: () => navigate("transfers") },
|
|
4320
|
+
{ id: "requisitions", label: "Requisi\xE7\xF5es", active: view === "requisitions", onClick: () => navigate("requisitions") }
|
|
4321
|
+
]
|
|
3515
4322
|
}
|
|
3516
4323
|
];
|
|
3517
4324
|
if (config.modules.recipes) {
|
|
@@ -3526,6 +4333,26 @@ function buildNav(config, view, navigate, t2) {
|
|
|
3526
4333
|
]
|
|
3527
4334
|
});
|
|
3528
4335
|
}
|
|
4336
|
+
for (const extra of config.extraViews) {
|
|
4337
|
+
const own = [extra.id, ...(extra.children ?? []).map((c) => c.id)];
|
|
4338
|
+
const hits = (id) => view === id || view.startsWith(`${id}/`) || view.startsWith(`${id}:`);
|
|
4339
|
+
items.push({
|
|
4340
|
+
id: extra.id,
|
|
4341
|
+
label: extra.label,
|
|
4342
|
+
icon: extra.icon,
|
|
4343
|
+
active: own.some(hits),
|
|
4344
|
+
// Com filhos, o PAI não navega — quem navega é o filho padrão, como o grupo
|
|
4345
|
+
// «Estoque» já faz com `stock-history`. Dar `onClick` ao pai E injetá-lo
|
|
4346
|
+
// como primeiro filho deslocava a pílula em um: a tela certa aparecia com o
|
|
4347
|
+
// nome da vizinha em destaque.
|
|
4348
|
+
...extra.children?.length ? {
|
|
4349
|
+
children: [
|
|
4350
|
+
{ id: extra.id, label: "Entrada", defaultChild: true, active: hits(extra.id), onClick: () => navigate(extra.id) },
|
|
4351
|
+
...extra.children.map((c) => ({ id: c.id, label: c.label, active: hits(c.id), onClick: () => navigate(c.id) }))
|
|
4352
|
+
]
|
|
4353
|
+
} : { onClick: () => navigate(extra.id) }
|
|
4354
|
+
});
|
|
4355
|
+
}
|
|
3529
4356
|
return items;
|
|
3530
4357
|
}
|
|
3531
4358
|
function InventoryPage({ config: appConfig, provider, store, registries }) {
|
|
@@ -3638,6 +4465,7 @@ function InventoryPage({ config: appConfig, provider, store, registries }) {
|
|
|
3638
4465
|
} },
|
|
3639
4466
|
// One route id for both: `/inventory/stock/counts/<uuid>` parses back as
|
|
3640
4467
|
// `stock-counts:<id>`, not `stock-counts-detail:<id>`.
|
|
4468
|
+
{ id: "stock-panel", render: () => /* @__PURE__ */ jsx(StockPanelView, {}) },
|
|
3641
4469
|
{ id: "stock-counts", render: ({ id }) => id ? /* @__PURE__ */ jsx(StockCountSessionView, { sessionId: id, onBack: () => navigate("stock-counts") }) : /* @__PURE__ */ jsx(StockCountsView, { onNew: () => navigate("stock-counts-new"), onOpen: (sid) => navigate(`stock-counts:${sid}`) }) },
|
|
3642
4470
|
{ id: "stock-usage-audit", render: () => /* @__PURE__ */ jsx(UsageAuditView, {}) },
|
|
3643
4471
|
{ id: "stock-counts-new", render: () => /* @__PURE__ */ jsx(StockCountFormView, { onOpened: (sid) => navigate(`stock-counts:${sid}`), onCancel: () => navigate("stock-counts") }) },
|
|
@@ -3645,6 +4473,40 @@ function InventoryPage({ config: appConfig, provider, store, registries }) {
|
|
|
3645
4473
|
{ id: "recipes-list", render: () => /* @__PURE__ */ jsx(RecipesView, { onNew: () => navigate("recipes-new"), onView: (id) => navigate(`recipes-detail:${id}`), onNewForProduct: (pid) => navigate(`recipes-new:${pid}`) }) },
|
|
3646
4474
|
{ id: "recipes-new", render: ({ id }) => /* @__PURE__ */ jsx(RecipeFormView, { productId: id, onSaved: (rid) => rid ? navigate(`recipes-detail:${rid}`) : navigate("recipes-list") }) },
|
|
3647
4475
|
{ id: "recipes-detail", render: ({ id }) => /* @__PURE__ */ jsx(RecipeDetailView, { recipeId: id, onBack: () => navigate("recipes-list") }) },
|
|
4476
|
+
// Telas emprestadas por outro plugin — hoje a Entrada de Notas, que é assunto
|
|
4477
|
+
// de estoque e não linha de rail (no V1: Estoque → Notas Fiscais → Entrada).
|
|
4478
|
+
...config.extraViews.flatMap((v) => [
|
|
4479
|
+
{ id: v.id, render: () => /* @__PURE__ */ jsx(Fragment, { children: v.render() }) },
|
|
4480
|
+
...(v.children ?? []).map((c) => ({ id: c.id, render: () => /* @__PURE__ */ jsx(Fragment, { children: c.render() }) }))
|
|
4481
|
+
]),
|
|
4482
|
+
// As telas que ainda não existem. Cada uma diz o que falta e o tamanho do
|
|
4483
|
+
// buraco, medido no V1 — ver `./GapScreens`.
|
|
4484
|
+
// Categorias e Locais NÃO são «em breve»: o plugin já declara os dois como
|
|
4485
|
+
// cadastro (`data/registries`), e eles só viviam atrás de Configurações —
|
|
4486
|
+
// onde ninguém procura o lugar onde a carne fica. `RegistryCrudView` é o
|
|
4487
|
+
// mesmo CRUD que a página de ajustes desenha, agora na aba onde o assunto
|
|
4488
|
+
// está. Sem o cadastro declarado (um app que corte o registro), cai no
|
|
4489
|
+
// «em breve» de antes.
|
|
4490
|
+
//
|
|
4491
|
+
// Sem `embedded`: aqui o cadastro É a tela — a aba do módulo leva a ele e a
|
|
4492
|
+
// nada mais. `embedded` é para o cadastro montado NO MEIO de outra tela, ao
|
|
4493
|
+
// lado de outros blocos, e o que ele faz é tirar o «Adicionar» da barra do
|
|
4494
|
+
// topo e largá-lo numa fileira própria, encostado à direita do corpo. Com
|
|
4495
|
+
// isso, Locais e Categorias eram as duas únicas listas do Estoque cujo botão
|
|
4496
|
+
// de criar ficava em outro lugar que o de Produtos e o de Contagens.
|
|
4497
|
+
{ id: "product-categories", render: () => {
|
|
4498
|
+
const reg = registries?.find((r) => r.id === "product-categories");
|
|
4499
|
+
return reg ? /* @__PURE__ */ jsx(RegistryCrudView, { registry: reg, basePath: "/inventory/product-categories" }) : /* @__PURE__ */ jsx(ComingSoon, { title: "Categorias", summary: "Como o cat\xE1logo se agrupa.", v1Path: "Estoque \u2192 Produtos \u2192 Categorias", missing: ["Este aplicativo n\xE3o declarou o cadastro de categorias"] });
|
|
4500
|
+
} },
|
|
4501
|
+
{ id: "stock-locations", render: () => {
|
|
4502
|
+
const reg = registries?.find((r) => r.id === "stock-locations");
|
|
4503
|
+
return reg ? /* @__PURE__ */ jsx(RegistryCrudView, { registry: reg, basePath: "/inventory/stock-locations" }) : /* @__PURE__ */ jsx(StockLocationsView, {});
|
|
4504
|
+
} },
|
|
4505
|
+
{ id: "temperature", render: () => /* @__PURE__ */ jsx(TemperatureView, {}) },
|
|
4506
|
+
{ id: "bulk-products", render: () => /* @__PURE__ */ jsx(BulkProductEditView, {}) },
|
|
4507
|
+
{ id: "stock-indicators", render: () => /* @__PURE__ */ jsx(StockIndicatorsView, {}) },
|
|
4508
|
+
{ id: "transfers", render: () => /* @__PURE__ */ jsx(TransfersView, {}) },
|
|
4509
|
+
{ id: "requisitions", render: () => /* @__PURE__ */ jsx(RequisitionsView, {}) },
|
|
3648
4510
|
{ id: "dashboard", render: () => /* @__PURE__ */ jsx(DashboardView, {}) }
|
|
3649
4511
|
], "dashboard");
|
|
3650
4512
|
return /* @__PURE__ */ jsx(InventoryContextProvider, { config, provider, store, children: /* @__PURE__ */ jsx(
|
|
@@ -3866,6 +4728,14 @@ function createInventoryDashboardWidgets(ctx2) {
|
|
|
3866
4728
|
];
|
|
3867
4729
|
}
|
|
3868
4730
|
|
|
4731
|
+
// src/lib/stock-health.ts
|
|
4732
|
+
function stockHealth(quantity, minQuantity) {
|
|
4733
|
+
if (quantity < 0) return "negative";
|
|
4734
|
+
if (quantity === 0) return "zero";
|
|
4735
|
+
if (minQuantity > 0 && quantity < minQuantity) return "below_min";
|
|
4736
|
+
return "ok";
|
|
4737
|
+
}
|
|
4738
|
+
|
|
3869
4739
|
// src/data/mock.ts
|
|
3870
4740
|
var nextId2 = 1;
|
|
3871
4741
|
function uid() {
|
|
@@ -4119,6 +4989,53 @@ function createMockInventoryProvider() {
|
|
|
4119
4989
|
async getPositions(productId) {
|
|
4120
4990
|
return store.positions.filter((p) => p.productId === productId);
|
|
4121
4991
|
},
|
|
4992
|
+
async getStockPanel(query) {
|
|
4993
|
+
const hoje = /* @__PURE__ */ new Date();
|
|
4994
|
+
const emDias = (d) => {
|
|
4995
|
+
const x = new Date(hoje);
|
|
4996
|
+
x.setDate(x.getDate() + d);
|
|
4997
|
+
return x.toISOString().slice(0, 10);
|
|
4998
|
+
};
|
|
4999
|
+
const linhas = store.positions.map((pos) => {
|
|
5000
|
+
const produto = store.products.find((pr) => pr.id === pos.productId);
|
|
5001
|
+
const quantity = Number(pos.quantity) || 0;
|
|
5002
|
+
const minQuantity = produto?.minQuantity ?? 0;
|
|
5003
|
+
const unitCost = Number(pos.unitCost) || 0;
|
|
5004
|
+
return {
|
|
5005
|
+
id: pos.id,
|
|
5006
|
+
productId: pos.productId,
|
|
5007
|
+
productName: produto?.name ?? "\u2014",
|
|
5008
|
+
categoryId: produto?.categoryId,
|
|
5009
|
+
categoryName: produto?.categoryName,
|
|
5010
|
+
stockLocationId: pos.stockLocationId,
|
|
5011
|
+
stockLocationName: pos.stockLocationName ?? store.locations.find((l) => l.id === pos.stockLocationId)?.name,
|
|
5012
|
+
batchNumber: pos.batchNumber,
|
|
5013
|
+
expirationDate: pos.expirationDate,
|
|
5014
|
+
quantity,
|
|
5015
|
+
minQuantity,
|
|
5016
|
+
unitCost,
|
|
5017
|
+
totalValue: quantity * unitCost,
|
|
5018
|
+
health: stockHealth(quantity, minQuantity)
|
|
5019
|
+
};
|
|
5020
|
+
});
|
|
5021
|
+
const termo = query.productSearch?.trim().toLowerCase();
|
|
5022
|
+
return linhas.filter((l) => !query.stockLocationId || l.stockLocationId === query.stockLocationId).filter((l) => !query.categoryId || l.categoryId === query.categoryId).filter((l) => !termo || l.productName.toLowerCase().includes(termo)).filter((l) => !query.batch || (l.batchNumber ?? "").toLowerCase().includes(query.batch.toLowerCase())).filter((l) => {
|
|
5023
|
+
switch (query.expiry) {
|
|
5024
|
+
case "expired":
|
|
5025
|
+
return !!l.expirationDate && l.expirationDate < emDias(0);
|
|
5026
|
+
case "none":
|
|
5027
|
+
return !l.expirationDate;
|
|
5028
|
+
case "le30":
|
|
5029
|
+
return !!l.expirationDate && l.expirationDate >= emDias(0) && l.expirationDate <= emDias(30);
|
|
5030
|
+
case "le60":
|
|
5031
|
+
return !!l.expirationDate && l.expirationDate >= emDias(0) && l.expirationDate <= emDias(60);
|
|
5032
|
+
case "le90":
|
|
5033
|
+
return !!l.expirationDate && l.expirationDate >= emDias(0) && l.expirationDate <= emDias(90);
|
|
5034
|
+
default:
|
|
5035
|
+
return true;
|
|
5036
|
+
}
|
|
5037
|
+
}).filter((l) => !query.status || query.status.length === 0 || query.status.includes(l.health)).slice(0, query.limit ?? 500);
|
|
5038
|
+
},
|
|
4122
5039
|
// --- Stock Locations ---
|
|
4123
5040
|
async getLocations() {
|
|
4124
5041
|
return store.locations.filter((l) => l.isActive);
|
|
@@ -5133,6 +6050,77 @@ function createSupabaseInventoryProvider() {
|
|
|
5133
6050
|
}
|
|
5134
6051
|
return positions;
|
|
5135
6052
|
},
|
|
6053
|
+
/**
|
|
6054
|
+
* O PAINEL DE ESTOQUE — o «Painel» do inventário do V1.
|
|
6055
|
+
*
|
|
6056
|
+
* Uma linha por POSIÇÃO (produto × local × lote × validade), que é a única
|
|
6057
|
+
* granularidade em que «Locais», «Lote» e «Validade» querem dizer alguma
|
|
6058
|
+
* coisa: somado por produto, um lote vencido desaparece dentro de um saldo
|
|
6059
|
+
* saudável.
|
|
6060
|
+
*
|
|
6061
|
+
* Produto e categoria recortam ANTES, resolvendo a lista de ids: filtrar
|
|
6062
|
+
* depois de paginar devolveria páginas pela metade, e o rodapé mentiria
|
|
6063
|
+
* sobre quantas linhas existem.
|
|
6064
|
+
*/
|
|
6065
|
+
async getStockPanel(query) {
|
|
6066
|
+
const { core, pub } = getClients();
|
|
6067
|
+
const limite = query.limit ?? 500;
|
|
6068
|
+
const precisaRecortarProduto = Boolean(query.productSearch || query.categoryId);
|
|
6069
|
+
const selectProduto = query.categoryId ? `*, ${T.productDetails}!inner(*)` : PRODUCT_SELECT;
|
|
6070
|
+
let qp = soMercadoria(core.from(PRODUCTS_READ).select(selectProduto));
|
|
6071
|
+
if (query.productSearch) qp = qp.ilike("name", `%${query.productSearch}%`);
|
|
6072
|
+
if (query.categoryId) qp = qp.eq(`${T.productDetails}.category_id`, query.categoryId);
|
|
6073
|
+
const { data: produtoRows } = await qp.limit(precisaRecortarProduto ? 1e3 : 5e3);
|
|
6074
|
+
const produtos = (produtoRows ?? []).map(mapProductRow);
|
|
6075
|
+
const porProduto = new Map(produtos.map((pr) => [pr.id, pr]));
|
|
6076
|
+
if (precisaRecortarProduto && produtos.length === 0) return [];
|
|
6077
|
+
let qb = pub.from(T.stockPositions).select("*");
|
|
6078
|
+
if (precisaRecortarProduto) qb = qb.in("product_id", [...porProduto.keys()]);
|
|
6079
|
+
if (query.stockLocationId) qb = qb.eq("stock_location_id", query.stockLocationId);
|
|
6080
|
+
if (query.batch) qb = qb.ilike("batch_number", `%${query.batch}%`);
|
|
6081
|
+
const hoje = /* @__PURE__ */ new Date();
|
|
6082
|
+
const emDias = (d) => {
|
|
6083
|
+
const x = new Date(hoje);
|
|
6084
|
+
x.setDate(x.getDate() + d);
|
|
6085
|
+
return `${x.getFullYear()}-${String(x.getMonth() + 1).padStart(2, "0")}-${String(x.getDate()).padStart(2, "0")}`;
|
|
6086
|
+
};
|
|
6087
|
+
if (query.expiry === "expired") qb = qb.lt("expiration_date", emDias(0));
|
|
6088
|
+
else if (query.expiry === "none") qb = qb.is("expiration_date", null);
|
|
6089
|
+
else if (query.expiry === "le30") qb = qb.gte("expiration_date", emDias(0)).lte("expiration_date", emDias(30));
|
|
6090
|
+
else if (query.expiry === "le60") qb = qb.gte("expiration_date", emDias(0)).lte("expiration_date", emDias(60));
|
|
6091
|
+
else if (query.expiry === "le90") qb = qb.gte("expiration_date", emDias(0)).lte("expiration_date", emDias(90));
|
|
6092
|
+
const { data: posRows } = await qb.limit(limite);
|
|
6093
|
+
const locationIds = [...new Set((posRows ?? []).map((r) => r.stock_location_id).filter(Boolean))];
|
|
6094
|
+
const nomeDoLocal = /* @__PURE__ */ new Map();
|
|
6095
|
+
if (locationIds.length > 0) {
|
|
6096
|
+
const { data: locs } = await pub.from(T.stockLocations).select("id, name").in("id", locationIds);
|
|
6097
|
+
for (const l of locs ?? []) nomeDoLocal.set(l.id, l.name);
|
|
6098
|
+
}
|
|
6099
|
+
const linhas = (posRows ?? []).map((r) => {
|
|
6100
|
+
const produto = porProduto.get(r.product_id);
|
|
6101
|
+
const quantity = Number(r.quantity) || 0;
|
|
6102
|
+
const minQuantity = produto?.minQuantity ?? 0;
|
|
6103
|
+
const unitCost = Number(r.unit_cost) || 0;
|
|
6104
|
+
return {
|
|
6105
|
+
id: r.id,
|
|
6106
|
+
productId: r.product_id,
|
|
6107
|
+
productName: produto?.name ?? "\u2014",
|
|
6108
|
+
categoryId: produto?.categoryId,
|
|
6109
|
+
categoryName: produto?.categoryName,
|
|
6110
|
+
stockLocationId: r.stock_location_id ?? void 0,
|
|
6111
|
+
stockLocationName: r.stock_location_id ? nomeDoLocal.get(r.stock_location_id) : void 0,
|
|
6112
|
+
batchNumber: r.batch_number ?? void 0,
|
|
6113
|
+
expirationDate: r.expiration_date ?? void 0,
|
|
6114
|
+
quantity,
|
|
6115
|
+
minQuantity,
|
|
6116
|
+
unitCost,
|
|
6117
|
+
totalValue: quantity * unitCost,
|
|
6118
|
+
health: stockHealth(quantity, minQuantity)
|
|
6119
|
+
};
|
|
6120
|
+
});
|
|
6121
|
+
const porEstado = query.status && query.status.length > 0 ? linhas.filter((l) => query.status.includes(l.health)) : linhas;
|
|
6122
|
+
return porEstado.sort((a, b) => a.productName.localeCompare(b.productName, "pt-BR"));
|
|
6123
|
+
},
|
|
5136
6124
|
// --- Stock Locations ---
|
|
5137
6125
|
async getLocations() {
|
|
5138
6126
|
const { pub } = getClients();
|
|
@@ -5818,8 +6806,6 @@ function createInventoryStore(provider) {
|
|
|
5818
6806
|
}
|
|
5819
6807
|
}));
|
|
5820
6808
|
}
|
|
5821
|
-
|
|
5822
|
-
// src/data/registries.ts
|
|
5823
6809
|
var supplierEntity = {
|
|
5824
6810
|
name: "Supplier",
|
|
5825
6811
|
namePlural: "Suppliers",
|
|
@@ -5851,37 +6837,53 @@ var measurementUnitEntity = {
|
|
|
5851
6837
|
namePlural: "Measurement Units",
|
|
5852
6838
|
icon: "Ruler",
|
|
5853
6839
|
displayField: "name",
|
|
6840
|
+
formColumns: 2,
|
|
6841
|
+
facets: [activeFacet({ active: "Ativos", inactive: "Inativos", all: "Todos" })],
|
|
6842
|
+
fieldGroups: [{ id: "identity", label: "Identifica\xE7\xE3o", columns: 2 }, { ...RECORD_GROUP }],
|
|
5854
6843
|
defaultSort: "name",
|
|
5855
6844
|
fields: [
|
|
5856
|
-
{ key: "name", label: "Name", type: "text", required: true, showInTable: true },
|
|
5857
|
-
{ key: "abbreviation", label: "Abbreviation", type: "text", required: true, showInTable: true },
|
|
5858
|
-
{
|
|
6845
|
+
{ key: "name", label: "Name", type: "text", required: true, showInTable: true, group: "identity" },
|
|
6846
|
+
{ key: "abbreviation", label: "Abbreviation", type: "text", required: true, showInTable: true, group: "identity" },
|
|
6847
|
+
...recordRailFields({ tagsTable: T.measurementUnits, activeLabel: "Ativo", unit: false })
|
|
5859
6848
|
],
|
|
5860
6849
|
data: { table: T.measurementUnits, tenantScoped: true }
|
|
5861
6850
|
};
|
|
5862
6851
|
var productCategoryEntity = {
|
|
5863
6852
|
name: "Product Category",
|
|
5864
6853
|
namePlural: "Product Categories",
|
|
6854
|
+
// O botao de criar interpola o SUBSTANTIVO, e `name` e um literal ingles:
|
|
6855
|
+
// sem estas chaves o CrudListView omite o nome e escreve so «Adicionar»,
|
|
6856
|
+
// enquanto a lista ao lado escreve «Novo Produto». Ver EntityDef.nameKey.
|
|
6857
|
+
nameKey: "inventory.entity.productCategory",
|
|
6858
|
+
namePluralKey: "inventory.entity.productCategory.plural",
|
|
5865
6859
|
icon: "Tag",
|
|
5866
6860
|
displayField: "name",
|
|
6861
|
+
formColumns: 2,
|
|
6862
|
+
facets: [activeFacet({ active: "Ativos", inactive: "Inativos", all: "Todos" })],
|
|
6863
|
+
fieldGroups: [{ id: "identity", label: "Identifica\xE7\xE3o", columns: 2 }, { ...RECORD_GROUP }],
|
|
5867
6864
|
defaultSort: "name",
|
|
5868
6865
|
fields: [
|
|
5869
|
-
{ key: "name", label: "Name", type: "text", required: true, showInTable: true },
|
|
5870
|
-
{ key: "parentId", label: "Parent", type: "text", showInTable: false },
|
|
5871
|
-
{
|
|
6866
|
+
{ key: "name", label: "Name", type: "text", required: true, showInTable: true, group: "identity" },
|
|
6867
|
+
{ key: "parentId", label: "Parent", type: "text", showInTable: false, group: "identity" },
|
|
6868
|
+
...recordRailFields({ tagsTable: T.productCategories, activeLabel: "Ativo", unit: false })
|
|
5872
6869
|
],
|
|
5873
6870
|
data: { table: T.productCategories, tenantScoped: true }
|
|
5874
6871
|
};
|
|
5875
6872
|
var stockLocationEntity = {
|
|
5876
|
-
name: "
|
|
5877
|
-
namePlural: "
|
|
6873
|
+
name: "Local de Estoque",
|
|
6874
|
+
namePlural: "Locais de Estoque",
|
|
6875
|
+
nameKey: "inventory.entity.stockLocation",
|
|
6876
|
+
namePluralKey: "inventory.entity.stockLocation.plural",
|
|
5878
6877
|
icon: "Warehouse",
|
|
5879
6878
|
displayField: "name",
|
|
6879
|
+
formColumns: 2,
|
|
6880
|
+
facets: [activeFacet({ active: "Ativos", inactive: "Inativos", all: "Todos" })],
|
|
6881
|
+
fieldGroups: [{ id: "identity", label: "Identifica\xE7\xE3o", columns: 2 }, { ...RECORD_GROUP }],
|
|
5880
6882
|
defaultSort: "name",
|
|
5881
6883
|
fields: [
|
|
5882
|
-
{ key: "name", label: "
|
|
5883
|
-
{ key: "description", label: "
|
|
5884
|
-
{
|
|
6884
|
+
{ key: "name", label: "Nome", type: "text", required: true, showInTable: true, group: "identity" },
|
|
6885
|
+
{ key: "description", label: "Descri\xE7\xE3o", type: "textarea", showInTable: true, group: "identity" },
|
|
6886
|
+
...recordRailFields({ tagsTable: T.stockLocations, activeLabel: "Ativo", unit: true })
|
|
5885
6887
|
],
|
|
5886
6888
|
data: { table: T.stockLocations, tenantScoped: true }
|
|
5887
6889
|
};
|
|
@@ -5905,7 +6907,7 @@ var inventoryRegistries = [
|
|
|
5905
6907
|
id: "stock-locations",
|
|
5906
6908
|
entity: stockLocationEntity,
|
|
5907
6909
|
icon: "Warehouse",
|
|
5908
|
-
description: "
|
|
6910
|
+
description: "Onde a mercadoria fica guardada",
|
|
5909
6911
|
display: "table"
|
|
5910
6912
|
},
|
|
5911
6913
|
// --- Seeded defaults, still editable by the tenant ---
|
|
@@ -6211,6 +7213,54 @@ function buildInventoryOnboarding() {
|
|
|
6211
7213
|
|
|
6212
7214
|
// src/locales/en.ts
|
|
6213
7215
|
var en = {
|
|
7216
|
+
"inventory.nav.panel": "Panel",
|
|
7217
|
+
"inventory.panel.columnProduct": "Product",
|
|
7218
|
+
"inventory.panel.columnLocation": "Location",
|
|
7219
|
+
"inventory.panel.columnBatch": "Batch",
|
|
7220
|
+
"inventory.panel.columnExpiry": "Expiry",
|
|
7221
|
+
"inventory.panel.columnQuantity": "Qty / min",
|
|
7222
|
+
"inventory.panel.columnValue": "Value",
|
|
7223
|
+
"inventory.panel.empty": "No stock position for this selection.",
|
|
7224
|
+
"inventory.panel.totalLines": "Lines",
|
|
7225
|
+
"inventory.panel.totalValue": "Stock value",
|
|
7226
|
+
"inventory.panel.unsupported": "This app does not expose the stock panel.",
|
|
7227
|
+
"inventory.filters.stockState": "Stock status",
|
|
7228
|
+
"inventory.filters.health.negative": "Negative",
|
|
7229
|
+
"inventory.filters.health.below_min": "Below min",
|
|
7230
|
+
"inventory.filters.health.zero": "Zero",
|
|
7231
|
+
"inventory.filters.health.ok": "OK",
|
|
7232
|
+
"inventory.filters.expiry": "Expiry",
|
|
7233
|
+
"inventory.filters.expiry.all": "All",
|
|
7234
|
+
"inventory.filters.expiry.expired": "Expired",
|
|
7235
|
+
"inventory.filters.expiry.le30": "Expires in 30 days",
|
|
7236
|
+
"inventory.filters.expiry.le60": "Expires in 60 days",
|
|
7237
|
+
"inventory.filters.expiry.le90": "Expires in 90 days",
|
|
7238
|
+
"inventory.filters.expiry.none": "No expiry",
|
|
7239
|
+
"inventory.filters.batchPlaceholder": "Batch",
|
|
7240
|
+
"inventory.filters.productPlaceholder": "Search product...",
|
|
7241
|
+
"inventory.filters.countState": "Count state",
|
|
7242
|
+
"inventory.filters.countAll": "All",
|
|
7243
|
+
"inventory.filters.countPending": "To count",
|
|
7244
|
+
"inventory.filters.countDone": "Counted",
|
|
7245
|
+
"inventory.filters.itemSearchPlaceholder": "Search product or code...",
|
|
7246
|
+
"inventory.filters.title": "Filters",
|
|
7247
|
+
"inventory.filters.apply": "Search",
|
|
7248
|
+
"inventory.filters.pending": "Filters changed",
|
|
7249
|
+
"inventory.filters.pendingHint": "The list still shows the previous selection. Click Search to apply.",
|
|
7250
|
+
"inventory.filters.clear": "Clear",
|
|
7251
|
+
"inventory.filters.more": "More filters",
|
|
7252
|
+
"inventory.filters.less": "Fewer filters",
|
|
7253
|
+
"inventory.filters.from": "From",
|
|
7254
|
+
"inventory.filters.to": "To",
|
|
7255
|
+
"inventory.filters.allTypes": "All types",
|
|
7256
|
+
"inventory.filters.location": "Location",
|
|
7257
|
+
"inventory.filters.allLocations": "All locations",
|
|
7258
|
+
"inventory.filters.category": "Category",
|
|
7259
|
+
"inventory.filters.allCategories": "All categories",
|
|
7260
|
+
"inventory.filters.kind": "Item type",
|
|
7261
|
+
"inventory.filters.allKinds": "All",
|
|
7262
|
+
"inventory.filters.status": "Status",
|
|
7263
|
+
"inventory.filters.allStatuses": "All",
|
|
6214
7264
|
"inventory.stock.pendingRetry": "Confirmation is pending. Do not create another entry. Retry the same details before leaving this screen.",
|
|
6215
7265
|
"inventory.stock.refreshAfterCommit": "Movement recorded. Refresh the screen to see the balance.",
|
|
6216
7266
|
"inventory.dashboard.activeItems": "Active items",
|
|
@@ -6237,6 +7287,10 @@ var en = {
|
|
|
6237
7287
|
"inventory.history.newEntry": "New entry",
|
|
6238
7288
|
"inventory.history.newExit": "Exit",
|
|
6239
7289
|
"inventory.nav.recipes": "Recipes",
|
|
7290
|
+
"inventory.entity.stockLocation": "Stock Location",
|
|
7291
|
+
"inventory.entity.stockLocation.plural": "Stock Locations",
|
|
7292
|
+
"inventory.entity.productCategory": "Category",
|
|
7293
|
+
"inventory.entity.productCategory.plural": "Categories",
|
|
6240
7294
|
"registry.suppliers": "Suppliers",
|
|
6241
7295
|
"registry.suppliers.description": "Who you buy from",
|
|
6242
7296
|
"registry.product-categories": "Categories",
|
|
@@ -6248,7 +7302,7 @@ var en = {
|
|
|
6248
7302
|
"inventory.history.searchPlaceholder": "Search movements...",
|
|
6249
7303
|
"inventory.history.subtitle": "All stock movements",
|
|
6250
7304
|
"inventory.history.title": "Stock History",
|
|
6251
|
-
"inventory.nav.dashboard": "
|
|
7305
|
+
"inventory.nav.dashboard": "Summary",
|
|
6252
7306
|
"inventory.nav.entry": "Entry",
|
|
6253
7307
|
"inventory.nav.exit": "Exit",
|
|
6254
7308
|
"inventory.nav.history": "History",
|
|
@@ -6687,6 +7741,54 @@ var en = {
|
|
|
6687
7741
|
|
|
6688
7742
|
// src/locales/pt-BR.ts
|
|
6689
7743
|
var ptBR = {
|
|
7744
|
+
"inventory.nav.panel": "Painel",
|
|
7745
|
+
"inventory.panel.columnProduct": "Produto",
|
|
7746
|
+
"inventory.panel.columnLocation": "Local",
|
|
7747
|
+
"inventory.panel.columnBatch": "Lote",
|
|
7748
|
+
"inventory.panel.columnExpiry": "Validade",
|
|
7749
|
+
"inventory.panel.columnQuantity": "Qtd / m\xEDn.",
|
|
7750
|
+
"inventory.panel.columnValue": "Valor",
|
|
7751
|
+
"inventory.panel.empty": "Nenhuma posi\xE7\xE3o de estoque para este recorte.",
|
|
7752
|
+
"inventory.panel.totalLines": "Linhas",
|
|
7753
|
+
"inventory.panel.totalValue": "Valor em estoque",
|
|
7754
|
+
"inventory.panel.unsupported": "Este aplicativo n\xE3o exp\xF5e o painel de estoque.",
|
|
7755
|
+
"inventory.filters.stockState": "Status do estoque",
|
|
7756
|
+
"inventory.filters.health.negative": "Negativo",
|
|
7757
|
+
"inventory.filters.health.below_min": "Abaixo do m\xEDn.",
|
|
7758
|
+
"inventory.filters.health.zero": "Zerado",
|
|
7759
|
+
"inventory.filters.health.ok": "OK",
|
|
7760
|
+
"inventory.filters.expiry": "Validade",
|
|
7761
|
+
"inventory.filters.expiry.all": "Todas",
|
|
7762
|
+
"inventory.filters.expiry.expired": "Vencidos",
|
|
7763
|
+
"inventory.filters.expiry.le30": "Vence em 30 dias",
|
|
7764
|
+
"inventory.filters.expiry.le60": "Vence em 60 dias",
|
|
7765
|
+
"inventory.filters.expiry.le90": "Vence em 90 dias",
|
|
7766
|
+
"inventory.filters.expiry.none": "Sem validade",
|
|
7767
|
+
"inventory.filters.batchPlaceholder": "Lote",
|
|
7768
|
+
"inventory.filters.productPlaceholder": "Buscar produto...",
|
|
7769
|
+
"inventory.filters.countState": "Situa\xE7\xE3o da contagem",
|
|
7770
|
+
"inventory.filters.countAll": "Todas",
|
|
7771
|
+
"inventory.filters.countPending": "A contar",
|
|
7772
|
+
"inventory.filters.countDone": "Contadas",
|
|
7773
|
+
"inventory.filters.itemSearchPlaceholder": "Buscar produto ou c\xF3digo...",
|
|
7774
|
+
"inventory.filters.title": "Filtros",
|
|
7775
|
+
"inventory.filters.apply": "Buscar",
|
|
7776
|
+
"inventory.filters.pending": "Filtros alterados",
|
|
7777
|
+
"inventory.filters.pendingHint": "A lista ainda mostra o recorte anterior. Clique em Buscar para aplicar.",
|
|
7778
|
+
"inventory.filters.clear": "Limpar",
|
|
7779
|
+
"inventory.filters.more": "Mais filtros",
|
|
7780
|
+
"inventory.filters.less": "Menos filtros",
|
|
7781
|
+
"inventory.filters.from": "De",
|
|
7782
|
+
"inventory.filters.to": "At\xE9",
|
|
7783
|
+
"inventory.filters.allTypes": "Todos os tipos",
|
|
7784
|
+
"inventory.filters.location": "Local",
|
|
7785
|
+
"inventory.filters.allLocations": "Todos os locais",
|
|
7786
|
+
"inventory.filters.category": "Categoria",
|
|
7787
|
+
"inventory.filters.allCategories": "Todas as categorias",
|
|
7788
|
+
"inventory.filters.kind": "Tipo de item",
|
|
7789
|
+
"inventory.filters.allKinds": "Todos",
|
|
7790
|
+
"inventory.filters.status": "Situa\xE7\xE3o",
|
|
7791
|
+
"inventory.filters.allStatuses": "Todas",
|
|
6690
7792
|
"inventory.stock.pendingRetry": "A confirma\xE7\xE3o ainda est\xE1 pendente. N\xE3o fa\xE7a outro lan\xE7amento. Tente novamente com os mesmos dados antes de sair desta tela.",
|
|
6691
7793
|
"inventory.stock.refreshAfterCommit": "Movimento registrado. Atualize a tela para consultar o saldo.",
|
|
6692
7794
|
"inventory.dashboard.activeItems": "Itens ativos",
|
|
@@ -6713,6 +7815,10 @@ var ptBR = {
|
|
|
6713
7815
|
"inventory.history.newEntry": "Nova entrada",
|
|
6714
7816
|
"inventory.history.newExit": "Sa\xEDda",
|
|
6715
7817
|
"inventory.nav.recipes": "Receitas",
|
|
7818
|
+
"inventory.entity.stockLocation": "Local de Estoque",
|
|
7819
|
+
"inventory.entity.stockLocation.plural": "Locais de Estoque",
|
|
7820
|
+
"inventory.entity.productCategory": "Categoria",
|
|
7821
|
+
"inventory.entity.productCategory.plural": "Categorias",
|
|
6716
7822
|
"registry.suppliers": "Fornecedores",
|
|
6717
7823
|
"registry.suppliers.description": "De quem voc\xEA compra",
|
|
6718
7824
|
"registry.product-categories": "Categorias",
|
|
@@ -6724,7 +7830,7 @@ var ptBR = {
|
|
|
6724
7830
|
"inventory.history.searchPlaceholder": "Buscar movimenta\xE7\xF5es...",
|
|
6725
7831
|
"inventory.history.subtitle": "Todas as movimenta\xE7\xF5es de estoque",
|
|
6726
7832
|
"inventory.history.title": "Hist\xF3rico de Estoque",
|
|
6727
|
-
"inventory.nav.dashboard": "
|
|
7833
|
+
"inventory.nav.dashboard": "Resumo",
|
|
6728
7834
|
"inventory.nav.entry": "Entrada",
|
|
6729
7835
|
"inventory.nav.exit": "Sa\xEDda",
|
|
6730
7836
|
"inventory.nav.history": "Hist\xF3rico",
|
|
@@ -15338,7 +16444,8 @@ function resolveConfig(options) {
|
|
|
15338
16444
|
currency: { ...DEFAULT_CURRENCY, ...options?.currency },
|
|
15339
16445
|
productTypes: options?.productTypes ?? DEFAULT_PRODUCT_TYPES,
|
|
15340
16446
|
locations: options?.locations ?? [],
|
|
15341
|
-
moduleNav: options?.moduleNav
|
|
16447
|
+
moduleNav: options?.moduleNav,
|
|
16448
|
+
extraViews: options?.extraViews ?? []
|
|
15342
16449
|
};
|
|
15343
16450
|
}
|
|
15344
16451
|
function createInventoryDataProvider() {
|