@fayz-ai/plugin-inventory 0.12.3 → 0.12.4
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 +2 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +295 -47
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +2 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/views/InventoryPage.d.ts.map +1 -1
- package/dist/views/ProductListView.d.ts.map +1 -1
- package/dist/views/RecipesView.d.ts.map +1 -1
- package/dist/views/StockCountsView.d.ts.map +1 -1
- package/dist/views/UsageAuditView.d.ts.map +1 -1
- package/dist/views/productEntity.d.ts.map +1 -1
- package/package.json +4 -4
package/dist/context.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface LocationOption {
|
|
|
20
20
|
isHQ?: boolean;
|
|
21
21
|
}
|
|
22
22
|
export interface ResolvedInventoryConfig {
|
|
23
|
+
/** Ver `InventoryPluginOptions.moduleNav`. */
|
|
24
|
+
moduleNav?: 'rail' | 'tabs' | 'sidebar';
|
|
23
25
|
modules: InventoryModules;
|
|
24
26
|
labels: InventoryPluginLabels;
|
|
25
27
|
currency: InventoryCurrency;
|
package/dist/context.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AACpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,OAAO,CAAA;IACvB,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAA;AAE9C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,gBAAgB,CAAA;IACzB,MAAM,EAAE,qBAAqB,CAAA;IAC7B,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,YAAY,EAAE,iBAAiB,EAAE,CAAA;IACjC,SAAS,EAAE,cAAc,EAAE,CAAA;CAC5B;AAID,eAAO,MAAM,wBAAwB;;;;;EAAsB,CAAA;AAC3D,eAAO,MAAM,kBAAkB,+BAAgB,CAAA;AAC/C,eAAO,MAAM,oBAAoB,6BAAkB,CAAA;AACnD,eAAO,MAAM,iBAAiB,oDAAe,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAuB,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAA;AACzF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAA;AACpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAE/C,MAAM,WAAW,gBAAgB;IAC/B,iFAAiF;IACjF,QAAQ,EAAE,OAAO,CAAA;IACjB,OAAO,EAAE,OAAO,CAAA;IAChB,cAAc,EAAE,OAAO,CAAA;IACvB,aAAa,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,MAAM,iBAAiB,GAAG,cAAc,CAAA;AAE9C,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAED,MAAM,WAAW,uBAAuB;IACtC,8CAA8C;IAC9C,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACvC,OAAO,EAAE,gBAAgB,CAAA;IACzB,MAAM,EAAE,qBAAqB,CAAA;IAC7B,QAAQ,EAAE,iBAAiB,CAAA;IAC3B,YAAY,EAAE,iBAAiB,EAAE,CAAA;IACjC,SAAS,EAAE,cAAc,EAAE,CAAA;CAC5B;AAID,eAAO,MAAM,wBAAwB;;;;;EAAsB,CAAA;AAC3D,eAAO,MAAM,kBAAkB,+BAAgB,CAAA;AAC/C,eAAO,MAAM,oBAAoB,6BAAkB,CAAA;AACnD,eAAO,MAAM,iBAAiB,oDAAe,CAAA;AAE7C,OAAO,EAAE,cAAc,EAAE,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -42,6 +42,18 @@ export interface InventoryPluginOptions {
|
|
|
42
42
|
navPosition?: number;
|
|
43
43
|
navSection?: 'main' | 'secondary';
|
|
44
44
|
scope?: PluginScope;
|
|
45
|
+
/**
|
|
46
|
+
* Como o Estoque desenha a PRÓPRIA navegação (Produtos, Movimentações,
|
|
47
|
+
* Contagens, Receitas).
|
|
48
|
+
*
|
|
49
|
+
* O padrão vem do shell, e num app cujo default é `sidebar` esse padrão diz
|
|
50
|
+
* "o rail lista as minhas sub-telas" — promessa que ninguém cumpre por este
|
|
51
|
+
* módulo: no rail ele é UMA linha, e o que tem dentro é tela irmã, não
|
|
52
|
+
* assunto novo. O resultado era o Estoque abrindo sem faixa de abas nenhuma.
|
|
53
|
+
*
|
|
54
|
+
* Mesma porta que `crm`, `marketing` e `financial` já usam.
|
|
55
|
+
*/
|
|
56
|
+
moduleNav?: 'rail' | 'tabs' | 'sidebar';
|
|
45
57
|
verticalId?: VerticalId;
|
|
46
58
|
dataProvider?: InventoryDataProvider;
|
|
47
59
|
locations?: Array<{
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAI5E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAkBzD,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,cAAc,CAAC,EAAE,OAAO,CAAA;QACxB,aAAa,CAAC,EAAE,OAAO,CAAA;QACvB;;;;;;WAMG;QACH,QAAQ,CAAC,EAAE,OAAO,CAAA;KACnB,CAAA;IACD,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtD,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;IACjC,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAChE;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAI5E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AAkBzD,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,cAAc,CAAC,EAAE,OAAO,CAAA;QACxB,aAAa,CAAC,EAAE,OAAO,CAAA;QACvB;;;;;;WAMG;QACH,QAAQ,CAAC,EAAE,OAAO,CAAA;KACnB,CAAA;IACD,MAAM,CAAC,EAAE,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACvC,YAAY,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACtD,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9D,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,CAAA;IACjC,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB;;;;;;;;;;OAUG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACvC,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,SAAS,CAAC,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;CAChE;AAuDD,wFAAwF;AACxF,wBAAgB,2BAA2B,IAAI,qBAAqB,CAKnE;AAED,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,cAAc,CAgMtF;AAED,YAAY,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACzD,YAAY,EAAE,uBAAuB,EAAE,MAAM,WAAW,CAAA;AAIxD,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,uBAAuB,EACvB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,2BAA2B,CAAA;AAClC,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAA;AAGjE,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC3C,YAAY,EACV,2BAA2B,EAC3B,6BAA6B,EAC7B,0BAA0B,EAC1B,0BAA0B,EAC1B,8BAA8B,GAC/B,MAAM,SAAS,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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, Skeleton } from '@fayz-ai/ui';
|
|
3
|
-
import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, createViewRouter, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, usePermissionOptional, CrudListView,
|
|
2
|
+
import { toast, defineKpiWidget, defineCustomWidget, PageTransition, ModulePage, Button, SubpageHeader, SearchSelect, cn, DatePicker, ListView, ConfirmDialog, Badge, DraftCommitBar, Switch, useSaveBar, DataTable, DashboardCanvas, Thumbnail, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, ModalFooter, StockEntryForm, KpiCard, Card, CardHeader, CardTitle, CardContent, Skeleton } from '@fayz-ai/ui';
|
|
3
|
+
import { createPluginContext, PluginSettingsPanel, dedup, useModuleNavigation, createViewRouter, ModuleActionBar, useTenantPluginSettings, SettingsGroup, ToggleRow, usePermissionOptional, CrudListView, useLimitGuard, invalidateRelationOptions, CrudFormSkeleton, CrudFormPage, CrudDetailPage, formatCurrency, useAgentSurface, askAI, PermissionGate, QuickActionsButton, invalidateLimit } from '@fayz-ai/admin';
|
|
4
4
|
import { 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
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';
|
|
@@ -361,13 +361,26 @@ function buildProductEntity(t2, productTypes, currency, onStockChanged, provider
|
|
|
361
361
|
searchable: true,
|
|
362
362
|
showInTable: true,
|
|
363
363
|
sortable: true,
|
|
364
|
-
renderCell: (_v, row) =>
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
364
|
+
renderCell: (_v, row) => (
|
|
365
|
+
// A foto vem junto do nome, como na lista da loja. O produto de um
|
|
366
|
+
// estoque é uma COISA, e a pessoa que confere a prateleira reconhece a
|
|
367
|
+
// embalagem antes de ler o SKU. `imageUrl` já viajava na linha — só não
|
|
368
|
+
// tinha quem o desenhasse.
|
|
369
|
+
//
|
|
370
|
+
// O quadrado aparece SEMPRE, com foto ou sem: um produto sem imagem
|
|
371
|
+
// que não renderizasse nada puxaria o nome para trás contra as linhas
|
|
372
|
+
// que têm, e a coluna deixaria de ler como coluna.
|
|
373
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
374
|
+
/* @__PURE__ */ jsx(Thumbnail, { src: row.imageUrl, icon: Package }),
|
|
375
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
|
|
376
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
377
|
+
/* @__PURE__ */ jsx("p", { className: `truncate font-medium ${row.isActive === false ? "text-muted-foreground" : ""}`, children: row.name }),
|
|
378
|
+
row.isActive === false && /* @__PURE__ */ jsx("span", { className: "inline-flex items-center rounded-full bg-muted px-1.5 py-0.5 text-[9px] font-medium text-muted-foreground", children: t2("inventory.productList.inactive") })
|
|
379
|
+
] }),
|
|
380
|
+
row.sku && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: row.sku })
|
|
381
|
+
] })
|
|
382
|
+
] })
|
|
383
|
+
)
|
|
371
384
|
},
|
|
372
385
|
{ key: "brand", label: t2("inventory.productForm.brand"), type: "text", group: "general", placeholder: t2("inventory.productForm.brandPlaceholder"), showInTable: false },
|
|
373
386
|
{ key: "sku", label: t2("inventory.productForm.sku"), type: "text", group: "general", placeholder: t2("inventory.productForm.skuPlaceholder"), searchable: true, showInTable: false },
|
|
@@ -664,14 +677,6 @@ function ProductListView({ onNew, onOpen }) {
|
|
|
664
677
|
return /* @__PURE__ */ jsx(
|
|
665
678
|
CrudListView,
|
|
666
679
|
{
|
|
667
|
-
headerActions: /* @__PURE__ */ jsx(
|
|
668
|
-
TeachMeLink,
|
|
669
|
-
{
|
|
670
|
-
feature: "inventory.produtos",
|
|
671
|
-
title: "Produtos",
|
|
672
|
-
about: "O cat\xE1logo do estoque. O tipo de produto \xE9 a divis\xE3o prim\xE1ria, e o estoque m\xEDnimo \xE9 o que faz o alerta de reposi\xE7\xE3o significar alguma coisa \u2014 com m\xEDnimo zero, tudo vira urgente."
|
|
673
|
-
}
|
|
674
|
-
),
|
|
675
680
|
entityDef: entity,
|
|
676
681
|
items: loadedOnce ? products2 : null,
|
|
677
682
|
total: productsTotal,
|
|
@@ -1607,14 +1612,6 @@ function StockCountsView({ onNew, onOpen }) {
|
|
|
1607
1612
|
return /* @__PURE__ */ jsx(
|
|
1608
1613
|
CrudListView,
|
|
1609
1614
|
{
|
|
1610
|
-
headerActions: /* @__PURE__ */ jsx(
|
|
1611
|
-
TeachMeLink,
|
|
1612
|
-
{
|
|
1613
|
-
feature: "inventory.contagem",
|
|
1614
|
-
title: "Contagem de Estoque",
|
|
1615
|
-
about: "A contagem f\xEDsica contra o saldo do sistema. A diferen\xE7a vira ajuste com rastro: sobra ou falta, com o n\xFAmero do invent\xE1rio."
|
|
1616
|
-
}
|
|
1617
|
-
),
|
|
1618
1615
|
entityDef: entity,
|
|
1619
1616
|
items: loadedOnce ? rows : null,
|
|
1620
1617
|
total: rows.length,
|
|
@@ -2267,17 +2264,7 @@ function RecipesView({ onNew, onView, onNewForProduct }) {
|
|
|
2267
2264
|
search,
|
|
2268
2265
|
onSearchChange: setSearch,
|
|
2269
2266
|
searchPlaceholder: t2("inventory.recipes.searchPlaceholder"),
|
|
2270
|
-
headerActions: /* @__PURE__ */
|
|
2271
|
-
/* @__PURE__ */ jsx(
|
|
2272
|
-
TeachMeLink,
|
|
2273
|
-
{
|
|
2274
|
-
feature: "inventory.receitas",
|
|
2275
|
-
title: "Receitas / Ficha T\xE9cnica",
|
|
2276
|
-
about: "O que cada item consome. \xC9 a ficha t\xE9cnica que permite a venda baixar o insumo sozinha."
|
|
2277
|
-
}
|
|
2278
|
-
),
|
|
2279
|
-
/* @__PURE__ */ jsx(PermissionGate, { feature: "inventory", action: "create", children: /* @__PURE__ */ jsx(QuickActionsButton, { variant: "default", label: t2("inventory.recipes.newRecipe"), actions: createActions }) })
|
|
2280
|
-
] }),
|
|
2267
|
+
headerActions: /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsx(PermissionGate, { feature: "inventory", action: "create", children: /* @__PURE__ */ jsx(QuickActionsButton, { variant: "default", label: t2("inventory.recipes.newRecipe"), actions: createActions }) }) }),
|
|
2281
2268
|
onRowClick: (row) => {
|
|
2282
2269
|
const target = row;
|
|
2283
2270
|
if (target.missing && target.productId) onNewForProduct?.(target.productId);
|
|
@@ -3153,15 +3140,7 @@ function UsageAuditView() {
|
|
|
3153
3140
|
SubpageHeader,
|
|
3154
3141
|
{
|
|
3155
3142
|
title: t2("inventory.usageAudit.title"),
|
|
3156
|
-
subtitle: t2("inventory.usageAudit.subtitle")
|
|
3157
|
-
actions: /* @__PURE__ */ jsx(
|
|
3158
|
-
TeachMeLink,
|
|
3159
|
-
{
|
|
3160
|
-
feature: "inventory.auditoria-de-uso",
|
|
3161
|
-
title: "Auditoria de Uso",
|
|
3162
|
-
about: "O que os atendimentos consumiram de verdade, lado a lado com o que era o padr\xE3o. \xC9 depois da baixa: o atendimento j\xE1 consumiu, e corrigir lan\xE7a a diferen\xE7a no raz\xE3o. Digite na pr\xF3pria linha e aplique tudo de uma vez, com uma observa\xE7\xE3o s\xF3."
|
|
3163
|
-
}
|
|
3164
|
-
)
|
|
3143
|
+
subtitle: t2("inventory.usageAudit.subtitle")
|
|
3165
3144
|
}
|
|
3166
3145
|
),
|
|
3167
3146
|
/* @__PURE__ */ jsx(
|
|
@@ -3462,6 +3441,7 @@ function InventoryPage({ config, provider, store, registries }) {
|
|
|
3462
3441
|
return /* @__PURE__ */ jsx(InventoryContextProvider, { config, provider, store, children: /* @__PURE__ */ jsx(
|
|
3463
3442
|
ModulePage,
|
|
3464
3443
|
{
|
|
3444
|
+
navVariant: config.moduleNav,
|
|
3465
3445
|
title: config.labels.pageTitle,
|
|
3466
3446
|
subtitle: config.labels.pageSubtitle,
|
|
3467
3447
|
nav,
|
|
@@ -11143,11 +11123,271 @@ BEGIN
|
|
|
11143
11123
|
END;
|
|
11144
11124
|
$$;
|
|
11145
11125
|
`;
|
|
11126
|
+
var MIGRATION_004_A_POSICAO_DE_ESTOQUE_ATRAVESSA = `-- A posi\xE7\xE3o de estoque atravessa.
|
|
11127
|
+
--
|
|
11128
|
+
-- \`stock_positions\` \u2014 o saldo por produto \xD7 local \xD7 lote \u2014 estava no plano de
|
|
11129
|
+
-- leitura e sem destino: 1.861 linhas do tenant Pertinho do C\xE9u ficaram
|
|
11130
|
+
-- \`pendentes\` em toda rodada, nunca ofertadas a writer nenhum. A nota no
|
|
11131
|
+
-- extrator dizia "no V2 a posi\xE7\xE3o \xE9 derivada do movimento", e essa frase \xE9
|
|
11132
|
+
-- verdadeira e insuficiente.
|
|
11133
|
+
--
|
|
11134
|
+
-- Derivar o saldo do movimento s\xF3 d\xE1 o mesmo n\xFAmero quando o movimento
|
|
11135
|
+
-- atravessa INTEIRO. Neste tenant 2.208 dos 2.697 movimentos estavam em
|
|
11136
|
+
-- quarentena, ent\xE3o o saldo derivado seria o saldo de 18% do hist\xF3rico \u2014
|
|
11137
|
+
-- apresentado com a mesma confian\xE7a do saldo certo. E lote e validade, que a
|
|
11138
|
+
-- posi\xE7\xE3o carrega e o movimento n\xE3o, n\xE3o se derivam de soma nenhuma.
|
|
11139
|
+
--
|
|
11140
|
+
-- Ent\xE3o a posi\xE7\xE3o vem do V1 como fato, e n\xE3o como conta.
|
|
11141
|
+
--
|
|
11142
|
+
-- \u2500\u2500 O que o destino exige e a origem n\xE3o d\xE1 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11143
|
+
--
|
|
11144
|
+
-- \`plg_inventory_stock_positions\` tem tr\xEAs colunas NOT NULL que precisam de
|
|
11145
|
+
-- tradu\xE7\xE3o:
|
|
11146
|
+
--
|
|
11147
|
+
-- product_id \u2192 resolvido pelo ledger de \`products\`
|
|
11148
|
+
-- stock_location_id \u2192 pelo ledger de \`stock_locations\`
|
|
11149
|
+
-- unit_id \u2192 a unidade da EMPRESA (app.units), e o V1 guarda duas
|
|
11150
|
+
-- coisas com nome parecido: \`unit_id\` (a unidade de
|
|
11151
|
+
-- MEDIDA do item) e \`company_unit_id\` (a filial). \xC9 a
|
|
11152
|
+
-- segunda. Confundi-las poria a posi\xE7\xE3o na unidade
|
|
11153
|
+
-- errada \u2014 e num tenant de 63 filiais isso \xE9 saldo no
|
|
11154
|
+
-- lugar de outro.
|
|
11155
|
+
--
|
|
11156
|
+
-- Quando a filial n\xE3o vem na linha, ela vem do LOCAL: um local de estoque
|
|
11157
|
+
-- pertence a uma unidade, e o saldo que est\xE1 nele est\xE1 nela.
|
|
11158
|
+
--
|
|
11159
|
+
-- Idempotente.
|
|
11160
|
+
|
|
11161
|
+
-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11162
|
+
-- 1. O writer
|
|
11163
|
+
-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11164
|
+
|
|
11165
|
+
CREATE OR REPLACE FUNCTION migration.upsert_inv_position(
|
|
11166
|
+
p_tenant uuid, p_id uuid, p_row jsonb, p_args jsonb DEFAULT '{}'::jsonb
|
|
11167
|
+
) RETURNS jsonb
|
|
11168
|
+
LANGUAGE plpgsql SECURITY DEFINER SET search_path TO ''
|
|
11169
|
+
AS $function$
|
|
11170
|
+
DECLARE
|
|
11171
|
+
v_meta jsonb := coalesce(p_row -> 'metadata', '{}'::jsonb);
|
|
11172
|
+
v_product uuid := (p_row ->> 'product_id')::uuid;
|
|
11173
|
+
v_location uuid := (p_row ->> 'stock_location_id')::uuid;
|
|
11174
|
+
v_unit uuid := (p_row ->> 'unit_id')::uuid;
|
|
11175
|
+
v_qty numeric := round(coalesce((p_row ->> 'quantity')::numeric, 0), 4);
|
|
11176
|
+
v_existing_tenant uuid;
|
|
11177
|
+
v_created boolean := false;
|
|
11178
|
+
v_standalone boolean := migration._standalone();
|
|
11179
|
+
BEGIN
|
|
11180
|
+
IF v_standalone THEN PERFORM migration._assert_fence(p_tenant); PERFORM set_config('migration.writer', 'on', true); END IF;
|
|
11181
|
+
IF p_id IS NULL THEN
|
|
11182
|
+
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'state', 'reason', 'uma posi\xE7\xE3o precisa de id');
|
|
11183
|
+
END IF;
|
|
11184
|
+
IF v_product IS NULL THEN
|
|
11185
|
+
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'fk',
|
|
11186
|
+
'reason', 'posi\xE7\xE3o de saldo cujo produto ainda n\xE3o atravessou');
|
|
11187
|
+
END IF;
|
|
11188
|
+
IF v_location IS NULL THEN
|
|
11189
|
+
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'fk',
|
|
11190
|
+
'reason', 'posi\xE7\xE3o de saldo sem local \u2014 saldo sem lugar n\xE3o \xE9 saldo');
|
|
11191
|
+
END IF;
|
|
11192
|
+
|
|
11193
|
+
-- A filial pelo LOCAL quando a linha n\xE3o a traz: o local pertence a uma
|
|
11194
|
+
-- unidade, e o saldo que est\xE1 nele est\xE1 nela.
|
|
11195
|
+
IF v_unit IS NULL THEN
|
|
11196
|
+
SELECT l.unit_id INTO v_unit
|
|
11197
|
+
FROM public.plg_inventory_stock_locations l
|
|
11198
|
+
WHERE l.id = v_location AND l.tenant_id = p_tenant;
|
|
11199
|
+
END IF;
|
|
11200
|
+
IF v_unit IS NULL THEN
|
|
11201
|
+
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'fk',
|
|
11202
|
+
'reason', 'posi\xE7\xE3o sem unidade, e o local dela tamb\xE9m n\xE3o diz de qual \xE9');
|
|
11203
|
+
END IF;
|
|
11204
|
+
|
|
11205
|
+
-- \`quantity >= 0\` \xE9 CHECK da tabela. O V1 admite saldo negativo (sa\xEDda que
|
|
11206
|
+
-- n\xE3o conferiu com a entrada); recusar aqui, com a frase, \xE9 melhor que o erro
|
|
11207
|
+
-- cru do banco no meio do lote \u2014 e melhor que gravar zero, que apagaria a
|
|
11208
|
+
-- evid\xEAncia de que a contagem daquele item est\xE1 furada.
|
|
11209
|
+
IF v_qty < 0 THEN
|
|
11210
|
+
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'value',
|
|
11211
|
+
'reason', format('saldo negativo (%s) \u2014 o V1 admite, o raz\xE3o de estoque do V2 n\xE3o', v_qty));
|
|
11212
|
+
END IF;
|
|
11213
|
+
|
|
11214
|
+
SELECT tenant_id INTO v_existing_tenant FROM public.plg_inventory_stock_positions WHERE id = p_id;
|
|
11215
|
+
IF v_existing_tenant IS NOT NULL AND v_existing_tenant <> p_tenant THEN
|
|
11216
|
+
RETURN jsonb_build_object('status', 'quarantined', 'kind', 'tenant',
|
|
11217
|
+
'reason', format('%s already exists in another tenant', p_id));
|
|
11218
|
+
END IF;
|
|
11219
|
+
|
|
11220
|
+
IF v_existing_tenant IS NULL THEN
|
|
11221
|
+
INSERT INTO public.plg_inventory_stock_positions
|
|
11222
|
+
(id, tenant_id, unit_id, product_id, stock_location_id, quantity, unit_cost,
|
|
11223
|
+
batch_number, expiration_date, unit_type)
|
|
11224
|
+
VALUES (p_id, p_tenant, v_unit, v_product, v_location, v_qty,
|
|
11225
|
+
round(coalesce((p_row ->> 'unit_cost')::numeric, 0), 4),
|
|
11226
|
+
p_row ->> 'batch_number', (p_row ->> 'expiration_date')::date,
|
|
11227
|
+
coalesce(nullif(btrim(p_row ->> 'unit_type'), ''), 'base'));
|
|
11228
|
+
v_created := true;
|
|
11229
|
+
ELSE
|
|
11230
|
+
UPDATE public.plg_inventory_stock_positions SET
|
|
11231
|
+
unit_id = coalesce(v_unit, unit_id),
|
|
11232
|
+
product_id = coalesce(v_product, product_id),
|
|
11233
|
+
stock_location_id = coalesce(v_location, stock_location_id),
|
|
11234
|
+
quantity = CASE WHEN p_row ? 'quantity' THEN v_qty ELSE quantity END,
|
|
11235
|
+
unit_cost = CASE WHEN p_row ? 'unit_cost' THEN round(coalesce((p_row ->> 'unit_cost')::numeric, 0), 4) ELSE unit_cost END,
|
|
11236
|
+
batch_number = CASE WHEN p_row ? 'batch_number' THEN p_row ->> 'batch_number' ELSE batch_number END,
|
|
11237
|
+
expiration_date = CASE WHEN p_row ? 'expiration_date' THEN (p_row ->> 'expiration_date')::date ELSE expiration_date END,
|
|
11238
|
+
unit_type = CASE WHEN p_row ? 'unit_type' THEN coalesce(nullif(btrim(p_row ->> 'unit_type'), ''), 'base') ELSE unit_type END,
|
|
11239
|
+
updated_at = now()
|
|
11240
|
+
WHERE id = p_id;
|
|
11241
|
+
END IF;
|
|
11242
|
+
|
|
11243
|
+
IF v_standalone THEN PERFORM migration._audit(p_tenant, 'public.plg_inventory_stock_positions', p_id, jsonb_build_object('reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END)); END IF;
|
|
11244
|
+
RETURN jsonb_build_object('status', 'migrated', 'target_id', p_id, 'reason', CASE WHEN v_created THEN 'created' ELSE 'updated' END);
|
|
11245
|
+
END $function$;
|
|
11246
|
+
|
|
11247
|
+
-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11248
|
+
-- 2. O vocabul\xE1rio de writers aceita o nome novo
|
|
11249
|
+
-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11250
|
+
|
|
11251
|
+
DO $$
|
|
11252
|
+
DECLARE v_def text;
|
|
11253
|
+
BEGIN
|
|
11254
|
+
SELECT pg_get_constraintdef(oid) INTO v_def
|
|
11255
|
+
FROM pg_constraint WHERE conname = 'allowlist_writer_check';
|
|
11256
|
+
IF v_def IS NULL OR strpos(v_def, 'inv_position') > 0 THEN RETURN; END IF;
|
|
11257
|
+
ALTER TABLE migration.allowlist DROP CONSTRAINT allowlist_writer_check;
|
|
11258
|
+
EXECUTE replace(
|
|
11259
|
+
'ALTER TABLE migration.allowlist ADD CONSTRAINT allowlist_writer_check ' || v_def,
|
|
11260
|
+
'''inv_movement''::text',
|
|
11261
|
+
'''inv_movement''::text, ''inv_position''::text');
|
|
11262
|
+
END $$;
|
|
11263
|
+
|
|
11264
|
+
-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11265
|
+
-- 3. O de-para
|
|
11266
|
+
-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11267
|
+
--
|
|
11268
|
+
-- \`unit_id\` do V1 \xE9 a unidade de MEDIDA e n\xE3o a filial \u2014 por isso ela vai para
|
|
11269
|
+
-- metadata e quem responde pela filial \xE9 \`company_unit_id\`.
|
|
11270
|
+
|
|
11271
|
+
-- A allowlist \xE9 CONGELADA (\`trg_allowlist_frozen\`): mudan\xE7a de core-A \xE9
|
|
11272
|
+
-- arquivo de migration, n\xE3o escrita solta \u2014 e esta \xE9 um arquivo. A escotilha
|
|
11273
|
+
-- \xE9 transacional (\`true\` no terceiro argumento), ent\xE3o n\xE3o fica porta aberta.
|
|
11274
|
+
SELECT set_config('migration.allowlist_unlock', 'on', true);
|
|
11275
|
+
|
|
11276
|
+
SELECT migration._allow(
|
|
11277
|
+
'stock_positions', 'public.plg_inventory_stock_positions', 'inventory',
|
|
11278
|
+
'inv_position', '{}'::jsonb, ARRAY['quantity', 'unit_cost'],
|
|
11279
|
+
jsonb_build_object(
|
|
11280
|
+
'uuid', 'id',
|
|
11281
|
+
'product_id', 'product_id@products',
|
|
11282
|
+
'stock_location_id', 'stock_location_id@stock_locations',
|
|
11283
|
+
'company_unit_id', 'unit_id@companies:app.units',
|
|
11284
|
+
'quantity', 'quantity',
|
|
11285
|
+
'unit_cost', 'unit_cost',
|
|
11286
|
+
'batch_number', 'batch_number',
|
|
11287
|
+
'expiration_date', 'expiration_date',
|
|
11288
|
+
'unit_type', 'unit_type',
|
|
11289
|
+
'location', 'jsonb:v1.location_label',
|
|
11290
|
+
'unit_id', 'jsonb:v1.measurement_unit_ref'
|
|
11291
|
+
),
|
|
11292
|
+
'O SALDO por produto \xD7 local \xD7 lote. Vem da origem como FATO e n\xE3o derivado '
|
|
11293
|
+
'do movimento: o saldo derivado s\xF3 bate quando o movimento atravessa inteiro, '
|
|
11294
|
+
'e lote e validade a posi\xE7\xE3o carrega e o movimento n\xE3o.'
|
|
11295
|
+
);
|
|
11296
|
+
|
|
11297
|
+
SELECT set_config('migration.allowlist_unlock', 'off', true);
|
|
11298
|
+
|
|
11299
|
+
|
|
11300
|
+
-- \u2500\u2500 quem pode executar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11301
|
+
--
|
|
11302
|
+
-- Fun\xE7\xE3o nova nasce execut\xE1vel por PUBLIC, e \`anon\`/\`authenticated\` herdam
|
|
11303
|
+
-- dali. Estas s\xE3o de PORTE: SECURITY DEFINER, escrevem dados de qualquer
|
|
11304
|
+
-- inquilino, e uma delas alcan\xE7\xE1vel pela chave an\xF4nima \xE9 a base inteira
|
|
11305
|
+
-- alcan\xE7\xE1vel pela chave an\xF4nima. S\xF3 o service_role, que \xE9 quem roda a onda.
|
|
11306
|
+
REVOKE ALL ON FUNCTION migration.upsert_inv_position(uuid, uuid, jsonb, jsonb) FROM PUBLIC, anon, authenticated;
|
|
11307
|
+
GRANT EXECUTE ON FUNCTION migration.upsert_inv_position(uuid, uuid, jsonb, jsonb) TO service_role;
|
|
11308
|
+
`;
|
|
11309
|
+
var MIGRATION_005_A_POSICAO_E_CONSEQUENCIA_DO_MOVIMENTO = `-- A posi\xE7\xE3o \xE9 consequ\xEAncia do movimento.
|
|
11310
|
+
--
|
|
11311
|
+
-- Desfaz a migration 004, que estava errada \u2014 e o registro do porqu\xEA vale mais
|
|
11312
|
+
-- que o c\xF3digo que sai.
|
|
11313
|
+
--
|
|
11314
|
+
-- A 004 criou um writer para trazer \`stock_positions\` do V1 como FATO, com o
|
|
11315
|
+
-- argumento de que derivar o saldo do movimento s\xF3 d\xE1 o mesmo n\xFAmero quando o
|
|
11316
|
+
-- movimento atravessa inteiro. O argumento era bom e a premissa tinha deixado
|
|
11317
|
+
-- de valer no mesmo dia: os 2.697 movimentos passaram a atravessar inteiros
|
|
11318
|
+
-- assim que a entrada por DANFE ganhou o local (a ponte no extrator).
|
|
11319
|
+
--
|
|
11320
|
+
-- E o destino j\xE1 dizia isso, em voz alta:
|
|
11321
|
+
--
|
|
11322
|
+
-- plg_inventory_stock_positions is written only through the inventory RPCs
|
|
11323
|
+
-- (inventory_receive / _transfer \u2026)
|
|
11324
|
+
--
|
|
11325
|
+
-- 339 das 1.861 linhas bateram nesse gatilho. O gatilho est\xE1 certo: no V2 a
|
|
11326
|
+
-- posi\xE7\xE3o n\xE3o \xE9 algo que se escreve, \xE9 o que sobra depois que o movimento
|
|
11327
|
+
-- passou. Escrever por fora seria manter duas verdades sobre o mesmo saldo e
|
|
11328
|
+
-- deixar a diverg\xEAncia para depois.
|
|
11329
|
+
--
|
|
11330
|
+
-- \u2500\u2500 A prova \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11331
|
+
--
|
|
11332
|
+
-- Medido em 19/09/2026 no tenant Pertinho do C\xE9u, depois de os movimentos
|
|
11333
|
+
-- entrarem:
|
|
11334
|
+
--
|
|
11335
|
+
-- V1 stock_positions 1.861 linhas 251.561,2484
|
|
11336
|
+
-- V2 plg_inventory_stock_positions 990 linhas 251.561,2484
|
|
11337
|
+
--
|
|
11338
|
+
-- O SALDO \xE9 o mesmo at\xE9 o quarto decimal. A contagem de linhas difere porque o
|
|
11339
|
+
-- V1 guarda uma posi\xE7\xE3o por lote/validade e o V2 consolida por produto \xD7 local
|
|
11340
|
+
-- quando n\xE3o h\xE1 lote \u2014 duas formas de escrever o mesmo estoque, e \xE9 o estoque
|
|
11341
|
+
-- que se confere.
|
|
11342
|
+
--
|
|
11343
|
+
-- \u2500\u2500 O que sai e o que fica \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
11344
|
+
--
|
|
11345
|
+
-- Sai a linha de \`stock_positions\` da allowlist. Fica \`migration.excluded_tables\`
|
|
11346
|
+
-- com o motivo medido, para que a pr\xF3xima pessoa que vir 1.861 linhas paradas
|
|
11347
|
+
-- no dashboard leia a resposta em vez de refazer a investiga\xE7\xE3o.
|
|
11348
|
+
--
|
|
11349
|
+
-- O writer \`upsert_inv_position\` FICA. Ele n\xE3o faz mal parado, e um pool que
|
|
11350
|
+
-- porte posi\xE7\xE3o sem portar movimento vai precisar dele \u2014 o que muda \xE9 que este
|
|
11351
|
+
-- n\xE3o \xE9 esse caso.
|
|
11352
|
+
--
|
|
11353
|
+
-- Idempotente.
|
|
11354
|
+
|
|
11355
|
+
-- A linha est\xE1 CONGELADA, e o gatilho recusa DELETE nela pelo mesmo motivo que
|
|
11356
|
+
-- recusa UPDATE: mudan\xE7a de core-A \xE9 arquivo de migration. Este \xE9 o arquivo, e
|
|
11357
|
+
-- a escotilha \xE9 transacional \u2014 n\xE3o fica porta aberta depois dele.
|
|
11358
|
+
SELECT set_config('migration.allowlist_unlock', 'on', true);
|
|
11359
|
+
DELETE FROM migration.allowlist WHERE source_table = 'stock_positions';
|
|
11360
|
+
SELECT set_config('migration.allowlist_unlock', 'off', true);
|
|
11361
|
+
|
|
11362
|
+
-- \`WHERE tenant_id IS NULL\` no \xE1rbitro: a 073 da espinha trocou a PK de
|
|
11363
|
+
-- \`migration.excluded_tables\` por dois \xEDndices \xFAnicos PARCIAIS \u2014 um global e
|
|
11364
|
+
-- um por inquilino \u2014, porque "n\xE3o trazer esta tabela" passou a ser decis\xE3o de
|
|
11365
|
+
-- cliente e n\xE3o do cluster. \xCDndice parcial s\xF3 serve de \xE1rbitro de ON CONFLICT
|
|
11366
|
+
-- quando o comando repete o mesmo recorte. Esta decis\xE3o \xE9 global.
|
|
11367
|
+
INSERT INTO migration.excluded_tables (source_table, reason)
|
|
11368
|
+
VALUES ('stock_positions',
|
|
11369
|
+
'Consequ\xEAncia, n\xE3o fato: no V2 a posi\xE7\xE3o s\xF3 se escreve pelas RPCs de '
|
|
11370
|
+
'invent\xE1rio (o gatilho recusa INSERT direto), e ela se forma sozinha '
|
|
11371
|
+
'quando o movimento atravessa. Conferido em 19/09/2026 no Pertinho do '
|
|
11372
|
+
'C\xE9u \u2014 V1 1.861 linhas / 251.561,2484 contra V2 990 linhas / '
|
|
11373
|
+
'251.561,2484: mesmo saldo, formas diferentes de agrupar (o V1 guarda '
|
|
11374
|
+
'por lote, o V2 consolida sem lote).')
|
|
11375
|
+
-- \`WHERE tenant_id IS NULL\` porque a PK de \`excluded_tables\` deixou de existir:
|
|
11376
|
+
-- o spine 070 a trocou por dois \xEDndices \xFAnicos PARCIAIS \u2014 um por tabela global,
|
|
11377
|
+
-- outro por tabela e tenant. \`ON CONFLICT (source_table)\` sozinho n\xE3o casa com
|
|
11378
|
+
-- \xEDndice parcial, e a cadeia do zero parou aqui com "there is no unique or
|
|
11379
|
+
-- exclusion constraint matching the ON CONFLICT specification".
|
|
11380
|
+
--
|
|
11381
|
+
-- Esta linha \xE9 global (sem tenant), ent\xE3o \xE9 o \xEDndice global que ela quer.
|
|
11382
|
+
ON CONFLICT (source_table) WHERE tenant_id IS NULL DO UPDATE SET reason = EXCLUDED.reason;
|
|
11383
|
+
`;
|
|
11146
11384
|
var MIGRATIONS = [
|
|
11147
11385
|
{ id: "000_baseline", sql: MIGRATION_000_BASELINE },
|
|
11148
11386
|
{ id: "001_the_usage_that_left_stock_can_be_audited", sql: MIGRATION_001_THE_USAGE_THAT_LEFT_STOCK_CAN_BE_AUDITED },
|
|
11149
11387
|
{ id: "002_a_auditoria_fecha_em_lote", sql: MIGRATION_002_A_AUDITORIA_FECHA_EM_LOTE },
|
|
11150
|
-
{ id: "003_stock_count_closes_through_ledger", sql: MIGRATION_003_STOCK_COUNT_CLOSES_THROUGH_LEDGER }
|
|
11388
|
+
{ id: "003_stock_count_closes_through_ledger", sql: MIGRATION_003_STOCK_COUNT_CLOSES_THROUGH_LEDGER },
|
|
11389
|
+
{ id: "004_a_posicao_de_estoque_atravessa", sql: MIGRATION_004_A_POSICAO_DE_ESTOQUE_ATRAVESSA },
|
|
11390
|
+
{ id: "005_a_posicao_e_consequencia_do_movimento", sql: MIGRATION_005_A_POSICAO_E_CONSEQUENCIA_DO_MOVIMENTO }
|
|
11151
11391
|
];
|
|
11152
11392
|
|
|
11153
11393
|
// src/index.ts
|
|
@@ -11184,7 +11424,8 @@ function resolveConfig(options) {
|
|
|
11184
11424
|
labels: { ...DEFAULT_LABELS, ...options?.labels },
|
|
11185
11425
|
currency: { ...DEFAULT_CURRENCY, ...options?.currency },
|
|
11186
11426
|
productTypes: options?.productTypes ?? DEFAULT_PRODUCT_TYPES,
|
|
11187
|
-
locations: options?.locations ?? []
|
|
11427
|
+
locations: options?.locations ?? [],
|
|
11428
|
+
moduleNav: options?.moduleNav
|
|
11188
11429
|
};
|
|
11189
11430
|
}
|
|
11190
11431
|
function createInventoryDataProvider() {
|
|
@@ -11202,6 +11443,13 @@ function createInventoryPlugin(options) {
|
|
|
11202
11443
|
const PageComponent = () => React3.createElement(InventoryPage, { config, provider, store, registries: inventoryRegistries });
|
|
11203
11444
|
return {
|
|
11204
11445
|
id: "inventory",
|
|
11446
|
+
// Como esta conta refaz o módulo com as opções DELA. Ver PluginRebuild:
|
|
11447
|
+
// a fábrica roda no carregamento do aplicativo, muito antes de a
|
|
11448
|
+
// configuração da conta existir, e sem isto `app.tenant_plugins.config`
|
|
11449
|
+
// não tinha como chegar aqui. A fusão é RASA e por bloco — a conta troca
|
|
11450
|
+
// `modules` inteiro ou não o troca —, que é o mesmo contrato que a chamada
|
|
11451
|
+
// do aplicativo já tem.
|
|
11452
|
+
rebuild: (tenantConfig) => createInventoryPlugin({ ...options, ...tenantConfig }),
|
|
11205
11453
|
defaultAgentRole: "operations",
|
|
11206
11454
|
name: config.labels.pageTitle,
|
|
11207
11455
|
icon: "Package",
|