feat(web+api): redesign ClientsPage/OrdersPage e corrige dados empresa 9001
Web — ClientsPage: - Redesign completo: métricas reais via usePortfolioStats (4 queries count), donut Chart.js com totais reais, tabela sem ellipsis, coluna Cliente com nome fantasia/razão/CNPJ completos, drawer de detalhes e análise comercial, cards mobile, filtros de status/busca em tempo real. - Dados reais: substitui mock por useClientList/useClientDetail/useClientOrders; remove tipos fictícios (prospect/lead, cidade, totalComprado). Web — OrdersPage: - Métricas reais via useOrderStats (contagens por situa, não da página atual). - Coluna Cliente sem truncamento (minWidth: 240). - Cabeçalho, filtros e layout alinhados ao padrão da ClientsPage. API — orders.service.ts: - Normalização situa SIG→SAR: SIG usa 5=Cancelado; SAR usa 3=Cancelado. sigToSar(5→3) no mapper; sarToSig(3→5) no filtro SQL. API — clients.service.ts: - dt_ultima_compra corrigida: JOIN duplo (vw_pedidos_erp + sar.pedidos) com GREATEST() — clientes com histórico ERP mas sem pedido SAR deixam de aparecer todos como Inativo. - Filtro de activityStatus movido para SQL — total e paginação corretos. - findOne() atualizado com o mesmo JOIN duplo. Infra — .env: - DEV_EMPRESA_ID: 1 → 9001 — API aponta para dados reais da empresa SIG. Ex: pedido nº 141022 passa de R$1.765,48 para R$2.454,90. Docs — sarweb_views.sql: - Documenta as views reais em schema sar; remove schema sarweb inexistente. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
# Investigation: Pedidos — Valores incorretos e vinculação clientes × vendedor
|
||||
|
||||
## Hand-off Brief
|
||||
|
||||
1. **O que aconteceu.** Pedidos exibem valores/status errados e a vinculação cliente↔vendedor está inconsistente; suspeita confirmada de 5 bugs distintos, sendo o mais grave o mismatch de códigos `situa` entre ERP e SAR.
|
||||
2. **Onde o caso está.** Cinco problemas confirmados ou fortemente deduzidos; `vw_pedidos_erp` (view central usada pelo service) **não está definida em nenhum arquivo de código** — sua definição existe apenas no banco.
|
||||
3. **O que é necessário agora.** Verificar a DDL de `vw_pedidos_erp` diretamente no banco (`\d+ sarweb.vw_pedidos_erp`) e confirmar se ela normaliza `situa` — isso resolve ou agrava o Bug #1.
|
||||
|
||||
## Case Info
|
||||
|
||||
| Campo | Valor |
|
||||
| ---------------- | ------------------------------------------------------------------ |
|
||||
| Ticket | N/A |
|
||||
| Data | 2026-05-30 |
|
||||
| Status | Active |
|
||||
| Sistema | Node 24 / NestJS 11 / Prisma 7 / PostgreSQL (SIG+GERENTE schemas) |
|
||||
| Fontes | orders.service.ts, clients.service.ts, sarweb_views.sql, schema.prisma, .env, jwt-auth.guard.ts |
|
||||
|
||||
## Problem Statement
|
||||
|
||||
Usuário relata: "pedidos com valores errados e vinculação clientes × vendedor"; complementou que a empresa usa `id_empresa = 9001` (schema SIG) e a empresa gerencial é a `1` (schema GERENTE), e que às vezes dados estão duplicando nas telas.
|
||||
|
||||
## Evidence Inventory
|
||||
|
||||
| Fonte | Status | Notas |
|
||||
| --------------------------------------- | ---------- | -------------------------------------------------------- |
|
||||
| `orders.service.ts` | Available | Lido completo — queries brutas sem esquema qualificado |
|
||||
| `clients.service.ts` | Available | Lido completo |
|
||||
| `sarweb_views.sql` | Available | Define `vw_pedidos`, NÃO define `vw_pedidos_erp` |
|
||||
| `schema.prisma` | Available | Schema `sar` no PG; tabela `pedidos` é SAR-only |
|
||||
| `jwt-auth.guard.ts` | Available | Em dev usa `DEV_EMPRESA_ID` e `DEV_REP_CODE` do `.env` |
|
||||
| `.env` | Available | `DEV_EMPRESA_ID=1`, `DEV_REP_CODE=29` |
|
||||
| DDL de `vw_pedidos_erp` no banco | **Missing** | Não está no repo — requer `\d+` direto no PG |
|
||||
| Logs do PG / EXPLAIN com id_empresa=9001| **Missing** | Requer execução manual |
|
||||
|
||||
## Confirmed Findings
|
||||
|
||||
### Finding 1: `DEV_EMPRESA_ID=1` mas a empresa real é SIG (id=9001)
|
||||
|
||||
**Evidência:** `.env:DEV_EMPRESA_ID=1` e `jwt-auth.guard.ts:56` — em dev, `idEmpresa` é forçado para `1` (GERENTE), ignorando o JWT.
|
||||
|
||||
**Detalhe:** Em desenvolvimento, todas as queries usam `WHERE id_empresa = 1` e buscam dados do schema GERENTE. A empresa real do usuário está no schema SIG com `id_empresa = 9001`. Portanto, os testes locais estão apontando para dados diferentes dos de produção.
|
||||
|
||||
**Fix imediato:** Alterar `.env`: `DEV_EMPRESA_ID=9001`.
|
||||
|
||||
---
|
||||
|
||||
### Finding 2: `vw_pedidos_erp` não existe em nenhum arquivo do repositório
|
||||
|
||||
**Evidência:**
|
||||
- `orders.service.ts:77` — `FROM vw_pedidos_erp e`
|
||||
- `sarweb_views.sql` — define `sarweb.vw_pedidos`, não `vw_pedidos_erp`
|
||||
- `grep -rn "vw_pedidos_erp"` → apenas `orders.service.ts` e `dashboard.service.ts`; zero arquivos SQL
|
||||
|
||||
**Detalhe:** A view existe somente no banco (criada manualmente). Seu comportamento exato — especialmente se normaliza `situa` do ERP para valores SAR — é desconhecido sem inspecionar o banco.
|
||||
|
||||
---
|
||||
|
||||
### Finding 3: Mismatch de códigos `situa` entre ERP e SAR
|
||||
|
||||
**Evidência:** `sarweb_views.sql:367-378` (GERENTE) vs `sarweb_views.sql:411-418` (SIG) vs `order.contract.ts:13-18` (SAR).
|
||||
|
||||
| Situa | GERENTE ERP | SIG ERP | SAR (app) |
|
||||
|-------|-------------|-------------|-----------------|
|
||||
| 1 | Pendente | Pendente | Ag. Aprovação |
|
||||
| 2 | Liberado | Liberado | Aprovado |
|
||||
| 3 | **Faturado**| Liberado | **Cancelado** |
|
||||
| 4 | **Cancelado**| **Faturado**| **Faturado** |
|
||||
| 5 | — | **Cancelado**| — |
|
||||
|
||||
**Efeito confirmado (GERENTE):** Um pedido FATURADO no ERP (`situa=3`) exibe cor **vermelha** (Cancelado) no SAR — porque `OrderStatusBadge` usa o número cru para definir `tagColor`. O texto pode estar correto (via `statusDescr`) mas a cor é errada.
|
||||
|
||||
**Efeito no filtro de status:** `situaFilter = situa != null ? 'AND e.situa = ${situa}' : ''` — se o usuário filtra `situa=3` (SAR=Cancelado), recebe pedidos FATURADOS do GERENTE.
|
||||
|
||||
---
|
||||
|
||||
### Finding 4: `dt_ultima_compra` ignorando histórico ERP — `activityStatus` sempre errado
|
||||
|
||||
**Evidência:** `clients.service.ts:100` —
|
||||
```sql
|
||||
LEFT JOIN pedidos p ON p.id_cliente = c.id_cliente
|
||||
AND p.id_empresa = c.id_empresa
|
||||
AND p.situa != 3
|
||||
```
|
||||
`pedidos` aqui é a tabela SAR (`sar.pedidos`, Prisma), não os pedidos históricos do ERP.
|
||||
|
||||
**Detalhe:** Clientes que têm anos de histórico no ERP mas nunca fizeram pedido pelo SAR ficam com `dt_ultima_compra = NULL` → `activityStatus = 'inactive'`. Portanto, a carteira inteira aparece como **"Inativo"** na tela de Clientes.
|
||||
|
||||
**Fix necessário:** O JOIN deve usar `vw_pedidos_erp` (ou equivalente) em vez da tabela SAR.
|
||||
|
||||
---
|
||||
|
||||
### Finding 5: Filtro por `status` de clientes aplicado DEPOIS da paginação SQL
|
||||
|
||||
**Evidência:** `clients.service.ts:138` —
|
||||
```typescript
|
||||
if (status) mapped = mapped.filter((c) => c.activityStatus === status);
|
||||
```
|
||||
O `total` vem de `SELECT COUNT(*)` sem o filtro de status (`clients.service.ts:114-122`).
|
||||
|
||||
**Efeito:** Ao filtrar por `status=active`, a API retorna menos itens que `limit` (ex.: 8 de 50), mas `total` ainda diz 2606. A paginação fica quebrada e a portfólio card mostra totais incorretos quando usa `useClientList({ limit:1, status:X })`.
|
||||
|
||||
---
|
||||
|
||||
## Hypothesized Paths
|
||||
|
||||
### Hypothesis 1: `vw_pedidos_erp` causa duplicação via UNION sem filtro por empresa
|
||||
|
||||
**Status:** Open
|
||||
|
||||
**Teoria:** `vw_pedidos_erp` faz UNION ALL de GERENTE + SIG sem filtrar por `id_empresa`, e para uma empresa que existe nos dois schemas (id=1 e id=9001), as mesmas ordens aparecem duas vezes.
|
||||
|
||||
**Confirmaria:** `\d+ sarweb.vw_pedidos_erp` mostrando UNION ALL sem cláusula WHERE por empresa, + query retornando duplicatas com `id_empresa` distintos.
|
||||
|
||||
**Refutaria:** View com UNION ALL onde cada SELECT tem `AND id_empresa = X` fixo.
|
||||
|
||||
---
|
||||
|
||||
### Hypothesis 2: SAR-created orders nunca aparecem na listagem
|
||||
|
||||
**Status:** Open
|
||||
|
||||
**Teoria:** `orders.service.ts list()` só consulta `vw_pedidos_erp` (ERP), nunca `sar.pedidos` (Prisma). Pedidos criados pelo SAR somem da lista após criação.
|
||||
|
||||
**Confirmaria:** Criar pedido via SAR → abrir `/pedidos` → pedido não aparece na lista.
|
||||
|
||||
**Refutaria:** `vw_pedidos_erp` inclui `sar.pedidos` via UNION ALL.
|
||||
|
||||
---
|
||||
|
||||
## Missing Evidence
|
||||
|
||||
| Gap | Impacto | Como obter |
|
||||
| -------------------------------- | -------------------------------------------- | --------------------------------------------- |
|
||||
| DDL de `vw_pedidos_erp` | Confirma/refuta H1, H2 e Bug #3 (situa) | `\d+ sarweb.vw_pedidos_erp` no psql |
|
||||
| Query real com id_empresa=9001 | Confirma duplicação e valores | Rodar GET /orders com JWT prod ou DEV_ID=9001 |
|
||||
| Confirmar se `sar.pedidos` aparece na lista | Confirma H2 | Criar pedido SAR → verificar lista /orders |
|
||||
|
||||
## Source Code Trace
|
||||
|
||||
| Elemento | Detalhe |
|
||||
| -------------- | ------------------------------------------------------------- |
|
||||
| Bug #1 origem | `jwt-auth.guard.ts:56` — `DEV_EMPRESA_ID` sobrescreve JWT |
|
||||
| Bug #2 origem | Banco de dados (DDL não versionada) |
|
||||
| Bug #3 origem | `orders.service.ts:43,45,98-99` — `situa` cru do ERP |
|
||||
| Bug #4 origem | `clients.service.ts:100` — JOIN com `sar.pedidos` (não ERP) |
|
||||
| Bug #5 origem | `clients.service.ts:138` — filter JS pós-paginação SQL |
|
||||
|
||||
## Conclusion
|
||||
|
||||
**Confidence:** Medium (root causes identificadas; DDL de `vw_pedidos_erp` é peça faltante)
|
||||
|
||||
Cinco bugs confirmados ou fortemente deduzidos explicam os sintomas:
|
||||
|
||||
1. **DEV aponta para empresa errada** (`DEV_EMPRESA_ID=1` vs real=9001) — dados diferentes entre dev e prod.
|
||||
2. **`situa` ERP ≠ SAR** — cores/filtros de status errados para pedidos históricos.
|
||||
3. **`dt_ultima_compra` ignora ERP** — carteira toda aparece inativa.
|
||||
4. **`status` filter pós-paginação** — totais e paginação quebrados.
|
||||
5. **`vw_pedidos_erp` não versionada** — comportamento opaco, possível fonte de duplicação.
|
||||
|
||||
## Recommended Next Steps
|
||||
|
||||
### Fix imediato (sem risco)
|
||||
Alterar `.env`: `DEV_EMPRESA_ID=9001` para que dev espelhe produção.
|
||||
|
||||
### Antes de qualquer outro fix: verificar `vw_pedidos_erp` no banco
|
||||
```sql
|
||||
-- Rodar diretamente no psql:
|
||||
\d+ sarweb.vw_pedidos_erp
|
||||
-- ou
|
||||
SELECT pg_get_viewdef('sarweb.vw_pedidos_erp', true);
|
||||
```
|
||||
O resultado define o caminho dos próximos fixes.
|
||||
|
||||
### Fix #3 — `situa` — normalizar na `vw_pedidos_erp` (ou no service)
|
||||
Adicionar CASE na view (ou no service) mapeando ERP situa → SAR situa:
|
||||
```sql
|
||||
-- GERENTE: 2→2, 3→4(Faturado), 4→3(Cancelado)
|
||||
-- SIG: 2→2, 4→4(Faturado), 5→3(Cancelado)
|
||||
```
|
||||
|
||||
### Fix #4 — `dt_ultima_compra` — usar ERP orders
|
||||
Em `clients.service.ts:100`, substituir `pedidos` por `vw_pedidos_erp` (ou a view equivalente):
|
||||
```sql
|
||||
LEFT JOIN vw_pedidos_erp p
|
||||
ON p.id_cliente = c.id_cliente
|
||||
AND p.id_empresa = c.id_empresa
|
||||
AND p.situa NOT IN (3, 4, 5) -- situa=cancelado nos dois sistemas
|
||||
```
|
||||
|
||||
### Fix #5 — `status` filter — mover para SQL
|
||||
Em `clients.service.ts`, incluir o filtro de `activityStatus` na query SQL via subquery ou CTE com `dt_ultima_compra`, eliminando o filter JS pós-paginação.
|
||||
|
||||
## Reproduction Plan
|
||||
|
||||
1. Alterar `DEV_EMPRESA_ID=9001` no `.env`
|
||||
2. Reiniciar a API
|
||||
3. Abrir `/clientes` → verificar se clientes aparecem e se `activityStatus` faz sentido
|
||||
4. Abrir `/pedidos` → verificar se pedidos aparecem e se status está correto
|
||||
5. Rodar `\d+ sarweb.vw_pedidos_erp` no banco e trazer o resultado para continuar a investigação
|
||||
|
||||
## Side Findings
|
||||
|
||||
- `ALERT_DAYS=30` e `INACTIVE_DAYS=60` estão hardcoded em `clients.service.ts:11-12`. O comentário diz "Configuráveis por empresa futuramente" — tarefa pendente.
|
||||
- `orders.service.ts:46` usa interpolação de string direta para `numPedSar` (ILIKE). O `escSql()` de `clients.service.ts:24` não é reutilizado aqui — potencial SQL injection menor para campo de busca.
|
||||
- `vw_pedidos` em `sarweb_views.sql` não é usada em lugar nenhum do código fonte — `vw_pedidos_erp` é usada em seu lugar. Possível que `vw_pedidos_erp` seja um rename ou extensão da `vw_pedidos`.
|
||||
Reference in New Issue
Block a user