@fayz-ai/plugin-inventory 0.12.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data/mock.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/types.d.ts +9 -1
- package/dist/data/types.d.ts.map +1 -1
- package/dist/index.js +327 -28
- package/dist/index.js.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/store.d.ts +6 -1
- package/dist/store.d.ts.map +1 -1
- package/dist/types.d.ts +27 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/views/InventoryPage.d.ts.map +1 -1
- package/dist/views/MovementHistoryView.d.ts.map +1 -1
- package/dist/views/dashboardWidgets.d.ts.map +1 -1
- package/dist/views/productEntity.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/data/mock.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../src/data/mock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA8DpD,wBAAgB,2BAA2B,IAAI,qBAAqB,
|
|
1
|
+
{"version":3,"file":"mock.d.ts","sourceRoot":"","sources":["../../src/data/mock.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA8DpD,wBAAgB,2BAA2B,IAAI,qBAAqB,CAmrBnE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA8YpD,wBAAgB,+BAA+B,IAAI,qBAAqB,
|
|
1
|
+
{"version":3,"file":"supabase.d.ts","sourceRoot":"","sources":["../../src/data/supabase.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AA8YpD,wBAAgB,+BAA+B,IAAI,qBAAqB,CA0hCvE"}
|
package/dist/data/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Product, StockMovement, StockPosition, StockLocation, Supplier, Recipe, RecipeIngredient, RecipeGroup, StockCountSession, StockCountItem, ProductCategory, CreateProductInput, CreateStockMovementInput, CreateRecipeInput, CreateRecipeIngredientInput, OpenStockCountInput, CloseStockCountResult, ProductQuery, MovementQuery, UsageAuditQuery, UsageAuditEntry, UsageAuditCorrection, UsageAuditBatchResult, PaginatedResult, InventorySummary, ConsumeRecipeForSourceInput, ReverseRecipeConsumptionInput, InventoryConsumptionResult } from '../types';
|
|
1
|
+
import type { Product, StockMovement, StockPosition, StockLocation, Supplier, Recipe, RecipeIngredient, RecipeGroup, StockCountSession, StockCountItem, ProductCategory, CreateProductInput, CreateStockMovementInput, CreateRecipeInput, CreateRecipeIngredientInput, OpenStockCountInput, CloseStockCountResult, ProductQuery, MovementQuery, UsageAuditQuery, UsageAuditEntry, UsageAuditCorrection, UsageAuditBatchResult, PaginatedResult, InventorySummary, RestockLine, CategoryCount, ConsumeRecipeForSourceInput, ReverseRecipeConsumptionInput, InventoryConsumptionResult } from '../types';
|
|
2
2
|
export interface InventoryDataProvider {
|
|
3
3
|
/**
|
|
4
4
|
* Ensure the tenant has the reference rows the inventory UI depends on.
|
|
@@ -77,5 +77,13 @@ export interface InventoryDataProvider {
|
|
|
77
77
|
/** Restores the stock consumed by the same source item, also idempotently. */
|
|
78
78
|
reverseRecipeConsumption(input: ReverseRecipeConsumptionInput): Promise<InventoryConsumptionResult>;
|
|
79
79
|
getSummary(): Promise<InventorySummary>;
|
|
80
|
+
/**
|
|
81
|
+
* O que precisa de compra, com o motivo em cada linha. Sem estoque e abaixo
|
|
82
|
+
* do mínimo são as duas perguntas do balcão, e separá-las em duas listas faria
|
|
83
|
+
* a pessoa somar de cabeça.
|
|
84
|
+
*/
|
|
85
|
+
getRestockList?(limit?: number): Promise<RestockLine[]>;
|
|
86
|
+
/** Quantos produtos por categoria, incluindo o balde "Sem categoria". */
|
|
87
|
+
getCategoryDistribution?(): Promise<CategoryCount[]>;
|
|
80
88
|
}
|
|
81
89
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/data/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAC9D,MAAM,EAAE,gBAAgB,EAAE,WAAW,EACrC,iBAAiB,EAAE,cAAc,EAAE,eAAe,EAClD,kBAAkB,EAAE,wBAAwB,EAAE,iBAAiB,EAAE,2BAA2B,EAC5F,mBAAmB,EAAE,qBAAqB,EAC1C,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,eAAe,EAC7D,oBAAoB,EAAE,qBAAqB,EAC3C,eAAe,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/data/types.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React3, { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
|
2
|
-
import { toast, defineKpiWidget, defineCustomWidget, PageTransition, ModulePage, Button, SubpageHeader, SearchSelect, cn, DatePicker, ListView, ConfirmDialog, Badge, DraftCommitBar, Switch, useSaveBar, DataTable, DashboardCanvas, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, ModalFooter, StockEntryForm, KpiCard, Card, CardHeader, CardTitle, CardContent } from '@fayz-ai/ui';
|
|
2
|
+
import { toast, defineKpiWidget, defineCustomWidget, PageTransition, ModulePage, Button, SubpageHeader, SearchSelect, cn, DatePicker, ListView, ConfirmDialog, Badge, DraftCommitBar, Switch, useSaveBar, DataTable, DashboardCanvas, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, ModalFooter, StockEntryForm, KpiCard, Card, CardHeader, CardTitle, CardContent, Skeleton } from '@fayz-ai/ui';
|
|
3
3
|
import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, createViewRouter, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, usePermissionOptional, CrudListView, TeachMeLink, useLimitGuard, invalidateRelationOptions, CrudFormSkeleton, CrudFormPage, CrudDetailPage, formatCurrency, useAgentSurface, askAI, PermissionGate, QuickActionsButton, invalidateLimit } from '@fayz-ai/admin';
|
|
4
4
|
import { createSafeDataProvider, registerTranslations, countByTenant, useTranslation, getActiveUnitId, 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, ClipboardCheck, AlertTriangle, Plus, BookOpen, Layers, Clock, Coins, Building2, Undo2 } from 'lucide-react';
|
|
6
|
+
import { Trash2, ArrowRightLeft, RefreshCw, ArrowUpRight, ArrowDownRight, Package, Ruler, MapPin, Loader2, Check, ChevronRight, CircleSlash, CircleCheck, ClipboardCheck, AlertTriangle, Plus, BookOpen, Layers, 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
|
|
@@ -360,6 +360,16 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
|
|
|
360
360
|
renderCell: (v) => /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium bg-muted text-muted-foreground", children: typeLabel(v) })
|
|
361
361
|
},
|
|
362
362
|
// — Stock column (list only) — placed before Cost for the desired column order
|
|
363
|
+
//
|
|
364
|
+
// A célula carrega o saldo E o mínimo, porque a pergunta do balcão é a
|
|
365
|
+
// COMPARAÇÃO entre os dois — no V1 são duas colunas e o olho faz a conta.
|
|
366
|
+
// Uma coluna a mais custaria a sétima numa tabela que já é larga.
|
|
367
|
+
//
|
|
368
|
+
// E o alarme obedece à mesma regra do resumo (`stock > 0 && stock <= min`).
|
|
369
|
+
// Antes era `currentQuantity <= minQuantity`, que com mínimo 0 — o caso de
|
|
370
|
+
// 169 dos 169 produtos deste tenant — pinta o catálogo INTEIRO de vermelho.
|
|
371
|
+
// Alarme que toca sempre é alarme que ninguém ouve: é literalmente o defeito
|
|
372
|
+
// do V1 que a Visão Geral documenta (203 de 218 "em reposição urgente").
|
|
363
373
|
{
|
|
364
374
|
key: "currentQuantity",
|
|
365
375
|
label: t2("inventory.productList.stock"),
|
|
@@ -367,7 +377,23 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
|
|
|
367
377
|
showInForm: false,
|
|
368
378
|
showInTable: true,
|
|
369
379
|
sortable: true,
|
|
370
|
-
renderCell: (_v, row) =>
|
|
380
|
+
renderCell: (_v, row) => {
|
|
381
|
+
const qty2 = Number(row.currentQuantity) || 0;
|
|
382
|
+
const min = Number(row.minQuantity) || 0;
|
|
383
|
+
const unit = row.measurementUnitName;
|
|
384
|
+
const below = min > 0 && qty2 > 0 && qty2 <= min;
|
|
385
|
+
const empty = qty2 <= 0;
|
|
386
|
+
return /* @__PURE__ */ jsxs("span", { className: "block text-right", children: [
|
|
387
|
+
/* @__PURE__ */ jsxs("span", { className: below ? "font-medium text-destructive" : empty ? "text-muted-foreground" : "", children: [
|
|
388
|
+
qty2.toLocaleString("pt-BR", { maximumFractionDigits: 3 }),
|
|
389
|
+
unit ? /* @__PURE__ */ jsx("span", { className: "ml-1 text-xs text-muted-foreground", children: unit }) : null
|
|
390
|
+
] }),
|
|
391
|
+
min > 0 && /* @__PURE__ */ jsxs("span", { className: "block text-xs text-muted-foreground", children: [
|
|
392
|
+
"m\xEDn. ",
|
|
393
|
+
min.toLocaleString("pt-BR", { maximumFractionDigits: 3 })
|
|
394
|
+
] })
|
|
395
|
+
] });
|
|
396
|
+
}
|
|
371
397
|
},
|
|
372
398
|
// — Pricing (form) + Cost column (table) —
|
|
373
399
|
{
|
|
@@ -380,7 +406,12 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
|
|
|
380
406
|
currencyLocale: currency.locale,
|
|
381
407
|
showInTable: true,
|
|
382
408
|
sortable: false,
|
|
383
|
-
|
|
409
|
+
// Mesma regra do preço de venda: custo zero é "não cadastrado", não
|
|
410
|
+
// "custa nada". E é ele que faz o Valor abaixo mentir.
|
|
411
|
+
renderCell: (v) => {
|
|
412
|
+
const n = Number(v);
|
|
413
|
+
return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: v == null || v === "" || !Number.isFinite(n) || n === 0 ? "\u2014" : formatCurrency(n, currency) });
|
|
414
|
+
}
|
|
384
415
|
},
|
|
385
416
|
// — Value column (list only): stock × cost —
|
|
386
417
|
{
|
|
@@ -390,7 +421,19 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
|
|
|
390
421
|
showInForm: false,
|
|
391
422
|
showInTable: true,
|
|
392
423
|
sortable: false,
|
|
393
|
-
|
|
424
|
+
// Saldo em mãos SEM custo cadastrado não vale R$ 0,00 — vale um número
|
|
425
|
+
// que esta casa não sabe. É o beco que o V1 documenta nas movimentações:
|
|
426
|
+
// "o Valor Total se descola da realidade sem ninguém errar nada". Zero
|
|
427
|
+
// com saldo zero é verdade; zero com 84 unidades na prateleira é uma
|
|
428
|
+
// afirmação que o dado não sustenta.
|
|
429
|
+
renderCell: (_v, row) => {
|
|
430
|
+
const qty2 = Number(row.currentQuantity) || 0;
|
|
431
|
+
const cost = Number(row.costPrice) || 0;
|
|
432
|
+
if (qty2 > 0 && cost === 0) {
|
|
433
|
+
return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", title: "Sem custo cadastrado, o valor deste saldo n\xE3o pode ser calculado", children: "\u2014" });
|
|
434
|
+
}
|
|
435
|
+
return /* @__PURE__ */ jsx("span", { className: "block text-right font-medium", children: formatCurrency(qty2 * cost, currency) });
|
|
436
|
+
}
|
|
394
437
|
},
|
|
395
438
|
{
|
|
396
439
|
key: "salePrice",
|
|
@@ -400,7 +443,16 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
|
|
|
400
443
|
currency: currency.code,
|
|
401
444
|
currencySymbol: currency.symbol,
|
|
402
445
|
currencyLocale: currency.locale,
|
|
403
|
-
|
|
446
|
+
// O V1 mostra Custo E Venda lado a lado. Custo sozinho não responde
|
|
447
|
+
// "quanto isto rende", e o preço estava só dentro da ficha.
|
|
448
|
+
showInTable: true,
|
|
449
|
+
// Zero aqui é "não informado", não "de graça" — 100 dos 169 produtos deste
|
|
450
|
+
// tenant estão assim. "R$ 0,00" afirmaria um preço; o travessão diz que
|
|
451
|
+
// falta cadastrar, que é a verdade e é o que se pode agir sobre.
|
|
452
|
+
renderCell: (v) => {
|
|
453
|
+
const n = Number(v);
|
|
454
|
+
return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: v == null || v === "" || !Number.isFinite(n) || n === 0 ? "\u2014" : formatCurrency(n, currency) });
|
|
455
|
+
},
|
|
404
456
|
// Ingredients and intermediate preparations are inputs, not things sold
|
|
405
457
|
// directly to the customer. If it is sold, it belongs to the menu-item
|
|
406
458
|
// classification and gets the commercial fields there.
|
|
@@ -1138,7 +1190,7 @@ function StockMovementView({ defaultType, onSaved, viewMovement }) {
|
|
|
1138
1190
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
1139
1191
|
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: needsDest ? t2("inventory.stock.fromLocation") : t2("inventory.stock.locationLabel") }),
|
|
1140
1192
|
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mt-1.5", children: sortedLocations.map((l) => {
|
|
1141
|
-
const
|
|
1193
|
+
const qty2 = positionByLocation[l.id] ?? 0;
|
|
1142
1194
|
return /* @__PURE__ */ jsxs(
|
|
1143
1195
|
"button",
|
|
1144
1196
|
{
|
|
@@ -1152,8 +1204,8 @@ function StockMovementView({ defaultType, onSaved, viewMovement }) {
|
|
|
1152
1204
|
/* @__PURE__ */ jsx("span", { children: l.name }),
|
|
1153
1205
|
productId && /* @__PURE__ */ jsx("span", { className: cn(
|
|
1154
1206
|
"inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-semibold tabular-nums",
|
|
1155
|
-
|
|
1156
|
-
), children:
|
|
1207
|
+
qty2 > 0 ? "bg-success/10 text-success" : "bg-muted text-muted-foreground"
|
|
1208
|
+
), children: qty2 })
|
|
1157
1209
|
]
|
|
1158
1210
|
},
|
|
1159
1211
|
l.id
|
|
@@ -1163,7 +1215,7 @@ function StockMovementView({ defaultType, onSaved, viewMovement }) {
|
|
|
1163
1215
|
needsDest && /* @__PURE__ */ jsxs("div", { children: [
|
|
1164
1216
|
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t2("inventory.stock.toLocation") }),
|
|
1165
1217
|
/* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 mt-1.5", children: sortedLocations.filter((l) => l.id !== locationId).map((l) => {
|
|
1166
|
-
const
|
|
1218
|
+
const qty2 = positionByLocation[l.id] ?? 0;
|
|
1167
1219
|
return /* @__PURE__ */ jsxs(
|
|
1168
1220
|
"button",
|
|
1169
1221
|
{
|
|
@@ -1177,8 +1229,8 @@ function StockMovementView({ defaultType, onSaved, viewMovement }) {
|
|
|
1177
1229
|
/* @__PURE__ */ jsx("span", { children: l.name }),
|
|
1178
1230
|
productId && /* @__PURE__ */ jsx("span", { className: cn(
|
|
1179
1231
|
"inline-flex items-center rounded-full px-1.5 py-0.5 text-[10px] font-semibold tabular-nums",
|
|
1180
|
-
|
|
1181
|
-
), children:
|
|
1232
|
+
qty2 > 0 ? "bg-success/10 text-success" : "bg-muted text-muted-foreground"
|
|
1233
|
+
), children: qty2 })
|
|
1182
1234
|
]
|
|
1183
1235
|
},
|
|
1184
1236
|
l.id
|
|
@@ -1336,9 +1388,30 @@ function MovementHistoryView({ onViewDetail, onNew, onNewExit } = {}) {
|
|
|
1336
1388
|
cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: getValue() })
|
|
1337
1389
|
},
|
|
1338
1390
|
{
|
|
1391
|
+
// O MOTIVO entra debaixo do produto, não como sétima coluna.
|
|
1392
|
+
//
|
|
1393
|
+
// É o campo central do razão do V1 — "nenhum movimento existe sem motivo"
|
|
1394
|
+
// — e estava gravado e invisível: as entradas deste tenant carregam
|
|
1395
|
+
// "Entrada via DANFE NF-e 10467692" desde a migração, que é a resposta
|
|
1396
|
+
// exata para a pergunta nº 1 do usuário no V1 ("de onde vieram essas
|
|
1397
|
+
// entradas que eu não lancei?"). Como legenda ele fica onde o olho já
|
|
1398
|
+
// está, em vez de numa coluna a mais numa tabela que já tem seis.
|
|
1339
1399
|
accessorKey: "productName",
|
|
1340
1400
|
header: t2("inventory.history.columnProduct"),
|
|
1341
|
-
cell: ({
|
|
1401
|
+
cell: ({ row }) => {
|
|
1402
|
+
const reason = row.original.reason || void 0;
|
|
1403
|
+
const doc = row.original.documentNumber || void 0;
|
|
1404
|
+
const printableDoc = doc && !/^[0-9a-f-]{32,36}$/i.test(doc) ? doc : void 0;
|
|
1405
|
+
const caption = reason ?? (printableDoc ? `Documento ${printableDoc}` : void 0);
|
|
1406
|
+
return /* @__PURE__ */ jsxs("span", { className: "block", children: [
|
|
1407
|
+
/* @__PURE__ */ jsx("span", { className: "block font-medium", children: row.original.productName ?? "\u2014" }),
|
|
1408
|
+
caption ? /* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-muted-foreground", title: caption, children: caption }) : (
|
|
1409
|
+
// Movimento sem motivo é um buraco no razão, e o V1 não deixa
|
|
1410
|
+
// criar um pela tela. Dizê-lo é o que faz alguém parar de criar.
|
|
1411
|
+
/* @__PURE__ */ jsx("span", { className: "block text-xs italic text-muted-foreground", children: "Sem motivo registrado" })
|
|
1412
|
+
)
|
|
1413
|
+
] });
|
|
1414
|
+
}
|
|
1342
1415
|
},
|
|
1343
1416
|
{
|
|
1344
1417
|
accessorKey: "movementType",
|
|
@@ -1372,7 +1445,25 @@ function MovementHistoryView({ onViewDetail, onNew, onNewExit } = {}) {
|
|
|
1372
1445
|
{
|
|
1373
1446
|
accessorKey: "totalCost",
|
|
1374
1447
|
header: t2("inventory.history.columnTotal"),
|
|
1375
|
-
|
|
1448
|
+
// Custo é OPCIONAL na entrada, e o V1 documenta o beco que isso abre: o
|
|
1449
|
+
// saldo de quantidade sobe, o valor do estoque não, e o "Valor Total" da
|
|
1450
|
+
// visão geral se descola da realidade sem ninguém errar nada. "R$ 0,00"
|
|
1451
|
+
// esconde isso atrás de um número; o travessão com a explicação no hover
|
|
1452
|
+
// diz que o movimento entrou sem valor — que é o fato.
|
|
1453
|
+
cell: ({ getValue }) => {
|
|
1454
|
+
const n = Number(getValue());
|
|
1455
|
+
if (!Number.isFinite(n) || n === 0) {
|
|
1456
|
+
return /* @__PURE__ */ jsx(
|
|
1457
|
+
"span",
|
|
1458
|
+
{
|
|
1459
|
+
className: "block text-right text-muted-foreground",
|
|
1460
|
+
title: "Movimento registrado sem custo: o saldo mudou, o valor do estoque n\xE3o",
|
|
1461
|
+
children: "\u2014"
|
|
1462
|
+
}
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
return /* @__PURE__ */ jsx("span", { className: "block text-right text-muted-foreground", children: formatCurrency(n, currency) });
|
|
1466
|
+
}
|
|
1376
1467
|
}
|
|
1377
1468
|
], [currency, t2]);
|
|
1378
1469
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
@@ -3159,22 +3250,36 @@ function buildNav(config, view, navigate, t2) {
|
|
|
3159
3250
|
icon: "Package",
|
|
3160
3251
|
active: view.startsWith("products"),
|
|
3161
3252
|
children: [
|
|
3162
|
-
{ id: "products-new", label: t2("inventory.nav.new"), active: view === "products-new", onClick: () => navigate("products-new") },
|
|
3253
|
+
{ id: "products-new", kind: "action", label: t2("inventory.nav.new"), active: view === "products-new", onClick: () => navigate("products-new") },
|
|
3163
3254
|
{ id: "products-list", label: t2("inventory.nav.list"), active: view === "products-list" || view.startsWith("products-detail:") || view.startsWith("products-stock:"), onClick: () => navigate("products-list") }
|
|
3164
3255
|
]
|
|
3165
3256
|
}] : [],
|
|
3257
|
+
// Movimentações é UMA tela: o que entrou e o que saiu. A pílula "Histórico"
|
|
3258
|
+
// ao lado da aba repetia o nome da própria aba (e o título da página já diz
|
|
3259
|
+
// "Histórico de Estoque"), então a aba passa a SER o destino — filha única
|
|
3260
|
+
// não desenha controle.
|
|
3166
3261
|
{
|
|
3167
3262
|
id: "stock",
|
|
3168
3263
|
label: t2("inventory.nav.stock"),
|
|
3169
3264
|
icon: "ArrowUpCircle",
|
|
3170
3265
|
children: [
|
|
3171
|
-
{ id: "stock-entry", label: t2("inventory.nav.entry"), active: view === "stock-entry", onClick: () => navigate("stock-entry") },
|
|
3172
|
-
{ id: "stock-exit", label: t2("inventory.nav.exit"), active: view === "stock-exit", onClick: () => navigate("stock-exit") },
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3266
|
+
{ id: "stock-entry", kind: "action", label: t2("inventory.nav.entry"), active: view === "stock-entry", onClick: () => navigate("stock-entry") },
|
|
3267
|
+
{ id: "stock-exit", kind: "action", label: t2("inventory.nav.exit"), active: view === "stock-exit", onClick: () => navigate("stock-exit") },
|
|
3268
|
+
{ id: "stock-history", label: t2("inventory.nav.history"), defaultChild: true, active: view === "stock-history", onClick: () => navigate("stock-history") }
|
|
3269
|
+
]
|
|
3270
|
+
},
|
|
3271
|
+
// Inventário — conferir o que o sistema diz contra o que existe na
|
|
3272
|
+
// prateleira. Contagem e auditoria de uso respondem à mesma pergunta por
|
|
3273
|
+
// dois caminhos (contar e comparar consumo), e nenhuma das duas é uma
|
|
3274
|
+
// movimentação: viviam como pílula de Movimentações e ninguém as achava.
|
|
3275
|
+
{
|
|
3276
|
+
id: "inventory-check",
|
|
3277
|
+
label: t2("inventory.nav.inventory"),
|
|
3278
|
+
icon: "ClipboardCheck",
|
|
3279
|
+
children: [
|
|
3280
|
+
{ id: "stock-counts-new", kind: "action", label: t2("inventory.nav.new"), active: view === "stock-counts-new", onClick: () => navigate("stock-counts-new") },
|
|
3281
|
+
{ id: "stock-counts", label: t2("inventory.nav.counts"), defaultChild: true, active: view === "stock-counts" || view.startsWith("stock-counts"), onClick: () => navigate("stock-counts") },
|
|
3282
|
+
{ id: "stock-usage-audit", label: t2("inventory.nav.usageAudit"), active: view === "stock-usage-audit", onClick: () => navigate("stock-usage-audit") }
|
|
3178
3283
|
]
|
|
3179
3284
|
}
|
|
3180
3285
|
];
|
|
@@ -3185,7 +3290,7 @@ function buildNav(config, view, navigate, t2) {
|
|
|
3185
3290
|
icon: "BookOpen",
|
|
3186
3291
|
children: [
|
|
3187
3292
|
{ id: "recipes-list", label: config.labels.recipesList, active: view === "recipes-list" || view.startsWith("recipes-detail:"), onClick: () => navigate("recipes-list") },
|
|
3188
|
-
{ id: "recipes-new", label: config.labels.recipesNew, active: view === "recipes-new", onClick: () => navigate("recipes-new") }
|
|
3293
|
+
{ id: "recipes-new", kind: "action", label: config.labels.recipesNew, active: view === "recipes-new", onClick: () => navigate("recipes-new") }
|
|
3189
3294
|
]
|
|
3190
3295
|
});
|
|
3191
3296
|
}
|
|
@@ -3389,6 +3494,120 @@ function RecentActivityPanel() {
|
|
|
3389
3494
|
] })
|
|
3390
3495
|
] });
|
|
3391
3496
|
}
|
|
3497
|
+
function NoMinimumKpi() {
|
|
3498
|
+
const summary = useInventoryStore((s) => s.summary);
|
|
3499
|
+
useEnsureSummary();
|
|
3500
|
+
const n = summary?.noMinimumCount ?? 0;
|
|
3501
|
+
return /* @__PURE__ */ jsx(
|
|
3502
|
+
KpiCard,
|
|
3503
|
+
{
|
|
3504
|
+
label: "Sem m\xEDnimo definido",
|
|
3505
|
+
icon: "AlertTriangle",
|
|
3506
|
+
value: String(n),
|
|
3507
|
+
sub: n > 0 ? "N\xE3o entram no alerta de estoque baixo" : "Todo produto tem m\xEDnimo"
|
|
3508
|
+
}
|
|
3509
|
+
);
|
|
3510
|
+
}
|
|
3511
|
+
function RestockRowSkeleton() {
|
|
3512
|
+
return /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: [0, 1, 2].map((i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3", children: [
|
|
3513
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-40" }),
|
|
3514
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
3515
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16" })
|
|
3516
|
+
] }, i)) });
|
|
3517
|
+
}
|
|
3518
|
+
function qty(value, unit) {
|
|
3519
|
+
const n = value.toLocaleString("pt-BR", { maximumFractionDigits: 3 });
|
|
3520
|
+
return unit ? `${n} ${unit}` : n;
|
|
3521
|
+
}
|
|
3522
|
+
function RestockPanel() {
|
|
3523
|
+
const rows = useInventoryStore((s) => s.restock);
|
|
3524
|
+
const summary = useInventoryStore((s) => s.summary);
|
|
3525
|
+
const fetchRestock = useInventoryStore((s) => s.fetchRestock);
|
|
3526
|
+
useEnsureSummary();
|
|
3527
|
+
useEffect(() => {
|
|
3528
|
+
void fetchRestock();
|
|
3529
|
+
}, []);
|
|
3530
|
+
const belowMinimum = (rows ?? []).filter((r) => r.reason === "below-minimum");
|
|
3531
|
+
const outOfStock = (rows ?? []).filter((r) => r.reason === "out-of-stock");
|
|
3532
|
+
const noMinimum = summary?.noMinimumCount ?? 0;
|
|
3533
|
+
const total = summary?.totalProducts ?? 0;
|
|
3534
|
+
const nobodyHasMinimum = total > 0 && noMinimum === total;
|
|
3535
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
3536
|
+
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: "Precisa de reposi\xE7\xE3o" }) }),
|
|
3537
|
+
/* @__PURE__ */ jsx(CardContent, { children: rows === null ? /* @__PURE__ */ jsx(RestockRowSkeleton, {}) : nobodyHasMinimum ? (
|
|
3538
|
+
// Sem nenhum mínimo cadastrado esta lista não é um alerta: é o
|
|
3539
|
+
// catálogo inteiro. Dizer o que falta vale mais do que listá-lo.
|
|
3540
|
+
/* @__PURE__ */ jsxs("div", { className: "text-sm", children: [
|
|
3541
|
+
/* @__PURE__ */ jsx("p", { className: "font-medium", children: "Nenhum produto tem estoque m\xEDnimo definido." }),
|
|
3542
|
+
/* @__PURE__ */ jsxs("p", { className: "mt-1 text-muted-foreground", children: [
|
|
3543
|
+
"Sem o m\xEDnimo na ficha do produto n\xE3o h\xE1 reposi\xE7\xE3o a calcular \u2014 o alerta de estoque baixo fica em zero mesmo com o dep\xF3sito vazio.",
|
|
3544
|
+
(summary?.outOfStockCount ?? 0) > 0 && ` Hoje ${summary.outOfStockCount === 1 ? "h\xE1 1 produto" : `h\xE1 ${summary.outOfStockCount} produtos`} com saldo zerado.`
|
|
3545
|
+
] })
|
|
3546
|
+
] })
|
|
3547
|
+
) : rows.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Nada a repor: todo produto est\xE1 acima do m\xEDnimo." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-2", children: [...belowMinimum, ...outOfStock].slice(0, 8).map((row) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 text-sm", children: [
|
|
3548
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
3549
|
+
/* @__PURE__ */ jsx("span", { className: "block truncate", children: row.name }),
|
|
3550
|
+
row.categoryName && /* @__PURE__ */ jsx("span", { className: "block truncate text-xs text-muted-foreground", children: row.categoryName })
|
|
3551
|
+
] }),
|
|
3552
|
+
row.reason === "below-minimum" ? /* @__PURE__ */ jsxs(Badge, { variant: "secondary", className: "gap-1 shrink-0", children: [
|
|
3553
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "h-3 w-3", "aria-hidden": "true" }),
|
|
3554
|
+
qty(row.currentQuantity, row.unit),
|
|
3555
|
+
" de ",
|
|
3556
|
+
qty(row.minQuantity, row.unit)
|
|
3557
|
+
] }) : /* @__PURE__ */ jsxs(Badge, { variant: "outline", className: "gap-1 shrink-0", children: [
|
|
3558
|
+
/* @__PURE__ */ jsx(PackageX, { className: "h-3 w-3", "aria-hidden": "true" }),
|
|
3559
|
+
"Sem estoque"
|
|
3560
|
+
] })
|
|
3561
|
+
] }, row.productId)) }) })
|
|
3562
|
+
] });
|
|
3563
|
+
}
|
|
3564
|
+
function CategoryMixPanel() {
|
|
3565
|
+
const mix = useInventoryStore((s) => s.categoryMix);
|
|
3566
|
+
const fetchMix = useInventoryStore((s) => s.fetchCategoryMix);
|
|
3567
|
+
useEffect(() => {
|
|
3568
|
+
void fetchMix();
|
|
3569
|
+
}, []);
|
|
3570
|
+
const total = (mix ?? []).reduce((sum, c) => sum + c.count, 0);
|
|
3571
|
+
const uncategorized = (mix ?? []).find((c) => c.categoryId === null);
|
|
3572
|
+
return /* @__PURE__ */ jsxs(Card, { children: [
|
|
3573
|
+
/* @__PURE__ */ jsx(CardHeader, { children: /* @__PURE__ */ jsx(CardTitle, { children: "Produtos por categoria" }) }),
|
|
3574
|
+
/* @__PURE__ */ jsx(CardContent, { children: mix === null ? /* @__PURE__ */ jsx(RestockRowSkeleton, {}) : total === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Nenhum produto ativo para distribuir." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3575
|
+
/* @__PURE__ */ jsx("ul", { className: "space-y-2", children: mix.slice(0, 8).map((row) => {
|
|
3576
|
+
const pct = Math.round(row.count / total * 100);
|
|
3577
|
+
const isGap = row.categoryId === null;
|
|
3578
|
+
return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 text-sm", children: [
|
|
3579
|
+
/* @__PURE__ */ jsx("span", { className: "w-40 shrink-0 truncate", title: row.name, children: row.name }),
|
|
3580
|
+
/* @__PURE__ */ jsx(
|
|
3581
|
+
"span",
|
|
3582
|
+
{
|
|
3583
|
+
className: "relative h-2 flex-1 overflow-hidden rounded-full bg-muted",
|
|
3584
|
+
"aria-hidden": "true",
|
|
3585
|
+
children: /* @__PURE__ */ jsx(
|
|
3586
|
+
"span",
|
|
3587
|
+
{
|
|
3588
|
+
className: isGap ? "absolute inset-y-0 left-0 rounded-full bg-muted-foreground/60" : "absolute inset-y-0 left-0 rounded-full bg-primary",
|
|
3589
|
+
style: { width: `${pct}%` }
|
|
3590
|
+
}
|
|
3591
|
+
)
|
|
3592
|
+
}
|
|
3593
|
+
),
|
|
3594
|
+
/* @__PURE__ */ jsxs("span", { className: "w-20 shrink-0 text-right tabular-nums text-muted-foreground", children: [
|
|
3595
|
+
row.count,
|
|
3596
|
+
" \xB7 ",
|
|
3597
|
+
pct,
|
|
3598
|
+
"%"
|
|
3599
|
+
] })
|
|
3600
|
+
] }, row.categoryId ?? "none");
|
|
3601
|
+
}) }),
|
|
3602
|
+
uncategorized && uncategorized.count > 0 && /* @__PURE__ */ jsxs("p", { className: "mt-3 text-xs text-muted-foreground", children: [
|
|
3603
|
+
uncategorized.count,
|
|
3604
|
+
" de ",
|
|
3605
|
+
total,
|
|
3606
|
+
" produtos ainda n\xE3o t\xEAm categoria \u2014 \xE9 o que impede este painel, os filtros e as isen\xE7\xF5es de taxa de dizerem algo \xFAtil."
|
|
3607
|
+
] })
|
|
3608
|
+
] }) })
|
|
3609
|
+
] });
|
|
3610
|
+
}
|
|
3392
3611
|
function createInventoryDashboardWidgets(ctx2) {
|
|
3393
3612
|
const withCtx = (Inner) => {
|
|
3394
3613
|
const Wrapped = () => /* @__PURE__ */ jsx(InventoryContextProvider, { config: ctx2.config, provider: ctx2.provider, store: ctx2.store, children: /* @__PURE__ */ jsx(Inner, {}) });
|
|
@@ -3402,7 +3621,12 @@ function createInventoryDashboardWidgets(ctx2) {
|
|
|
3402
3621
|
defineKpiWidget({ id: "inventory.kpi.total-products", title: "inventory.dashboard.totalProducts", domain: "inventory", defaultOrder: 1, defaultVisible: false, component: withCtx(TotalProductsKpi) }),
|
|
3403
3622
|
defineKpiWidget({ id: "inventory.kpi.low-stock", title: "inventory.dashboard.lowStock", domain: "inventory", defaultOrder: 2, defaultVisible: false, component: withCtx(LowStockKpi) }),
|
|
3404
3623
|
defineKpiWidget({ id: "inventory.kpi.out-of-stock", title: "inventory.dashboard.outOfStock", domain: "inventory", defaultOrder: 3, defaultVisible: false, component: withCtx(OutOfStockKpi) }),
|
|
3405
|
-
|
|
3624
|
+
// Escondido na home global (é diagnóstico de cadastro, não manchete do
|
|
3625
|
+
// negócio) e visível no painel do estoque, que é onde se conserta.
|
|
3626
|
+
defineKpiWidget({ id: "inventory.kpi.no-minimum", title: "Sem m\xEDnimo definido", domain: "inventory", defaultOrder: 4, defaultVisible: false, component: withCtx(NoMinimumKpi) }),
|
|
3627
|
+
defineCustomWidget({ id: "inventory.panel.recent-activity", title: "inventory.dashboard.recentActivity", domain: "inventory", span: 2, defaultOrder: 10, surfaces: ["plugin-home"], component: withCtx(RecentActivityPanel) }),
|
|
3628
|
+
defineCustomWidget({ id: "inventory.panel.restock", title: "Precisa de reposi\xE7\xE3o", domain: "inventory", span: 2, defaultOrder: 11, surfaces: ["plugin-home"], component: withCtx(RestockPanel) }),
|
|
3629
|
+
defineCustomWidget({ id: "inventory.panel.category-mix", title: "Produtos por categoria", domain: "inventory", span: 2, defaultOrder: 12, surfaces: ["plugin-home"], component: withCtx(CategoryMixPanel) })
|
|
3406
3630
|
];
|
|
3407
3631
|
}
|
|
3408
3632
|
|
|
@@ -4024,7 +4248,8 @@ function createMockInventoryProvider() {
|
|
|
4024
4248
|
outOfStockCount: outOfStock.length,
|
|
4025
4249
|
totalStockValue: totalValue,
|
|
4026
4250
|
recentMovementCount: recent.length,
|
|
4027
|
-
movementsByType
|
|
4251
|
+
movementsByType,
|
|
4252
|
+
noMinimumCount: active.filter((p) => !(p.minQuantity > 0)).length
|
|
4028
4253
|
};
|
|
4029
4254
|
}
|
|
4030
4255
|
};
|
|
@@ -5060,8 +5285,58 @@ function createSupabaseInventoryProvider() {
|
|
|
5060
5285
|
outOfStockCount: outOfStock.length,
|
|
5061
5286
|
totalStockValue: totalValue,
|
|
5062
5287
|
recentMovementCount: movs.length,
|
|
5063
|
-
movementsByType
|
|
5288
|
+
movementsByType,
|
|
5289
|
+
// `> 0`, não `!= null`: mínimo zero é o mesmo que não ter mínimo — é
|
|
5290
|
+
// dele que sai o alerta que nunca dispara.
|
|
5291
|
+
noMinimumCount: items.filter((p) => !(Number(p.min_stock) > 0)).length
|
|
5064
5292
|
};
|
|
5293
|
+
},
|
|
5294
|
+
async getRestockList(limit = 50) {
|
|
5295
|
+
const { core, pub } = getClients();
|
|
5296
|
+
const { data } = await core.from(PRODUCTS_READ).select("id, name, category_id, stock, min_stock, unit").eq("is_active", true);
|
|
5297
|
+
const rows = data ?? [];
|
|
5298
|
+
const needs = rows.map((r) => ({
|
|
5299
|
+
productId: r.id,
|
|
5300
|
+
name: r.name,
|
|
5301
|
+
categoryId: r.category_id,
|
|
5302
|
+
currentQuantity: Number(r.stock) || 0,
|
|
5303
|
+
minQuantity: Number(r.min_stock) || 0,
|
|
5304
|
+
unit: r.unit ?? void 0
|
|
5305
|
+
})).filter((r) => r.currentQuantity <= 0 || r.minQuantity > 0 && r.currentQuantity <= r.minQuantity).sort((a, b) => {
|
|
5306
|
+
const rank = (x) => x.minQuantity > 0 && x.currentQuantity > 0 ? 0 : 1;
|
|
5307
|
+
return rank(a) - rank(b) || a.name.localeCompare(b.name, "pt-BR");
|
|
5308
|
+
}).slice(0, limit);
|
|
5309
|
+
const catIds = [...new Set(needs.map((n) => n.categoryId).filter(Boolean))];
|
|
5310
|
+
const names = /* @__PURE__ */ new Map();
|
|
5311
|
+
if (catIds.length) {
|
|
5312
|
+
const { data: cats } = await pub.from("categories").select("id, name").in("id", catIds);
|
|
5313
|
+
for (const c of cats ?? []) names.set(c.id, c.name);
|
|
5314
|
+
}
|
|
5315
|
+
return needs.map(({ categoryId, ...rest }) => ({
|
|
5316
|
+
...rest,
|
|
5317
|
+
categoryName: categoryId ? names.get(categoryId) : void 0,
|
|
5318
|
+
reason: rest.currentQuantity <= 0 ? "out-of-stock" : "below-minimum"
|
|
5319
|
+
}));
|
|
5320
|
+
},
|
|
5321
|
+
async getCategoryDistribution() {
|
|
5322
|
+
const { core, pub } = getClients();
|
|
5323
|
+
const { data } = await core.from(PRODUCTS_READ).select("category_id").eq("is_active", true);
|
|
5324
|
+
const rows = data ?? [];
|
|
5325
|
+
const tally = /* @__PURE__ */ new Map();
|
|
5326
|
+
for (const r of rows) tally.set(r.category_id ?? null, (tally.get(r.category_id ?? null) ?? 0) + 1);
|
|
5327
|
+
const ids = [...tally.keys()].filter(Boolean);
|
|
5328
|
+
const names = /* @__PURE__ */ new Map();
|
|
5329
|
+
if (ids.length) {
|
|
5330
|
+
const { data: cats } = await pub.from("categories").select("id, name").in("id", ids);
|
|
5331
|
+
for (const c of cats ?? []) names.set(c.id, c.name);
|
|
5332
|
+
}
|
|
5333
|
+
return [...tally.entries()].map(([categoryId, count]) => ({
|
|
5334
|
+
categoryId,
|
|
5335
|
+
// "Sem categoria" é uma resposta, não um buraco: é o que diz ao dono
|
|
5336
|
+
// que o cadastro está incompleto. Esconder o balde esconderia isso.
|
|
5337
|
+
name: categoryId ? names.get(categoryId) ?? "Categoria removida" : "Sem categoria",
|
|
5338
|
+
count
|
|
5339
|
+
})).sort((a, b) => b.count - a.count);
|
|
5065
5340
|
}
|
|
5066
5341
|
};
|
|
5067
5342
|
return provider;
|
|
@@ -5085,6 +5360,28 @@ function createInventoryStore(provider) {
|
|
|
5085
5360
|
recipesLoading: false,
|
|
5086
5361
|
summary: null,
|
|
5087
5362
|
summaryLoading: false,
|
|
5363
|
+
restock: null,
|
|
5364
|
+
categoryMix: null,
|
|
5365
|
+
async fetchRestock() {
|
|
5366
|
+
if (!provider.getRestockList) return;
|
|
5367
|
+
return dedup("inv:restock", async () => {
|
|
5368
|
+
try {
|
|
5369
|
+
set({ restock: await provider.getRestockList() });
|
|
5370
|
+
} catch {
|
|
5371
|
+
set({ restock: [] });
|
|
5372
|
+
}
|
|
5373
|
+
});
|
|
5374
|
+
},
|
|
5375
|
+
async fetchCategoryMix() {
|
|
5376
|
+
if (!provider.getCategoryDistribution) return;
|
|
5377
|
+
return dedup("inv:category-mix", async () => {
|
|
5378
|
+
try {
|
|
5379
|
+
set({ categoryMix: await provider.getCategoryDistribution() });
|
|
5380
|
+
} catch {
|
|
5381
|
+
set({ categoryMix: [] });
|
|
5382
|
+
}
|
|
5383
|
+
});
|
|
5384
|
+
},
|
|
5088
5385
|
async fetchSummary() {
|
|
5089
5386
|
return dedup("inv:summary", async () => {
|
|
5090
5387
|
set({ summaryLoading: true });
|
|
@@ -5609,10 +5906,11 @@ var en = {
|
|
|
5609
5906
|
"inventory.nav.entry": "Entry",
|
|
5610
5907
|
"inventory.nav.exit": "Exit",
|
|
5611
5908
|
"inventory.nav.history": "History",
|
|
5909
|
+
"inventory.nav.inventory": "Inventory",
|
|
5612
5910
|
"inventory.nav.list": "List",
|
|
5613
5911
|
"inventory.nav.new": "New",
|
|
5614
5912
|
"inventory.nav.products": "Products",
|
|
5615
|
-
"inventory.nav.stock": "
|
|
5913
|
+
"inventory.nav.stock": "Movements",
|
|
5616
5914
|
"inventory.page.newProduct": "New Product",
|
|
5617
5915
|
"inventory.page.newProductDesc": "Add a product to your catalog",
|
|
5618
5916
|
"inventory.page.settingsSubtitle": "Preferences, suppliers, categories, and units",
|
|
@@ -6057,10 +6355,11 @@ var ptBR = {
|
|
|
6057
6355
|
"inventory.nav.entry": "Entrada",
|
|
6058
6356
|
"inventory.nav.exit": "Sa\xEDda",
|
|
6059
6357
|
"inventory.nav.history": "Hist\xF3rico",
|
|
6358
|
+
"inventory.nav.inventory": "Invent\xE1rio",
|
|
6060
6359
|
"inventory.nav.list": "Lista",
|
|
6061
6360
|
"inventory.nav.new": "Novo",
|
|
6062
6361
|
"inventory.nav.products": "Produtos",
|
|
6063
|
-
"inventory.nav.stock": "
|
|
6362
|
+
"inventory.nav.stock": "Movimenta\xE7\xF5es",
|
|
6064
6363
|
"inventory.page.newProduct": "Novo Produto",
|
|
6065
6364
|
"inventory.page.newProductDesc": "Adicionar um produto ao seu cat\xE1logo",
|
|
6066
6365
|
"inventory.page.settingsSubtitle": "Prefer\xEAncias, fornecedores, categorias e unidades",
|