@fayz-ai/plugin-inventory 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/InventoryContext.d.ts +37 -0
- package/dist/InventoryContext.d.ts.map +1 -0
- package/dist/InventoryPage.d.ts +13 -0
- package/dist/InventoryPage.d.ts.map +1 -0
- package/dist/components/InventoryGeneralSettings.d.ts +3 -0
- package/dist/components/InventoryGeneralSettings.d.ts.map +1 -0
- package/dist/components/InventoryOnboarding.d.ts +5 -0
- package/dist/components/InventoryOnboarding.d.ts.map +1 -0
- package/dist/components/InventorySettings.d.ts +8 -0
- package/dist/components/InventorySettings.d.ts.map +1 -0
- package/dist/data/index.d.ts +3 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/mock.d.ts +3 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/supabase.d.ts +3 -0
- package/dist/data/supabase.d.ts.map +1 -0
- package/dist/data/types.d.ts +22 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/index.cjs +2936 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2930 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/tenant.d.ts +3 -0
- package/dist/lib/tenant.d.ts.map +1 -0
- package/dist/locales/en.d.ts +2 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/pt-BR.d.ts +2 -0
- package/dist/locales/pt-BR.d.ts.map +1 -0
- package/dist/registries.d.ts +3 -0
- package/dist/registries.d.ts.map +1 -0
- package/dist/store.d.ts +28 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +213 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/views/DashboardView.d.ts +8 -0
- package/dist/views/DashboardView.d.ts.map +1 -0
- package/dist/views/MovementHistoryView.d.ts +5 -0
- package/dist/views/MovementHistoryView.d.ts.map +1 -0
- package/dist/views/ProductCrudForm.d.ts +6 -0
- package/dist/views/ProductCrudForm.d.ts.map +1 -0
- package/dist/views/ProductFormView.d.ts +6 -0
- package/dist/views/ProductFormView.d.ts.map +1 -0
- package/dist/views/ProductListView.d.ts +6 -0
- package/dist/views/ProductListView.d.ts.map +1 -0
- package/dist/views/RecipeDetailView.d.ts +6 -0
- package/dist/views/RecipeDetailView.d.ts.map +1 -0
- package/dist/views/RecipeFormView.d.ts +5 -0
- package/dist/views/RecipeFormView.d.ts.map +1 -0
- package/dist/views/RecipesView.d.ts +6 -0
- package/dist/views/RecipesView.d.ts.map +1 -0
- package/dist/views/StockMovementView.d.ts +8 -0
- package/dist/views/StockMovementView.d.ts.map +1 -0
- package/dist/views/dashboardWidgets.d.ts +11 -0
- package/dist/views/dashboardWidgets.d.ts.map +1 -0
- package/dist/views/productEntity.d.ts +6 -0
- package/dist/views/productEntity.d.ts.map +1 -0
- package/package.json +55 -0
- package/src/InventoryContext.tsx +40 -0
- package/src/InventoryPage.tsx +170 -0
- package/src/README.md +177 -0
- package/src/components/InventoryGeneralSettings.tsx +26 -0
- package/src/components/InventoryOnboarding.tsx +60 -0
- package/src/components/InventorySettings.tsx +27 -0
- package/src/data/index.ts +2 -0
- package/src/data/mock.ts +266 -0
- package/src/data/supabase.ts +358 -0
- package/src/data/types.ts +35 -0
- package/src/index.ts +191 -0
- package/src/lib/tenant.ts +4 -0
- package/src/locales/en.ts +242 -0
- package/src/locales/index.ts +7 -0
- package/src/locales/pt-BR.ts +242 -0
- package/src/migrations/001_inventory_base.sql +69 -0
- package/src/migrations/002_recipes.sql +34 -0
- package/src/migrations/003_measurement_units.sql +13 -0
- package/src/registries.ts +111 -0
- package/src/store.ts +127 -0
- package/src/types.ts +256 -0
- package/src/views/DashboardView.tsx +11 -0
- package/src/views/MovementHistoryView.tsx +104 -0
- package/src/views/ProductCrudForm.tsx +99 -0
- package/src/views/ProductFormView.tsx +283 -0
- package/src/views/ProductListView.tsx +107 -0
- package/src/views/RecipeDetailView.tsx +192 -0
- package/src/views/RecipeFormView.tsx +235 -0
- package/src/views/RecipesView.tsx +103 -0
- package/src/views/StockMovementView.tsx +516 -0
- package/src/views/dashboardWidgets.tsx +101 -0
- package/src/views/productEntity.tsx +124 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// inventory.* — merged plugin-own + saas-core central i18n
|
|
2
|
+
export const ptBR: Record<string, string> = {
|
|
3
|
+
"inventory.dashboard.activeItems": "Itens ativos",
|
|
4
|
+
"inventory.dashboard.belowMinimum": "Abaixo do mínimo",
|
|
5
|
+
"inventory.dashboard.entries": "Entradas:",
|
|
6
|
+
"inventory.dashboard.exits": "Saídas:",
|
|
7
|
+
"inventory.dashboard.last7Days": "Últimos 7 dias",
|
|
8
|
+
"inventory.dashboard.lowStock": "Estoque Baixo",
|
|
9
|
+
"inventory.dashboard.needRestocking": "Precisam reposição",
|
|
10
|
+
"inventory.dashboard.outOfStock": "Sem Estoque",
|
|
11
|
+
"inventory.dashboard.quickActions": "Ações Rápidas",
|
|
12
|
+
"inventory.dashboard.quickActionsDesc": "Use a barra lateral para registrar entradas, saídas ou gerenciar produtos.",
|
|
13
|
+
"inventory.dashboard.recentActivity": "Atividade Recente",
|
|
14
|
+
"inventory.dashboard.stockValue": "Valor do Estoque",
|
|
15
|
+
"inventory.dashboard.totalProducts": "Total de Produtos",
|
|
16
|
+
"inventory.dashboard.totalValue": "Valor total do estoque",
|
|
17
|
+
"inventory.history.noMovements": "Nenhuma movimentação registrada",
|
|
18
|
+
"inventory.history.searchPlaceholder": "Buscar movimentações...",
|
|
19
|
+
"inventory.history.subtitle": "Todas as movimentações de estoque",
|
|
20
|
+
"inventory.history.title": "Histórico de Estoque",
|
|
21
|
+
"inventory.nav.dashboard": "Painel",
|
|
22
|
+
"inventory.nav.entry": "Entrada",
|
|
23
|
+
"inventory.nav.exit": "Saída",
|
|
24
|
+
"inventory.nav.history": "Histórico",
|
|
25
|
+
"inventory.nav.list": "Lista",
|
|
26
|
+
"inventory.nav.new": "Novo",
|
|
27
|
+
"inventory.nav.products": "Produtos",
|
|
28
|
+
"inventory.nav.stock": "Estoque",
|
|
29
|
+
"inventory.page.newProduct": "Novo Produto",
|
|
30
|
+
"inventory.page.newProductDesc": "Adicionar um produto ao seu catálogo",
|
|
31
|
+
"inventory.page.settingsSubtitle": "Preferências, fornecedores, categorias e unidades",
|
|
32
|
+
"inventory.page.settingsTitle": "Configurações de Estoque",
|
|
33
|
+
"inventory.page.stockEntry": "Entrada de Estoque",
|
|
34
|
+
"inventory.page.stockEntryDesc": "Registrar mercadorias recebidas",
|
|
35
|
+
"inventory.page.stockExit": "Saída de Estoque",
|
|
36
|
+
"inventory.page.stockExitDesc": "Registrar mercadorias usadas ou vendidas",
|
|
37
|
+
"inventory.recipeDetail.active": "Ativa",
|
|
38
|
+
"inventory.recipeDetail.backToList": "Voltar à lista",
|
|
39
|
+
"inventory.recipeDetail.inactive": "Inativa",
|
|
40
|
+
"inventory.recipeDetail.ingredientCount": "Ingredientes",
|
|
41
|
+
"inventory.recipeDetail.ingredientsTitle": "Ingredientes",
|
|
42
|
+
"inventory.recipeDetail.instructions": "Instruções",
|
|
43
|
+
"inventory.recipeDetail.noIngredients": "Nenhum ingrediente definido",
|
|
44
|
+
"inventory.recipeDetail.notFound": "Receita não encontrada",
|
|
45
|
+
"inventory.recipeDetail.prepTime": "Tempo de preparo",
|
|
46
|
+
"inventory.recipeDetail.produces": "Produz",
|
|
47
|
+
"inventory.recipeDetail.recipes": "Receitas",
|
|
48
|
+
"inventory.recipeDetail.yield": "Rendimento",
|
|
49
|
+
"inventory.recipes.createFirst": "Crie sua primeira receita",
|
|
50
|
+
"inventory.recipes.ingredients": "Ingredientes",
|
|
51
|
+
"inventory.recipes.newRecipe": "Nova Receita",
|
|
52
|
+
"inventory.recipes.noRecipes": "Nenhuma receita ainda",
|
|
53
|
+
"inventory.recipes.produces": "Produz:",
|
|
54
|
+
"inventory.recipes.productionFormulas": "{{count}} fórmulas de produção",
|
|
55
|
+
"inventory.recipes.recipesDesc": "Receitas definem como produzir produtos a partir de ingredientes",
|
|
56
|
+
"inventory.recipes.title": "Receitas",
|
|
57
|
+
"inventory.recipes.yield": "Rendimento",
|
|
58
|
+
"inventory.stock.additionalDetails": "Detalhes adicionais (opcional)",
|
|
59
|
+
"inventory.stock.adjustmentDesc": "Correção de contagem",
|
|
60
|
+
"inventory.stock.adjustmentLabel": "Ajuste",
|
|
61
|
+
"inventory.stock.back": "Voltar",
|
|
62
|
+
"inventory.stock.batchNumber": "Número do Lote",
|
|
63
|
+
"inventory.stock.currentStock": "Estoque Atual",
|
|
64
|
+
"inventory.stock.destination": "Destino",
|
|
65
|
+
"inventory.stock.document": "Documento",
|
|
66
|
+
"inventory.stock.documentNumber": "Número do Documento",
|
|
67
|
+
"inventory.stock.entry": "Entrada de Estoque",
|
|
68
|
+
"inventory.stock.entryDesc": "Recebimento de mercadorias",
|
|
69
|
+
"inventory.stock.entryLabel": "Entrada",
|
|
70
|
+
"inventory.stock.exit": "Saída de Estoque",
|
|
71
|
+
"inventory.stock.exitDesc": "Uso ou venda",
|
|
72
|
+
"inventory.stock.exitLabel": "Saída",
|
|
73
|
+
"inventory.stock.expirationDate": "Data de Validade",
|
|
74
|
+
"inventory.stock.fromLocation": "Local de Origem",
|
|
75
|
+
"inventory.stock.locationLabel": "Local",
|
|
76
|
+
"inventory.stock.lossDesc": "Desperdício ou dano",
|
|
77
|
+
"inventory.stock.lossLabel": "Perda",
|
|
78
|
+
"inventory.stock.movement": "Movimentação de Estoque",
|
|
79
|
+
"inventory.stock.movementDetails": "Detalhes da Movimentação",
|
|
80
|
+
"inventory.stock.movementType": "Tipo de movimentação",
|
|
81
|
+
"inventory.stock.next": "Próximo",
|
|
82
|
+
"inventory.stock.notes": "Observações",
|
|
83
|
+
"inventory.stock.product": "Produto",
|
|
84
|
+
"inventory.stock.quantity": "Quantidade *",
|
|
85
|
+
"inventory.stock.quantityLabel": "Quantidade",
|
|
86
|
+
"inventory.stock.reason": "Motivo *",
|
|
87
|
+
"inventory.stock.reasonLabel": "Motivo",
|
|
88
|
+
"inventory.stock.recordMovement": "Registrar Movimentação",
|
|
89
|
+
"inventory.stock.recorded": "Registrado",
|
|
90
|
+
"inventory.stock.recording": "Registrando...",
|
|
91
|
+
"inventory.stock.searchProduct": "Buscar por nome, SKU ou código de barras...",
|
|
92
|
+
"inventory.stock.stepOf": "Etapa {{step}} de 3",
|
|
93
|
+
"inventory.stock.supplier": "Fornecedor",
|
|
94
|
+
"inventory.stock.toLocation": "Local de Destino *",
|
|
95
|
+
"inventory.stock.total": "Total",
|
|
96
|
+
"inventory.stock.totalCost": "Custo Total",
|
|
97
|
+
"inventory.stock.transferDesc": "Entre locais",
|
|
98
|
+
"inventory.stock.transferLabel": "Transferência",
|
|
99
|
+
"inventory.stock.unitCost": "Custo Unitário",
|
|
100
|
+
"inventory.stock.unitCostLabel": "Custo Unitário",
|
|
101
|
+
"inventory.onboarding.welcome": "Bem-vindo ao Estoque",
|
|
102
|
+
"inventory.onboarding.description": "Acompanhe produtos, gerencie níveis de estoque e monitore movimentações do seu negócio.",
|
|
103
|
+
"inventory.onboarding.skip": "Pular configuração",
|
|
104
|
+
"inventory.onboarding.getStarted": "Começar",
|
|
105
|
+
"inventory.onboarding.units.title": "Unidades de Medida",
|
|
106
|
+
"inventory.onboarding.units.description": "Unidades padrão (Unidade, Caixa, Kg, L, etc.) estão prontas. Personalize-as a qualquer momento em Configurações.",
|
|
107
|
+
"inventory.onboarding.locations.title": "Locais de Estoque",
|
|
108
|
+
"inventory.onboarding.locations.description": "Um local de armazenamento padrão foi criado. Adicione mais para controle multi-local.",
|
|
109
|
+
"inventory.onboarding.start": "Começar a usar Estoque",
|
|
110
|
+
"inventory.quickActions.newProduct": "Novo Produto",
|
|
111
|
+
"inventory.quickActions.newProductDesc": "Adicionar um produto ao catálogo",
|
|
112
|
+
"inventory.quickActions.stockEntry": "Entrada de Estoque",
|
|
113
|
+
"inventory.quickActions.stockEntryDesc": "Registrar mercadorias recebidas",
|
|
114
|
+
"inventory.quickActions.stockExit": "Saída de Estoque",
|
|
115
|
+
"inventory.quickActions.stockExitDesc": "Registrar mercadorias usadas ou vendidas",
|
|
116
|
+
"inventory.settingsPage.title": "Configurações de Estoque",
|
|
117
|
+
"inventory.settingsPage.subtitle": "Gerenciar unidades, categorias e locais de estoque",
|
|
118
|
+
"inventory.settings.stockManagement": "Gestão de Estoque",
|
|
119
|
+
"inventory.settings.stockManagementDesc": "Como os níveis de estoque são rastreados",
|
|
120
|
+
"inventory.settings.lowStockAlerts": "Alertas de estoque baixo",
|
|
121
|
+
"inventory.settings.lowStockAlertsDesc": "Mostrar avisos quando produtos ficarem abaixo da quantidade mínima",
|
|
122
|
+
"inventory.settings.requireReason": "Exigir motivo para ajustes",
|
|
123
|
+
"inventory.settings.requireReasonDesc": "Tornar obrigatório o motivo para ajustes e perdas de estoque",
|
|
124
|
+
"inventory.settings.autoDeduct": "Auto-deduzir no serviço",
|
|
125
|
+
"inventory.settings.autoDeductDesc": "Deduzir automaticamente produtos quando um serviço é executado",
|
|
126
|
+
"inventory.settings.products": "Produtos",
|
|
127
|
+
"inventory.settings.productsDesc": "Comportamento do catálogo de produtos",
|
|
128
|
+
"inventory.settings.requireSku": "Exigir SKU",
|
|
129
|
+
"inventory.settings.requireSkuDesc": "Tornar SKU obrigatório ao criar produtos",
|
|
130
|
+
"inventory.settings.allowNegative": "Permitir estoque negativo",
|
|
131
|
+
"inventory.settings.allowNegativeDesc": "Permitir que quantidades de estoque fiquem abaixo de zero",
|
|
132
|
+
"inventory.settings.notifications": "Notificações",
|
|
133
|
+
"inventory.settings.notificationsDesc": "Alertas e lembretes",
|
|
134
|
+
"inventory.settings.lowStockEmail": "Alertas de estoque baixo por e-mail",
|
|
135
|
+
"inventory.settings.lowStockEmailDesc": "Enviar e-mail quando produtos atingirem a quantidade mínima",
|
|
136
|
+
"inventory.settings.expiryWarnings": "Avisos de validade",
|
|
137
|
+
"inventory.settings.expiryWarningsDesc": "Alertar antes de produtos vencerem (rastreamento de lotes)",
|
|
138
|
+
"inventory.productForm.newProduct": "Novo Produto",
|
|
139
|
+
"inventory.productForm.editProduct": "Editar Produto",
|
|
140
|
+
"inventory.productForm.updateDetails": "Atualizar detalhes do produto",
|
|
141
|
+
"inventory.productForm.addToCatalog": "Adicionar um produto ao seu catálogo",
|
|
142
|
+
"inventory.productForm.cancel": "Cancelar",
|
|
143
|
+
"inventory.productForm.save": "Salvar",
|
|
144
|
+
"inventory.productForm.saving": "Salvando...",
|
|
145
|
+
"inventory.productForm.generalInfo": "Informações Gerais",
|
|
146
|
+
"inventory.productForm.name": "Nome",
|
|
147
|
+
"inventory.productForm.namePlaceholder": "Nome do produto",
|
|
148
|
+
"inventory.productForm.brand": "Marca",
|
|
149
|
+
"inventory.productForm.brandPlaceholder": "Nome da marca",
|
|
150
|
+
"inventory.productForm.sku": "SKU",
|
|
151
|
+
"inventory.productForm.skuPlaceholder": "Código interno",
|
|
152
|
+
"inventory.productForm.barcode": "Código de Barras",
|
|
153
|
+
"inventory.productForm.barcodePlaceholder": "EAN / UPC",
|
|
154
|
+
"inventory.productForm.description": "Descrição",
|
|
155
|
+
"inventory.productForm.descriptionPlaceholder": "Informações adicionais sobre o produto",
|
|
156
|
+
"inventory.productForm.classification": "Classificação",
|
|
157
|
+
"inventory.productForm.classificationDesc": "Como este produto é usado no seu negócio",
|
|
158
|
+
"inventory.productForm.typeIngredient": "Matéria-prima usada na produção ou serviços",
|
|
159
|
+
"inventory.productForm.typeSale": "Vendido diretamente aos clientes",
|
|
160
|
+
"inventory.productForm.typeIntermediate": "Produzido internamente a partir de outros itens",
|
|
161
|
+
"inventory.productForm.typeAsset": "Ativo fixo para controle patrimonial",
|
|
162
|
+
"inventory.productForm.pricing": "Preços",
|
|
163
|
+
"inventory.productForm.costPrice": "Preço de Custo",
|
|
164
|
+
"inventory.productForm.salePrice": "Preço de Venda",
|
|
165
|
+
"inventory.productForm.margin": "Margem",
|
|
166
|
+
"inventory.productForm.stockLevels": "Níveis de Estoque",
|
|
167
|
+
"inventory.productForm.stockLevelsDesc": "Limites mínimo e máximo para alertas",
|
|
168
|
+
"inventory.productForm.minQuantity": "Quantidade Mínima",
|
|
169
|
+
"inventory.productForm.minQuantityHint": "Alertar quando o estoque ficar abaixo deste valor",
|
|
170
|
+
"inventory.productForm.maxQuantity": "Quantidade Máxima",
|
|
171
|
+
"inventory.productForm.maxQuantityHint": "Capacidade máxima para este produto",
|
|
172
|
+
"inventory.productForm.optional": "Opcional",
|
|
173
|
+
"inventory.productList.title": "Produtos",
|
|
174
|
+
"inventory.productList.subtitle": "{{count}} produtos",
|
|
175
|
+
"inventory.productList.product": "Produto",
|
|
176
|
+
"inventory.productList.type": "Tipo",
|
|
177
|
+
"inventory.productList.stock": "Estoque",
|
|
178
|
+
"inventory.productList.cost": "Custo",
|
|
179
|
+
"inventory.productList.value": "Valor",
|
|
180
|
+
"inventory.productList.searchPlaceholder": "Buscar produtos...",
|
|
181
|
+
"inventory.productList.newProduct": "Novo Produto",
|
|
182
|
+
"inventory.productList.empty": "Nenhum produto ainda",
|
|
183
|
+
"inventory.productList.createFirst": "Crie seu primeiro produto",
|
|
184
|
+
"inventory.stock.unknownProduct": "Produto Desconhecido",
|
|
185
|
+
"inventory.stock.reasonLossPlaceholder": "ex. Vencido, danificado",
|
|
186
|
+
"inventory.stock.reasonAdjustPlaceholder": "ex. Correção de contagem física",
|
|
187
|
+
"inventory.stock.documentPlaceholder": "Nota fiscal, recibo, PO...",
|
|
188
|
+
"inventory.stock.batchPlaceholder": "ex. LOT001",
|
|
189
|
+
"inventory.stock.notesPlaceholder": "Observações adicionais...",
|
|
190
|
+
"inventory.recipeDetail.loading": "Carregando...",
|
|
191
|
+
"inventory.recipeDetail.ingredient": "Ingrediente",
|
|
192
|
+
"inventory.recipeDetail.quantity": "Quantidade",
|
|
193
|
+
"inventory.recipeDetail.unit": "Unidade",
|
|
194
|
+
"inventory.recipeDetail.notes": "Observações",
|
|
195
|
+
"inventory.recipeDetail.created": "Criado",
|
|
196
|
+
"inventory.recipeDetail.updated": "Atualizado",
|
|
197
|
+
"inventory.recipeForm.newRecipe": "Nova Receita",
|
|
198
|
+
"inventory.recipeForm.subtitle": "Definir uma fórmula de produção",
|
|
199
|
+
"inventory.recipeForm.cancel": "Cancelar",
|
|
200
|
+
"inventory.recipeForm.saveRecipe": "Salvar Receita",
|
|
201
|
+
"inventory.recipeForm.saving": "Salvando...",
|
|
202
|
+
"inventory.recipeForm.recipeDetails": "Detalhes da Receita",
|
|
203
|
+
"inventory.recipeForm.recipeName": "Nome da Receita",
|
|
204
|
+
"inventory.recipeForm.recipeNamePlaceholder": "ex. Molho de Tomate",
|
|
205
|
+
"inventory.recipeForm.description": "Descrição",
|
|
206
|
+
"inventory.recipeForm.descriptionPlaceholder": "Breve descrição...",
|
|
207
|
+
"inventory.recipeForm.produces": "Produz (Produto)",
|
|
208
|
+
"inventory.recipeForm.searchProduct": "Buscar produto...",
|
|
209
|
+
"inventory.recipeForm.createProduct": "Criar produto",
|
|
210
|
+
"inventory.recipeForm.yieldQuantity": "Quantidade de Rendimento",
|
|
211
|
+
"inventory.recipeForm.prepTime": "Tempo de Preparo (min)",
|
|
212
|
+
"inventory.recipeForm.instructions": "Instruções",
|
|
213
|
+
"inventory.recipeForm.instructionsPlaceholder": "Instruções de preparo passo a passo...",
|
|
214
|
+
"inventory.recipeForm.ingredients": "Ingredientes",
|
|
215
|
+
"inventory.recipeForm.addIngredient": "Adicionar Ingrediente",
|
|
216
|
+
"inventory.recipeForm.noIngredients": "Nenhum ingrediente adicionado",
|
|
217
|
+
"inventory.recipeForm.addFirstIngredient": "Adicione seu primeiro ingrediente",
|
|
218
|
+
"inventory.recipeForm.ingredient": "Ingrediente",
|
|
219
|
+
"inventory.recipeForm.quantity": "Quantidade",
|
|
220
|
+
"inventory.recipeForm.notes": "Observações",
|
|
221
|
+
"inventory.recipeForm.searchIngredient": "Buscar ingrediente...",
|
|
222
|
+
"inventory.recipeForm.createIngredient": "Criar ingrediente",
|
|
223
|
+
"inventory.recipeForm.notesPlaceholder": "ex. picado, fresco",
|
|
224
|
+
"inventory.recipeForm.ingredientsConfigured": "{{configured}} de {{total}} ingredientes configurados",
|
|
225
|
+
"inventory.title": "Estoque",
|
|
226
|
+
"inventory.stock.title": "Estoque",
|
|
227
|
+
"inventory.stock.minStock": "Estoque Mín.",
|
|
228
|
+
"inventory.stock.adjust": "Ajustar Estoque",
|
|
229
|
+
"inventory.stock.lowStock": "Estoque Baixo",
|
|
230
|
+
"inventory.products.title": "Produtos",
|
|
231
|
+
"inventory.products.create": "Novo Produto",
|
|
232
|
+
"inventory.recipes.create": "Nova Receita",
|
|
233
|
+
"inventory.recipes.addIngredient": "Adicionar Ingrediente",
|
|
234
|
+
"inventory.recipes.cost": "Custo",
|
|
235
|
+
"inventory.recipes.loading": "Carregando...",
|
|
236
|
+
"inventory.suppliers.title": "Fornecedores",
|
|
237
|
+
"inventory.suppliers.create": "Novo Fornecedor",
|
|
238
|
+
"inventory.movements.title": "Movimentações",
|
|
239
|
+
"inventory.movements.in": "Entrada",
|
|
240
|
+
"inventory.movements.out": "Saída",
|
|
241
|
+
"inventory.settings.title": "Configurações de Estoque",
|
|
242
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
-- Inventory Plugin: Base Tables
|
|
2
|
+
-- Products use saas_core.products archetype directly
|
|
3
|
+
-- These are plugin-specific extension tables
|
|
4
|
+
|
|
5
|
+
CREATE TABLE IF NOT EXISTS public.product_categories (
|
|
6
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
+
tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
|
|
8
|
+
name text NOT NULL,
|
|
9
|
+
parent_id uuid REFERENCES public.product_categories(id),
|
|
10
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
11
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
12
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
13
|
+
);
|
|
14
|
+
ALTER TABLE public.product_categories ENABLE ROW LEVEL SECURITY;
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_product_categories_tenant ON public.product_categories(tenant_id);
|
|
16
|
+
|
|
17
|
+
CREATE TABLE IF NOT EXISTS public.stock_locations (
|
|
18
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
19
|
+
tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
|
|
20
|
+
name text NOT NULL,
|
|
21
|
+
description text,
|
|
22
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
23
|
+
unit_id uuid,
|
|
24
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
25
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
26
|
+
);
|
|
27
|
+
ALTER TABLE public.stock_locations ENABLE ROW LEVEL SECURITY;
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_stock_locations_tenant ON public.stock_locations(tenant_id);
|
|
29
|
+
|
|
30
|
+
CREATE TABLE IF NOT EXISTS public.stock_movements (
|
|
31
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
32
|
+
tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
|
|
33
|
+
product_id uuid NOT NULL REFERENCES saas_core.products(id),
|
|
34
|
+
quantity numeric(14,4) NOT NULL,
|
|
35
|
+
movement_type text NOT NULL,
|
|
36
|
+
unit_cost numeric(14,2) DEFAULT 0,
|
|
37
|
+
total_cost numeric(14,2) DEFAULT 0,
|
|
38
|
+
stock_location_id uuid REFERENCES public.stock_locations(id),
|
|
39
|
+
destination_location_id uuid REFERENCES public.stock_locations(id),
|
|
40
|
+
supplier_id uuid REFERENCES saas_core.persons(id),
|
|
41
|
+
document_number text,
|
|
42
|
+
reason text,
|
|
43
|
+
notes text,
|
|
44
|
+
movement_date date NOT NULL DEFAULT CURRENT_DATE,
|
|
45
|
+
user_id uuid,
|
|
46
|
+
batch_number text,
|
|
47
|
+
expiration_date date,
|
|
48
|
+
metadata jsonb DEFAULT '{}'::jsonb,
|
|
49
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
50
|
+
);
|
|
51
|
+
ALTER TABLE public.stock_movements ENABLE ROW LEVEL SECURITY;
|
|
52
|
+
CREATE INDEX IF NOT EXISTS idx_stock_movements_tenant ON public.stock_movements(tenant_id);
|
|
53
|
+
CREATE INDEX IF NOT EXISTS idx_stock_movements_product ON public.stock_movements(product_id);
|
|
54
|
+
CREATE INDEX IF NOT EXISTS idx_stock_movements_date ON public.stock_movements(tenant_id, movement_date);
|
|
55
|
+
|
|
56
|
+
CREATE TABLE IF NOT EXISTS public.stock_positions (
|
|
57
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
58
|
+
tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
|
|
59
|
+
product_id uuid NOT NULL REFERENCES saas_core.products(id),
|
|
60
|
+
quantity numeric(14,4) NOT NULL,
|
|
61
|
+
unit_cost numeric(14,2) DEFAULT 0,
|
|
62
|
+
stock_location_id uuid REFERENCES public.stock_locations(id),
|
|
63
|
+
batch_number text,
|
|
64
|
+
expiration_date date,
|
|
65
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
66
|
+
);
|
|
67
|
+
ALTER TABLE public.stock_positions ENABLE ROW LEVEL SECURITY;
|
|
68
|
+
CREATE INDEX IF NOT EXISTS idx_stock_positions_tenant ON public.stock_positions(tenant_id);
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_stock_positions_product ON public.stock_positions(product_id);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
-- Inventory Plugin: Recipes & Technical Specs
|
|
2
|
+
|
|
3
|
+
CREATE TABLE IF NOT EXISTS public.recipes (
|
|
4
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
5
|
+
tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
|
|
6
|
+
name text NOT NULL,
|
|
7
|
+
description text,
|
|
8
|
+
product_id uuid REFERENCES saas_core.products(id),
|
|
9
|
+
yield_quantity numeric(14,4) DEFAULT 1,
|
|
10
|
+
yield_unit_id uuid,
|
|
11
|
+
preparation_time_minutes integer,
|
|
12
|
+
instructions text,
|
|
13
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
14
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
15
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
16
|
+
);
|
|
17
|
+
ALTER TABLE public.recipes ENABLE ROW LEVEL SECURITY;
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_recipes_tenant ON public.recipes(tenant_id);
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_recipes_product ON public.recipes(product_id);
|
|
20
|
+
|
|
21
|
+
CREATE TABLE IF NOT EXISTS public.recipe_ingredients (
|
|
22
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
23
|
+
recipe_id uuid NOT NULL REFERENCES public.recipes(id) ON DELETE CASCADE,
|
|
24
|
+
tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
|
|
25
|
+
product_id uuid NOT NULL REFERENCES saas_core.products(id),
|
|
26
|
+
quantity numeric(14,4) NOT NULL,
|
|
27
|
+
unit_id uuid,
|
|
28
|
+
display_order integer DEFAULT 0,
|
|
29
|
+
notes text,
|
|
30
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
31
|
+
);
|
|
32
|
+
ALTER TABLE public.recipe_ingredients ENABLE ROW LEVEL SECURITY;
|
|
33
|
+
CREATE INDEX IF NOT EXISTS idx_recipe_ingredients_tenant ON public.recipe_ingredients(tenant_id);
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_recipe_ingredients_recipe ON public.recipe_ingredients(recipe_id);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
-- Inventory Plugin: Measurement Units
|
|
2
|
+
|
|
3
|
+
CREATE TABLE IF NOT EXISTS public.measurement_units (
|
|
4
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
5
|
+
tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
|
|
6
|
+
name text NOT NULL,
|
|
7
|
+
abbreviation text NOT NULL,
|
|
8
|
+
is_active boolean NOT NULL DEFAULT true,
|
|
9
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
10
|
+
updated_at timestamptz NOT NULL DEFAULT now()
|
|
11
|
+
);
|
|
12
|
+
ALTER TABLE public.measurement_units ENABLE ROW LEVEL SECURITY;
|
|
13
|
+
CREATE INDEX IF NOT EXISTS idx_measurement_units_tenant ON public.measurement_units(tenant_id);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { PluginRegistryDef } from '@fayz-ai/core'
|
|
2
|
+
import type { EntityDef } from '@fayz-ai/core'
|
|
3
|
+
|
|
4
|
+
const supplierEntity: EntityDef = {
|
|
5
|
+
name: 'Supplier',
|
|
6
|
+
namePlural: 'Suppliers',
|
|
7
|
+
icon: 'Building2',
|
|
8
|
+
layout: 'person',
|
|
9
|
+
displayField: 'name',
|
|
10
|
+
defaultSort: 'name',
|
|
11
|
+
fields: [
|
|
12
|
+
{ key: 'name', label: 'Name', type: 'text', required: true, showInTable: true, searchable: true },
|
|
13
|
+
{ key: 'phone', label: 'Phone', type: 'phone', showInTable: true },
|
|
14
|
+
{ key: 'email', label: 'Email', type: 'email', showInTable: true },
|
|
15
|
+
{ key: 'documentNumber', label: 'Tax ID', type: 'text', showInTable: true },
|
|
16
|
+
{ key: 'address', label: 'Address', type: 'text' },
|
|
17
|
+
{ key: 'notes', label: 'Notes', type: 'textarea' },
|
|
18
|
+
{ key: 'isActive', label: 'Active', type: 'boolean', showInTable: true, defaultValue: true },
|
|
19
|
+
],
|
|
20
|
+
data: {
|
|
21
|
+
table: 'persons',
|
|
22
|
+
schema: 'saas_core',
|
|
23
|
+
tenantScoped: true,
|
|
24
|
+
filters: { kind: 'supplier' },
|
|
25
|
+
defaults: { kind: 'supplier' },
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const measurementUnitEntity: EntityDef = {
|
|
30
|
+
name: 'Measurement Unit',
|
|
31
|
+
namePlural: 'Measurement Units',
|
|
32
|
+
icon: 'Ruler',
|
|
33
|
+
displayField: 'name',
|
|
34
|
+
defaultSort: 'name',
|
|
35
|
+
fields: [
|
|
36
|
+
{ key: 'name', label: 'Name', type: 'text', required: true, showInTable: true },
|
|
37
|
+
{ key: 'abbreviation', label: 'Abbreviation', type: 'text', required: true, showInTable: true },
|
|
38
|
+
{ key: 'isActive', label: 'Active', type: 'boolean', showInTable: true, defaultValue: true },
|
|
39
|
+
],
|
|
40
|
+
data: { table: 'measurement_units', tenantScoped: true },
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const productCategoryEntity: EntityDef = {
|
|
44
|
+
name: 'Product Category',
|
|
45
|
+
namePlural: 'Product Categories',
|
|
46
|
+
icon: 'Tag',
|
|
47
|
+
displayField: 'name',
|
|
48
|
+
defaultSort: 'name',
|
|
49
|
+
fields: [
|
|
50
|
+
{ key: 'name', label: 'Name', type: 'text', required: true, showInTable: true },
|
|
51
|
+
{ key: 'parentId', label: 'Parent', type: 'text', showInTable: false },
|
|
52
|
+
{ key: 'isActive', label: 'Active', type: 'boolean', showInTable: true, defaultValue: true },
|
|
53
|
+
],
|
|
54
|
+
data: { table: 'product_categories', tenantScoped: true },
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const stockLocationEntity: EntityDef = {
|
|
58
|
+
name: 'Stock Location',
|
|
59
|
+
namePlural: 'Stock Locations',
|
|
60
|
+
icon: 'Warehouse',
|
|
61
|
+
displayField: 'name',
|
|
62
|
+
defaultSort: 'name',
|
|
63
|
+
fields: [
|
|
64
|
+
{ key: 'name', label: 'Name', type: 'text', required: true, showInTable: true },
|
|
65
|
+
{ key: 'description', label: 'Description', type: 'textarea', showInTable: true },
|
|
66
|
+
{ key: 'isActive', label: 'Active', type: 'boolean', showInTable: true, defaultValue: true },
|
|
67
|
+
],
|
|
68
|
+
data: { table: 'stock_locations', tenantScoped: true },
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const inventoryRegistries: PluginRegistryDef[] = [
|
|
72
|
+
// --- Editable ---
|
|
73
|
+
{
|
|
74
|
+
id: 'suppliers',
|
|
75
|
+
entity: supplierEntity,
|
|
76
|
+
icon: 'Building2',
|
|
77
|
+
description: 'Manage your product and material suppliers',
|
|
78
|
+
display: 'table',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'product-categories',
|
|
82
|
+
entity: productCategoryEntity,
|
|
83
|
+
icon: 'Tag',
|
|
84
|
+
description: 'Product categories for organization',
|
|
85
|
+
display: 'table',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
id: 'stock-locations',
|
|
89
|
+
entity: stockLocationEntity,
|
|
90
|
+
icon: 'Warehouse',
|
|
91
|
+
description: 'Physical storage locations for inventory',
|
|
92
|
+
display: 'table',
|
|
93
|
+
},
|
|
94
|
+
// --- Read-only (system / seeded) ---
|
|
95
|
+
{
|
|
96
|
+
id: 'measurement-units',
|
|
97
|
+
entity: measurementUnitEntity,
|
|
98
|
+
icon: 'Ruler',
|
|
99
|
+
description: 'Standard units of measure',
|
|
100
|
+
display: 'table',
|
|
101
|
+
readOnly: true,
|
|
102
|
+
seedData: [
|
|
103
|
+
{ id: 'mu-unit', name: 'Unit', abbreviation: 'un', isActive: true },
|
|
104
|
+
{ id: 'mu-box', name: 'Box', abbreviation: 'box', isActive: true },
|
|
105
|
+
{ id: 'mu-kg', name: 'Kilogram', abbreviation: 'kg', isActive: true },
|
|
106
|
+
{ id: 'mu-g', name: 'Gram', abbreviation: 'g', isActive: true },
|
|
107
|
+
{ id: 'mu-l', name: 'Liter', abbreviation: 'L', isActive: true },
|
|
108
|
+
{ id: 'mu-ml', name: 'Milliliter', abbreviation: 'mL', isActive: true },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
]
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createStore, type StoreApi } from 'zustand/vanilla'
|
|
2
|
+
import { dedup } from '@fayz-ai/saas'
|
|
3
|
+
import { toast } from '@fayz-ai/ui'
|
|
4
|
+
import type { InventoryDataProvider } from './data/types'
|
|
5
|
+
import type {
|
|
6
|
+
Product, StockMovement, StockLocation, Recipe,
|
|
7
|
+
InventorySummary, ProductQuery, MovementQuery,
|
|
8
|
+
CreateProductInput, CreateStockMovementInput, CreateRecipeInput,
|
|
9
|
+
} from './types'
|
|
10
|
+
|
|
11
|
+
export interface InventoryUIState {
|
|
12
|
+
products: Product[]
|
|
13
|
+
productsTotal: number
|
|
14
|
+
productsLoading: boolean
|
|
15
|
+
productQuery: ProductQuery
|
|
16
|
+
|
|
17
|
+
movements: StockMovement[]
|
|
18
|
+
movementsTotal: number
|
|
19
|
+
movementsLoading: boolean
|
|
20
|
+
|
|
21
|
+
locations: StockLocation[]
|
|
22
|
+
locationsLoading: boolean
|
|
23
|
+
|
|
24
|
+
recipes: Recipe[]
|
|
25
|
+
recipesLoading: boolean
|
|
26
|
+
|
|
27
|
+
summary: InventorySummary | null
|
|
28
|
+
summaryLoading: boolean
|
|
29
|
+
|
|
30
|
+
fetchSummary(): Promise<void>
|
|
31
|
+
fetchProducts(query: ProductQuery): Promise<void>
|
|
32
|
+
fetchMovements(query: MovementQuery): Promise<void>
|
|
33
|
+
fetchLocations(): Promise<void>
|
|
34
|
+
fetchRecipes(): Promise<void>
|
|
35
|
+
createProduct(input: CreateProductInput): Promise<Product>
|
|
36
|
+
createMovement(input: CreateStockMovementInput): Promise<StockMovement>
|
|
37
|
+
createRecipe(input: CreateRecipeInput): Promise<Recipe>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createInventoryStore(provider: InventoryDataProvider): StoreApi<InventoryUIState> {
|
|
41
|
+
return createStore<InventoryUIState>((set, get) => ({
|
|
42
|
+
products: [], productsTotal: 0, productsLoading: false, productQuery: {},
|
|
43
|
+
movements: [], movementsTotal: 0, movementsLoading: false,
|
|
44
|
+
locations: [], locationsLoading: false,
|
|
45
|
+
recipes: [], recipesLoading: false,
|
|
46
|
+
summary: null, summaryLoading: false,
|
|
47
|
+
|
|
48
|
+
async fetchSummary() {
|
|
49
|
+
return dedup('inv:summary', async () => {
|
|
50
|
+
set({ summaryLoading: true })
|
|
51
|
+
const summary = await provider.getSummary()
|
|
52
|
+
set({ summary, summaryLoading: false })
|
|
53
|
+
})
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
async fetchProducts(query) {
|
|
57
|
+
const key = 'inv:products:' + JSON.stringify(query)
|
|
58
|
+
return dedup(key, async () => {
|
|
59
|
+
set({ productsLoading: true, productQuery: query })
|
|
60
|
+
const result = await provider.getProducts(query)
|
|
61
|
+
set({ products: result.data, productsTotal: result.total, productsLoading: false })
|
|
62
|
+
})
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async fetchMovements(query) {
|
|
66
|
+
return dedup('inv:movements:' + JSON.stringify(query), async () => {
|
|
67
|
+
set({ movementsLoading: true })
|
|
68
|
+
const result = await provider.getMovements(query)
|
|
69
|
+
set({ movements: result.data, movementsTotal: result.total, movementsLoading: false })
|
|
70
|
+
})
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async fetchLocations() {
|
|
74
|
+
return dedup('inv:locations', async () => {
|
|
75
|
+
set({ locationsLoading: true })
|
|
76
|
+
const locations = await provider.getLocations()
|
|
77
|
+
set({ locations, locationsLoading: false })
|
|
78
|
+
})
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async fetchRecipes() {
|
|
82
|
+
set({ recipesLoading: true })
|
|
83
|
+
const recipes = await provider.getRecipes()
|
|
84
|
+
set({ recipes, recipesLoading: false })
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
async createProduct(input) {
|
|
88
|
+
try {
|
|
89
|
+
const product = await provider.createProduct(input)
|
|
90
|
+
const query = get().productQuery
|
|
91
|
+
const [result, summary] = await Promise.all([provider.getProducts(query), provider.getSummary()])
|
|
92
|
+
set({ products: result.data, productsTotal: result.total, summary })
|
|
93
|
+
toast.success('Product created')
|
|
94
|
+
return product
|
|
95
|
+
} catch (err: any) {
|
|
96
|
+
toast.error('Failed to create product', { description: err?.message })
|
|
97
|
+
throw err
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
async createMovement(input) {
|
|
102
|
+
try {
|
|
103
|
+
const movement = await provider.createMovement(input)
|
|
104
|
+
const [summary] = await Promise.all([provider.getSummary()])
|
|
105
|
+
set({ summary })
|
|
106
|
+
toast.success('Stock movement recorded')
|
|
107
|
+
return movement
|
|
108
|
+
} catch (err: any) {
|
|
109
|
+
toast.error('Failed to record movement', { description: err?.message })
|
|
110
|
+
throw err
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
async createRecipe(input) {
|
|
115
|
+
try {
|
|
116
|
+
const recipe = await provider.createRecipe(input)
|
|
117
|
+
const recipes = await provider.getRecipes()
|
|
118
|
+
set({ recipes })
|
|
119
|
+
toast.success('Recipe created')
|
|
120
|
+
return recipe
|
|
121
|
+
} catch (err: any) {
|
|
122
|
+
toast.error('Failed to create recipe', { description: err?.message })
|
|
123
|
+
throw err
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
}))
|
|
127
|
+
}
|