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:
2026-05-30 14:08:56 +00:00
parent 70d5a2d1e4
commit 1647871a39
6 changed files with 2176 additions and 862 deletions

View File

@@ -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`.