feat: Arcádia Suite v2.0 - Sistema completo com 66 páginas, 6 motores, módulo Retail

This commit is contained in:
jonaspachecoometas 2026-02-26 11:38:24 -03:00
parent 065ab19a17
commit ec173da844
12 changed files with 4878 additions and 359 deletions

65
.gitignore vendored
View File

@ -1,8 +1,65 @@
node_modules
dist
node_modules/
dist/
.DS_Store
server/public
.env
.env.local
.env.production
# Replit specific
.cache/
.upm/
.config/
.local/
.pythonlibs/
.replit
replit.nix
# Build artifacts
server/public/
vite.config.ts.*
# Backups and archives
*.tar.gz
*.zip
plus_backup_*/
# Metabase
metabase/metabase.jar
metabase/metabase-data.*
metabase/plugins/
metabase/plugins/
# Python
__pycache__/
*.pyc
*.pyo
.venv/
uv.lock
# WhatsApp sessions
whatsapp-sessions/
# Uploads (user-generated content)
uploads/
plus/public/uploads/
plus/storage/logs/
# PHP dependencies (install via composer)
plus/vendor/
# Logs
*.log
# IDE
.idea/
.vscode/
*.swp
*.swo
# OS files
Thumbs.db
# Attached assets
attached_assets/
# Package lock (regenerated on install)
package-lock.json

705
MAPA_GERAL_RETAIL.md Normal file
View File

@ -0,0 +1,705 @@
# MAPA GERAL DO SISTEMA RETAIL - Arcádia Suite
---
## 1. VISÃO MACRO DO MÓDULO
```
╔══════════════════════════════════════════════════════════════════════════════════╗
║ ARCÁDIA RETAIL - MAPA GERAL ║
║ Loja de Celulares e Assistência Técnica ║
╠══════════════════════════════════════════════════════════════════════════════════╣
║ ║
║ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ║
║ │DASHBOARD│ │ PDV │ │ PESSOAS │ │ ESTOQUE │ │SERVIÇOS │ ║
║ │ (KPIs) │ │ (Caixa) │ │(Cadastro│ │(Depósito│ │ (O.S.) │ ║
║ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ ║
║ │ │ │ │ │ ║
║ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ┌────┴────┐ ║
║ │TRADE-IN │ │ COMPRAS │ │CADASTROS│ │RELATÓRIO│ │COMISSÕES│ ║
║ │(Aval.) │ │(Aquis.) │ │ (Config)│ │(Reports)│ │ (Metas) │ ║
║ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ ║
║ │ │ │ │ │ ║
║ └────────────┴────────────┴─────┬──────┴────────────┘ ║
║ │ ║
║ ┌─────────┴─────────┐ ║
║ │ CONFIGURAÇÃO │ ║
║ │ Plus / Empresas │ ║
║ └───────────────────┘ ║
╚══════════════════════════════════════════════════════════════════════════════════╝
```
---
## 2. FLUXO PRINCIPAL DE OPERAÇÕES
```
┌──────────────────────────────────────────────┐
│ ENTRADA DE MERCADORIA │
└──────────────────────┬───────────────────────┘
┌──────────────────────┬┴──────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ COMPRA │ │ TRADE-IN │ │ CONSIGNAÇÃO │
│ Fornecedor │ │ Cliente │ │ Parceiro │
└──────┬───────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
│ ┌──────┴───────┐ │
│ │ Avaliação │ │
│ │ 19 itens │ │
│ │ checklist │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────┴───────┐ │
│ │ Aprovação │ │
│ │ Gera Crédito│ │
│ │ Cria O.S. │ │
│ └──────┬───────┘ │
│ │ │
│ ┌──────┴───────┐ │
│ │ Revisão │ │
│ │ O.S. Interna│ │
│ │ Manutenção │ │
│ └──────┬───────┘ │
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────┐
│ ESTOQUE │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Depósito │ │Depósito │ │Depósito │ │
│ │ Loja │ │ Central │ │Trânsito │ │
│ └────┬────┘ └────┬────┘ └────┬────┘ │
│ └────────────┼────────────┘ │
│ │ │
│ ┌────────────────┬┴──────────────────┐ │
│ │ Dispositivos │ Produtos │ │
│ │ (por IMEI) │ (por qtd/série) │ │
│ └────────────────┴───────────────────┘ │
└────────────────────┬────────────────────────────────┘
┌─────────────────────────────────────────────────────┐
│ PDV │
│ │
│ ┌────────────┐ ┌────────────┐ ┌──────────────┐ │
│ │Dispositivos│ │ Produtos │ │ Faturar O.S. │ │
│ │ (IMEI) │ │(Acessórios)│ │ (Serviços) │ │
│ └─────┬──────┘ └─────┬──────┘ └──────┬───────┘ │
│ └───────────────┼────────────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ CARRINHO │ │
│ │ │ │
│ │ Subtotal │ │
│ │ - Desconto │ │
│ │ - Trade-In │ │
│ │ - Crédito │ │
│ │ = TOTAL │ │
│ └────────┬─────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ PAGAMENTO │ │
│ │ 💵 Dinheiro │ │
│ │ 💳 Débito │ │
│ │ 💳 Crédito (Nx) │ │
│ │ 📱 PIX │ │
│ │ 🔀 Combinado │ │
│ └────────┬─────────┘ │
│ ▼ │
│ ┌──────────────────┐ │
│ │ VENDA │ │
│ │ Impressão A4 │ │
│ │ Sync Plus │ │
│ │ NF-e/NFC-e │ │
│ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
┌────────────┼────────────────┐
▼ ▼ ▼
┌────────────┐ ┌─────────┐ ┌────────────┐
│ COMISSÃO │ │RELATÓRIO│ │ DEVOLUÇÃO │
│ Vendedor │ │Caixa │ │ Troca │
│ Metas │ │Diário │ │ Crédito │
└────────────┘ └─────────┘ └────────────┘
```
---
## 3. CICLO DE VIDA DO DISPOSITIVO (IMEI)
```
┌─────────────────────────────────────────────────────────────────────────┐
│ JORNADA DO DISPOSITIVO POR IMEI │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ENTRADA │
│ ═══════ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ COMPRA │ │ TRADE-IN │ │ CONSIGNAÇÃO │ │
│ │ Fornecedor │ │ Cliente │ │ Parceiro │ │
│ │ condition: │ │ condition: │ │ condition: │ │
│ │ new │ │ used/refurb │ │ varies │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ │ ┌──────┴───────┐ │ │
│ │ │ AVALIAÇÃO │ │ │
│ │ │ Checklist │ │ │
│ │ │ 19 itens │ │ │
│ │ └──────┬───────┘ │ │
│ │ │ │ │
│ │ ┌──────┴───────┐ │ │
│ │ │ O.S. INT. │ │ │
│ │ │ Revisão │ │ │
│ │ │ Manutenção │ │ │
│ │ └──────┬───────┘ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ESTOQUE (status: in_stock) │ │
│ │ │ │
│ │ IMEI: 35XXXXXXXXXXXXX │ │
│ │ Marca: Samsung | Modelo: S24 Ultra │ │
│ │ Cor: Preto | Storage: 256GB | RAM: 12GB │ │
│ │ Condição: refurbished │ │
│ │ Preço Compra: R$ 2.500 | Preço Venda: R$ 3.500 │ │
│ │ Depósito: Loja Centro │ │
│ └──────────────────────┬──────────────────────────────┘ │
│ │ │
│ MOVIMENTAÇÕES │ │
│ ═══════════════ │ │
│ │ │
│ ┌──────────┐ ┌──────┴──────┐ ┌──────────────┐ │
│ │TRANSFER. │◄───│ DISPONÍVEL │───►│ ASSISTÊNCIA │ │
│ │Entre lojas│ │ para venda │ │ O.S. cliente│ │
│ │in_transit │ │ in_stock │ │ in_service │ │
│ └──────────┘ └──────┬──────┘ └──────┬───────┘ │
│ │ │ │
│ │ │ (retorna após reparo) │
│ │ └──────────┐ │
│ │ │ │
│ SAÍDA ▼ ▼ │
│ ═════ ┌──────────────┐ ┌──────────────┐ │
│ │ VENDA │ │ DE VOLTA AO │ │
│ │ PDV + NF-e │ │ ESTOQUE │ │
│ │ status:sold │ │ in_stock │ │
│ └──────┬───────┘ └──────────────┘ │
│ │ │
│ ┌──────┴───────┐ │
│ │ DEVOLUÇÃO? │ │
│ │ returned │──────────► Volta ao estoque │
│ └──────────────┘ │
│ │
│ RASTREAMENTO: device_history + imei_history (kardex completo) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
---
## 4. FLUXO DO TRADE-IN (4 ETAPAS DETALHADAS)
```
╔═══════════════════════════════════════════════════════════════════════════╗
║ TRADE-IN - FLUXO COMPLETO ║
╠═══════════════════════════════════════════════════════════════════════════╣
║ ║
║ ETAPA 1: AVALIAÇÃO ║
║ ══════════════════ ║
║ ║
║ ┌────────────────────────────────────────────────────────────┐ ║
║ │ Cliente chega com dispositivo usado │ ║
║ │ │ ║
║ │ 1. Identificar cliente (busca por nome/CPF) │ ║
║ │ 2. Registrar IMEI, Marca, Modelo │ ║
║ │ 3. Preencher Checklist (19 itens): │ ║
║ │ │ ║
║ │ ┌──────────────────────────────────────────────────────┐ │ ║
║ │ │ ☐ Liga corretamente ☐ Sensores/NFC │ │ ║
║ │ │ ☐ Sem avarias/fantasma ☐ Face ID/Touch ID │ │ ║
║ │ │ ☐ Sem manchas na tela ☐ Microfones │ │ ║
║ │ │ ☐ Botões funcionando ☐ Áudio auricular │ │ ║
║ │ │ ☐ Marcas de uso ☐ Alto-falante │ │ ║
║ │ │ ☐ Wi-Fi ☐ Carregamento │ │ ║
║ │ │ ☐ Chip ☐ Câmeras │ │ ║
║ │ │ ☐ 4G/5G ☐ Flash │ │ ║
║ │ │ ☐ Possui carregador ☐ 3uTools OK │ │ ║
║ │ │ 🔋 Bateria: ____% │ │ ║
║ │ └──────────────────────────────────────────────────────┘ │ ║
║ │ │ ║
║ │ 4. Listar peças necessárias (se houver) │ ║
║ │ 5. Definir valor estimado │ ║
║ │ 6. Assinatura digital do cliente │ ║
║ │ 7. Imprimir comprovante │ ║
║ │ │ ║
║ │ Status: PENDING │ ║
║ └────────────────────────────────────────────┬───────────────┘ ║
║ │ ║
║ ▼ ║
║ ETAPA 2: APROVAÇÃO ║
║ ══════════════════ ║
║ ║
║ ┌────────────────────────────────────────────────────────────┐ ║
║ │ Gerente revisa avaliação e decide: │ ║
║ │ │ ║
║ │ [APROVAR] [REJEITAR] │ ║
║ │ │ │ │ ║
║ │ ▼ ▼ │ ║
║ │ Automaticamente: Avaliação │ ║
║ │ ✅ Gera CRÉDITO para cliente rejeitada. │ ║
║ │ (R$ do valor estimado) Sem ações. │ ║
║ │ ✅ Cria O.S. INTERNA │ ║
║ │ (INT2602XXXXXX) │ ║
║ │ ✅ Registra no IMEI History │ ║
║ │ │ ║
║ │ Status: APPROVED │ ║
║ └────────────────────────────────────────────┬───────────────┘ ║
║ │ ║
║ ▼ ║
║ ETAPA 3: REVISÃO / MANUTENÇÃO ║
║ ═════════════════════════════ ║
║ ║
║ ┌────────────────────────────────────────────────────────────┐ ║
║ │ Técnico trabalha na O.S. Interna: │ ║
║ │ │ ║
║ │ 1. Diagnóstico técnico │ ║
║ │ 2. Troca de peças (registra peças + custos) │ ║
║ │ 3. Limpeza e preparação │ ║
║ │ 4. Teste de qualidade │ ║
║ │ 5. Preenche checklist de conclusão │ ║
║ │ 6. Finaliza O.S. Interna │ ║
║ │ │ ║
║ │ Custos: │ ║
║ │ ├── Peças utilizadas: R$ XXX │ ║
║ │ ├── Mão de obra: R$ XXX │ ║
║ │ └── Total reparo: R$ XXX │ ║
║ │ │ ║
║ │ Status O.S.: COMPLETED │ ║
║ └────────────────────────────────────────────┬───────────────┘ ║
║ │ ║
║ ▼ ║
║ ETAPA 4: ENTRADA NO ESTOQUE ║
║ ═══════════════════════════ ║
║ ║
║ ┌────────────────────────────────────────────────────────────┐ ║
║ │ Ao finalizar O.S. Interna: │ ║
║ │ │ ║
║ │ ✅ Cria dispositivo no estoque (mobile_devices) │ ║
║ │ condition: "refurbished" │ ║
║ │ acquisitionType: "trade_in" │ ║
║ │ │ ║
║ │ 💰 Cálculo do preço sugerido: │ ║
║ │ Custo aquisição = Valor Trade-In + Custo Reparo │ ║
║ │ Preço sugerido = Custo × (1 + Margem%) │ ║
║ │ Ex: R$1500 + R$300 = R$1800 × 1.30 = R$ 2.340 │ ║
║ │ │ ║
║ │ ✅ Registra no IMEI History │ ║
║ │ ✅ Registra no Activity Feed │ ║
║ │ ✅ Disponível no PDV para venda │ ║
║ │ │ ║
║ │ Status: IN_STOCK (pronto para venda) │ ║
║ └────────────────────────────────────────────────────────────┘ ║
╚═══════════════════════════════════════════════════════════════════════════╝
```
---
## 5. MAPA DO PDV (PONTO DE VENDA)
```
╔═══════════════════════════════════════════════════════════════════════╗
║ PDV - PONTO DE VENDA ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ BARRA SUPERIOR ║
║ ┌──────────────────────────────────────────────────────────────────┐ ║
║ │ 🏪 Empresa: Loja Centro 👤 Vendedor: João Silva │ ║
║ │ │ ║
║ │ [Sangria] [Reforço] [Devolução] [Selecionar Cliente] [Limpar] │ ║
║ └──────────────────────────────────────────────────────────────────┘ ║
║ ║
║ ┌───────────────────────────────────┬──────────────────────────────┐ ║
║ │ CATÁLOGO (3 colunas) │ CARRINHO (2 colunas) │ ║
║ │ │ │ ║
║ │ ┌──────────┬──────────┬────────┐ │ ┌──────────────────────────┐│ ║
║ │ │📱Disposit│📦Produtos│🔧Fat.OS│ │ │ Samsung S24 Ultra ││ ║
║ │ └──────────┴──────────┴────────┘ │ │ IMEI: 35XXX 📱Celular ││ ║
║ │ │ │ R$ 3.500,00 ││ ║
║ │ [🔍 Buscar... ] │ ├──────────────────────────┤│ ║
║ │ │ │ Capinha Silicone ││ ║
║ │ ┌──────────────────────────────┐ │ │ 2x R$ 45,00 ││ ║
║ │ │ Samsung S24 Ultra │ │ │ R$ 90,00 ││ ║
║ │ │ 256GB | Preto | IMEI: 35XX │ │ ├──────────────────────────┤│ ║
║ │ │ 🟢 Novo 📦 Loja Centro │ │ │ O.S. #OS2602ABC ││ ║
║ │ │ R$ 3.500,00 │ │ │ Troca de tela 🔧Serviço ││ ║
║ │ │ [+ Adicionar] │ │ │ R$ 450,00 ││ ║
║ │ ├──────────────────────────────┤ │ ╞══════════════════════════╡│ ║
║ │ │ iPhone 15 Pro │ │ │ Subtotal R$ 4.040,00 ││ ║
║ │ │ 128GB | Branco | IMEI: 86XX │ │ │ Desconto -R$ 40,00 ││ ║
║ │ │ 🔵 Recond 📦 Loja Centro │ │ │ Trade-In -R$ 1.500,00 ││ ║
║ │ │ R$ 4.200,00 │ │ │ Crédito -R$ 200,00 ││ ║
║ │ │ [+ Adicionar] │ │ │─────────────────────────││ ║
║ │ ├──────────────────────────────┤ │ │ TOTAL R$ 2.300,00 ││ ║
║ │ │ Xiaomi 14 │ │ │ ││ ║
║ │ │ 512GB | Azul | IMEI: 86XX │ │ │ 💙 Crédito: R$ 200,00 ││ ║
║ │ │ 🟡 Usado 📦 Loja Centro │ │ │ ││ ║
║ │ │ R$ 2.800,00 │ │ │ [↕️ Trade-In] ││ ║
║ │ │ [+ Adicionar] │ │ │ [💰 FINALIZAR VENDA] ││ ║
║ │ └──────────────────────────────┘ │ └──────────────────────────┘│ ║
║ └───────────────────────────────────┴──────────────────────────────┘ ║
║ ║
║ MODAL DE PAGAMENTO ║
║ ┌──────────────────────────────────────────────────────────────────┐ ║
║ │ Total: R$ 2.300,00 │ ║
║ │ │ ║
║ │ [Dinheiro ✓] [Débito] [Crédito] [PIX] [Combinado] │ ║
║ │ │ ║
║ │ Valor: [R$ 2.300,00] Parcelas: [1x] │ ║
║ │ │ ║
║ │ Desconto: [___]% ou [R$ ___] │ ║
║ │ │ ║
║ │ [CONFIRMAR PAGAMENTO] │ ║
║ └──────────────────────────────────────────────────────────────────┘ ║
╚═══════════════════════════════════════════════════════════════════════╝
```
---
## 6. FLUXO DA ORDEM DE SERVIÇO (12 ESTADOS)
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ ORDEM DE SERVIÇO - FLUXO DE STATUS │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ │
│ │ ABERTA │ ◄── Cliente entrega dispositivo │
│ │ open │ │
│ └────┬─────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ DIAGNÓSTICO │ ◄── Técnico analisa problema │
│ │ diagnosis │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ ORÇAMENTO │ ◄── Técnico elabora orçamento │
│ │ quote │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────┐ │
│ │ AGUARD. APROVAÇÃO │ ◄── Enviado para cliente │
│ │ pending_approval │ │
│ └─────┬────────┬─────┘ │
│ │ │ │
│ APROVOU │ │ REJEITOU │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ APROVADA │ │ REJEITADA│ ──► FIM │
│ │ approved │ │ rejected │ │
│ └────┬─────┘ └──────────┘ │
│ │ │
│ ▼ │
│ ┌──────────┐ │
│ │ EM REPARO│ ◄── Técnico inicia trabalho │
│ │in_repair │ │
│ └────┬─────┘ │
│ │ │
│ ┌──────────┼──────────┐ │
│ │ │ │
│ ▼ │ │
│ ┌──────────────┐ │ │
│ │ AGUARD. PEÇAS│ │ ◄── Se precisar de peça │
│ │waiting_parts │───────────────┘ │
│ └──────────────┘ (peça chegou, volta para reparo) │
│ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ QUALIDADE │ ◄── Verificação pós-reparo │
│ │quality_check │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ PRONTO PARA │ ◄── Disponível para retirada │
│ │ RETIRADA │ (aparece no PDV p/ faturar) │
│ │ ready_pickup │ │
│ └──────┬───────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ CONCLUÍDA │ ◄── Cliente retirou + pagou │
│ │ completed │ (faturada no PDV) │
│ └──────────────┘ │
│ │
│ Em qualquer momento: ──────────► ┌──────────────┐ │
│ │ CANCELADA │ │
│ │ cancelled │ │
│ └──────────────┘ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## 7. MAPA DE RELATÓRIOS
```
┌─────────────────────────────────────────────────────────────────────┐
│ RELATÓRIOS DO RETAIL │
├──────────────┬──────────────────────────────────────────────────────┤
│ │ │
│ OS POR │ Status │ Qtd │ Valor Total │
│ STATUS │ open │ 12 │ R$ 5.400 │
│ │ in_repair │ 8 │ R$ 3.200 │
│ │ completed │ 45 │ R$ 22.500 │
│ │ │
├──────────────┼──────────────────────────────────────────────────────┤
│ │ │
│ OS POR │ Técnico │ Total │ Concl. │ Andamento │ Receita │
│ TÉCNICO │ Carlos │ 15 │ 12 │ 3 │ R$8.500 │
│ │ Pedro │ 10 │ 8 │ 2 │ R$5.200 │
│ │ │
├──────────────┼──────────────────────────────────────────────────────┤
│ │ │
│ VENDAS POR │ Vendedor │ Vendas │ Receita │ Ticket │ Dias │
│ VENDEDOR │ João │ 28 │ R$45.000 │ R$1607 │ 22 │
│ │ Maria │ 22 │ R$38.000 │ R$1727 │ 20 │
│ │ │
├──────────────┼──────────────────────────────────────────────────────┤
│ │ │
│ MARGEM │ Dispositivo │ IMEI │ Custo │ Venda │ Margem │
│ POR IMEI │ S24 Ultra │ 35XXX │ R$2500 │ R$3500 │ 40.0% │
│ │ iPhone 15 │ 86XXX │ R$3800 │ R$4200 │ 10.5% │
│ │ │
├──────────────┼──────────────────────────────────────────────────────┤
│ │ │
│ CAIXA │ ┌────────────────────────────────────────────┐ │
│ DIÁRIO │ │ Total Vendas: R$ 12.500 (8 vendas) │ │
│ │ │ Dinheiro: R$ 4.200 │ Cartão: R$ 5.800 │ │
│ │ │ PIX: R$ 2.500 │ Combinado: R$ 0 │ │
│ (4 queries) │ │ Sangrias: -R$ 500 │ Reforços: +R$ 200 │ │
│ │ │ Saldo Caixa: R$ 3.900 │ │
│ │ └────────────────────────────────────────────┘ │
│ │ │
│ │ POR VENDEDOR: │
│ │ ┌──────┬──────┬──────┬──────┬──────┬──────┬──────┐ │
│ │ │Vendedor│Vendas│Dinh. │Déb. │Créd.│ PIX │TOTAL │ │
│ │ │João │ 5 │R$2100│R$1500│R$800│R$1200│R$5600│ │
│ │ │Maria │ 3 │R$2100│R$1300│R$600│R$1300│R$6900│ │
│ │ │TOTAL │ 8 │R$4200│R$2800│R$1400│R$2500│R$12.5│ │
│ │ └──────┴──────┴──────┴──────┴──────┴──────┴──────┘ │
│ │ │
├──────────────┼──────────────────────────────────────────────────────┤
│ │ │
│ GIRO DE │ Produto │ Estoque │ Vendas 30d │ Turnover │
│ ESTOQUE │ Capinha │ 50 │ 35 │ 0.70 │
│ │ Película │ 80 │ 42 │ 0.53 │
│ │ │
└──────────────┴──────────────────────────────────────────────────────┘
```
---
## 8. ARQUITETURA TÉCNICA
```
╔═══════════════════════════════════════════════════════════════════════╗
║ ARQUITETURA DO MÓDULO RETAIL ║
╠═══════════════════════════════════════════════════════════════════════╣
║ ║
║ FRONTEND (React + TypeScript + Tailwind + shadcn/ui) ║
║ ═══════════════════════════════════════════════════ ║
║ ║
║ client/src/pages/ArcadiaRetail.tsx ──── 10.067 linhas ║
║ │ ║
║ ├── Dashboard (KPIs, Feed, Alertas) ║
║ ├── PDV (Carrinho, Catálogo, Pagamento) ║
║ ├── Pessoas (CRUD, Papéis, Histórico) ║
║ ├── Estoque (Depósitos, Séries, Inventário) ║
║ ├── Serviços (O.S. CRUD, Checklist) ║
║ ├── Trade-In (Avaliações, Fluxo 4 etapas) ║
║ ├── Compras (Pedidos, Recebimento) ║
║ ├── Cadastros (Pagamento, Vendedores, Promoções) ║
║ ├── Relatórios (6 sub-abas) ║
║ ├── Comissões (Dashboard, Metas, Fechamento) ║
║ └── Configuração (Plus, Empresas, Sync) ║
║ ║
║ client/src/components/TradeInForm.tsx ─── 988 linhas ║
║ └── Formulário completo c/ checklist + assinatura ║
║ ║
║ ───────────────────────────────────────────────────────────── ║
║ API (fetch → /api/retail/*) ║
║ ───────────────────────────────────────────────────────────── ║
║ ║
║ BACKEND (Express.js + Drizzle ORM) ║
║ ══════════════════════════════════ ║
║ ║
║ server/retail/routes.ts ──── 5.218 linhas (~130 endpoints) ║
║ │ ║
║ ├── CRUD: Devices, Evaluations, ServiceOrders, ║
║ │ Persons, Products, Warehouses, etc. ║
║ ├── PDV: Sessions, Sales, Payments, CashMovements ║
║ ├── Trade-In: Approve, Process, FullFlow, Stock ║
║ ├── Reports: OsStatus, OsTech, SalesSeller, ║
║ │ MarginIMEI, DailyCash, StockTurnover ║
║ ├── Returns: Search, Process, Credits ║
║ └── Commissions: Plans, Goals, Calculate, Close ║
║ ║
║ server/retail/plus-sync.ts ──── 542 linhas ║
║ └── Sync: Customers, Sales+Items, NF-e ║
║ ║
║ ───────────────────────────────────────────────────────────── ║
║ Drizzle ORM → PostgreSQL ║
║ ───────────────────────────────────────────────────────────── ║
║ ║
║ BANCO DE DADOS (PostgreSQL + 40 tabelas) ║
║ ════════════════════════════════════════ ║
║ ║
║ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ║
║ │ Dispositivos │ │ Vendas │ │ Estoque │ ║
║ │ mobile_devices│ │ pos_sales │ │ warehouses │ ║
║ │ device_history│ │pos_sale_items│ │warehouse_stk │ ║
║ │ imei_history │ │pos_sessions │ │stock_movem. │ ║
║ └──────────────┘ │cash_movements│ │transfers │ ║
║ └──────────────┘ │serials │ ║
║ ┌──────────────┐ │inventories │ ║
║ │ Trade-In │ ┌──────────────┐ └──────────────┘ ║
║ │ evaluations │ │ Serviços │ ║
║ │ checklist_* │ │service_orders│ ┌──────────────┐ ║
║ │ acquisitions │ │ so_items │ │ Cadastros │ ║
║ │ transfer_doc │ │ warranties │ │payment_meth. │ ║
║ └──────────────┘ └──────────────┘ │ sellers │ ║
║ │ comm_plans │ ║
║ ┌──────────────┐ ┌──────────────┐ │ promotions │ ║
║ │ Créditos │ │ Devoluções │ │ price_tables │ ║
║ │customer_cred.│ │return_exch. │ │product_types │ ║
║ └──────────────┘ │return_items │ └──────────────┘ ║
║ └──────────────┘ ║
║ ║
║ ───────────────────────────────────────────────────────────── ║
║ ║
║ INTEGRAÇÃO PLUS (Laravel ERP - Porta 8080) ║
║ ══════════════════════════════════════════ ║
║ ║
║ ┌───────────┐ ┌───────────┐ ┌───────────┐ ║
║ │Proxy Rev. │ │ SSO │ │Auto-Start │ ║
║ │/plus/* → │ │HMAC-SHA256│ │php artisan│ ║
║ │:8080 │ │Token │ │serve │ ║
║ └───────────┘ └───────────┘ └───────────┘ ║
║ │ ║
║ ▼ ║
║ ┌─────────────────────────────────────────────────┐ ║
║ │ Plus (Laravel) │ ║
║ │ ├── NF-e / NFC-e (SEFAZ) │ ║
║ │ ├── Clientes / Fornecedores │ ║
║ │ ├── Vendas / Faturamento │ ║
║ │ └── Fiscal completo │ ║
║ └─────────────────────────────────────────────────┘ ║
║ ║
╚═══════════════════════════════════════════════════════════════════════╝
```
---
## 9. SEGURANÇA MULTI-TENANT
```
┌─────────────────────────────────────────────────────────────────────┐
│ CAMADAS DE SEGURANÇA │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ CAMADA 1: AUTENTICAÇÃO │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Passport.js Session → req.isAuthenticated() │ │
│ │ Todas as rotas /api/retail/* exigem login │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ CAMADA 2: TENANT SCOPING │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ req.user.tenantId → obrigatório │ │
│ │ Se ausente → 403 "Tenant not identified" │ │
│ │ WHERE tenant_id = $tenantId em TODAS as queries │ │
│ │ Dados de um tenant NUNCA vazam para outro │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ CAMADA 3: MODULE GATING │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ requireModule('retail') → verifica tenants.features.retail │ │
│ │ Se módulo inativo → 403 "Módulo não habilitado" │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ CAMADA 4: OPERAÇÕES SENSÍVEIS │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ Devoluções → Exige senha do GERENTE │ │
│ │ Sangrias → Registra responsável + autorizador │ │
│ │ Exclusões → Soft delete quando possível │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ CAMADA 5: AUDITORIA │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ retail_activity_feed → Todas as operações relevantes │ │
│ │ device_history → Movimentação de IMEI │ │
│ │ imei_history → Kardex completo por IMEI │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
---
## 10. MAPA DE INTEGRAÇÕES
```
┌─────────────────────────────────────────────────────────────────────┐
│ INTEGRAÇÕES DO RETAIL │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ │
│ │ ARCÁDIA RETAIL │ │
│ │ (Express) │ │
│ └────────┬─────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ PLUS (PHP) │ │ ERPNEXT │ │ POSTGRESQL │ │
│ │ :8080 │ │ (API ext.) │ │ (Drizzle) │ │
│ │ │ │ │ │ │ │
│ │ ▸ NF-e/NFC-e│ │ ▸ Clientes │ │ ▸ 40+ tabelas│ │
│ │ ▸ Clientes │ │ ▸ Fornecedor │ │ ▸ Retail │ │
│ │ ▸ Vendas │ │ ▸ Estoque │ │ ▸ Multi-ten. │ │
│ │ ▸ Fiscal │ │ ▸ Financeiro │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ SEFAZ │ │
│ │ NF-e / NFC-e (via Plus Laravel + nfelib) │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
```
---
**Métricas Finais:**
- **16.800+ linhas** de código
- **40+ tabelas** no banco
- **~130 endpoints** REST
- **11 abas** na interface
- **19 itens** no checklist de avaliação
- **12 estados** de O.S.
- **4 etapas** no fluxo de Trade-In
- **5 formas** de pagamento
- **4 queries** no caixa diário

469
MAPA_SISTEMA_ARCADIA.md Normal file
View File

@ -0,0 +1,469 @@
# Arcádia Suite - Mapa Geral do Sistema
## Visão Geral
**Arcádia Suite** é o Escritório Estratégico para a Empresa Moderna. Uma plataforma que centraliza produtividade, inteligência, tomada de decisão e governança, orquestrando ERPs, pessoas e dados.
**Princípio Central:** Separação absoluta entre decisão e execução.
- Arcádia **pensa, governa e orienta**
- ERPs **executam, registram e obedecem**
---
## Arquitetura de 4 Camadas
```
┌─────────────────────────────────────────────────────────────────────┐
│ CAMADA DE APRESENTAÇÃO │
│ React 18 + TypeScript + Tailwind CSS + shadcn/ui │
│ Interface tipo browser com abas + omnibox │
│ 66 páginas/módulos │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ CAMADA DE ORQUESTRAÇÃO │
│ Express.js + Socket.IO + Manus Agent │
│ Porta 5000 (API + WebSocket) │
│ 38 arquivos de rotas / 23 ferramentas registradas │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ CAMADA DE INTELIGÊNCIA │
│ FastAPI (Contábil 8003, BI 8004, Automação 8005) │
│ Communication Engine (Node 8006) │
│ OpenAI GPT-4o (Manus/Dev Center) + GPT-4o-mini (WhatsApp) │
└─────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────┐
│ CAMADA DE DADOS │
│ PostgreSQL + Drizzle ORM │
│ Knowledge Graph + ChromaDB (embeddings) │
│ Session Store + Multi-tenant │
└─────────────────────────────────────────────────────────────────────┘
```
---
## Mapa de Portas
| Porta | Serviço | Tecnologia |
|-------|---------|-----------|
| 5000 | API Principal + Frontend | Express.js + React |
| 8002 | Motor Fiscal (Fisco) | FastAPI (Python) |
| 8003 | Motor Contábil | FastAPI (Python) |
| 8004 | Motor BI (Insights) | FastAPI (Python) |
| 8005 | Motor Automação | FastAPI (Python) |
| 8006 | Motor Comunicação | Node.js/Express |
| 8080 | Arcádia Plus (Laravel) | PHP/Laravel |
---
## Módulos do Frontend (66 páginas)
### Núcleo & Administração
| Página | Rota | Descrição |
|--------|------|-----------|
| Home | `/` | Dashboard principal |
| SOE | `/soe` | Sistema Operacional Empresarial |
| ERP | `/erp` | Módulo ERP legado |
| Admin | `/admin` | Administração do sistema |
| SuperAdmin | `/super-admin` | Gestão multi-tenant |
### Módulos de Negócio
| Página | Rota | Descrição |
|--------|------|-----------|
| Financeiro | `/financeiro` | Contas a pagar/receber, fluxo de caixa |
| Contábil | `/contabil` | Contabilidade, DRE, balancetes |
| Fiscal | `/fisco` | NF-e, NFC-e, CFOP, NCM, CEST |
| CRM | `/crm` | Gestão de relacionamento com cliente |
| People | `/people` | RH, colaboradores, folha |
| Production | `/production` | Ordens de produção |
| Quality | `/quality` | Controle de qualidade |
### Varejo & Comércio
| Página | Rota | Descrição |
|--------|------|-----------|
| Retail | `/retail` | Varejo (celulares, assistência técnica) |
| RetailReports | `/retail-reports` | Relatórios do varejo |
| Marketplace | `/marketplace` | Marketplace integrado |
| Valuation | `/valuation` | Avaliação de trade-in |
### Comunicação
| Página | Rota | Descrição |
|--------|------|-----------|
| WhatsApp | `/whatsapp` | Multi-sessão WhatsApp |
| Chat | `/chat` | Chat interno |
| XOS Inbox | `/xos/inbox` | Caixa de entrada unificada |
| XOS CRM | `/xos/crm` | CRM unificado |
| XOS Campaigns | `/xos/campaigns` | Campanhas de marketing |
| XOS Tickets | `/xos/tickets` | Sistema de tickets |
### Inteligência & IA
| Página | Rota | Descrição |
|--------|------|-----------|
| Scientist | `/scientist` | Auto-programação com IA |
| Knowledge | `/knowledge` | Base de conhecimento/grafo |
| BI Workspace | `/bi` | Business Intelligence |
| Manus | `/agent` | Agente autônomo central |
### Desenvolvimento & DevOps
| Página | Rota | Descrição |
|--------|------|-----------|
| IDE | `/ide` | Editor Monaco + Terminal |
| Dev Center | `/dev-center` | Centro de desenvolvimento XOS |
| XOS Pipeline | `/xos/pipeline` | Pipeline autônomo de código |
| XOS Governance | `/xos/governance` | Governança e políticas |
| API Hub | `/api-hub` | Central de APIs |
| API Tester | `/api-tester` | Testador de APIs |
| DocType Builder | `/doctype-builder` | Construtor de tipos |
| Page Builder | `/page-builder` | Construtor de páginas |
| Workflow Builder | `/workflow-builder` | Construtor de workflows |
### Operações & Engenharia
| Página | Rota | Descrição |
|--------|------|-----------|
| Engineering Hub | `/engineering` | Hub de engenharia |
| Field Operations | `/field-ops` | Operações de campo |
| Process Compass | `/compass` | Bússola de processos |
| Suppliers Portal | `/suppliers` | Portal de fornecedores |
### Plataforma
| Página | Rota | Descrição |
|--------|------|-----------|
| Engine Room | `/engine-room` | Casa de Máquinas (status dos motores) |
| Automations | `/automations` | Motor de automações |
| Plus | `/plus` | ERP Laravel (proxy) |
| LMS | `/lms` | Sistema de aprendizagem |
| Communities | `/communities` | Comunidades |
| Support | `/support` | Central de suporte |
| Migration | `/migration` | Migração de dados |
| Central APIs | `/central-apis` | APIs centrais |
---
## APIs do Backend (38 grupos de rotas)
### Core
| Rota Base | Arquivo | Descrição |
|-----------|---------|-----------|
| `/api/login`, `/api/register` | `server/auth.ts` | Autenticação |
| `/api/admin/*` | `server/admin/routes.ts` | Administração |
| `/api/erp/*` | `server/erp/routes.ts` | ERP principal |
| `/api/soe/*` | `server/erp/routes.ts` | SOE (alias) |
| `/api/users/*` | `server/routes.ts` | Gestão de usuários |
### Negócio
| Rota Base | Arquivo | Descrição |
|-----------|---------|-----------|
| `/api/financeiro/*` | `server/financeiro/routes.ts` | Financeiro |
| `/api/contabil/*` | `server/contabil/routes.ts` | Contabilidade |
| `/api/fisco/*` | `server/fisco/routes.ts` | Fiscal |
| `/api/crm/*` | `server/crm/routes.ts` | CRM |
| `/api/people/*` | `server/people/routes.ts` | RH/Pessoas |
| `/api/production/*` | `server/production/routes.ts` | Produção |
| `/api/quality/*` | `server/quality/routes.ts` | Qualidade |
| `/api/retail/*` | `server/retail/routes.ts` | Varejo |
| `/api/valuation/*` | `server/valuation/routes.ts` | Avaliação trade-in |
| `/api/marketplace/*` | `server/marketplace/routes.ts` | Marketplace |
### Comunicação
| Rota Base | Arquivo | Descrição |
|-----------|---------|-----------|
| `/api/whatsapp/*` | `server/whatsapp/routes.ts` | WhatsApp multi-sessão |
| `/api/chat/*` | `server/chat/routes.ts` | Chat interno |
| `/api/email/*` | `server/email/routes.ts` | E-mail |
| `/api/comm/*` | proxy | Motor de Comunicação |
| `/api/xos/*` | `server/xos/routes.ts` | XOS CRM unificado |
### Inteligência
| Rota Base | Arquivo | Descrição |
|-----------|---------|-----------|
| `/api/manus/*` | `server/manus/routes.ts` | Agente Manus IA |
| `/api/knowledge/*` | `server/learning/routes.ts` | Knowledge Graph |
| `/api/bi/*` | `server/bi/routes.ts` | Business Intelligence |
| `/api/bi/metaset/*` | `server/metaset/routes.ts` | Motor BI MetaSet |
| `/api/scientist/*` | `server/routes.ts` | Cientista de dados |
### Desenvolvimento
| Rota Base | Arquivo | Descrição |
|-----------|---------|-----------|
| `/api/ide/*` | `server/ide/routes.ts` | IDE integrada |
| `/api/dev-center/*` | `server/blackboard/routes.ts` | Dev Center/Blackboard |
| `/api/xos/pipeline` | `server/blackboard/routes.ts` | Pipeline autônomo |
| `/api/governance/*` | `server/governance/routes.ts` | Governança |
| `/api/lowcode/*` | `server/lowcode/routes.ts` | Low-code engine |
### Protocolos de Interoperabilidade
| Rota Base | Arquivo | Descrição |
|-----------|---------|-----------|
| `/api/mcp/v1/*` | `server/mcp/routes.ts` | Model Context Protocol |
| `/api/a2a/v1/*` | `server/routes.ts` | Agent to Agent Protocol |
| `/api/api-central/*` | `server/api-central/routes.ts` | Central de APIs |
### Infraestrutura
| Rota Base | Arquivo | Descrição |
|-----------|---------|-----------|
| `/api/engine-room/*` | `server/engine-room/routes.ts` | Casa de Máquinas |
| `/api/automations/*` | `server/automations/routes.ts` | Motor de Automação |
| `/api/modules/*` | `server/modules/loader.ts` | Módulos dinâmicos |
| `/api/login-bridge/*` | `server/login-bridge/routes.ts` | SSO Bridge |
| `/api/migration/*` | `server/migration/routes.ts` | Migração |
---
## Motores (Engines)
### Motor IA - Manus (Node.js, porta 5000)
- **Modelo:** GPT-4o (Dev Center), GPT-4o-mini (WhatsApp)
- **Agentes:** 6 agentes autônomos (Architect, Generator, Validator, Executor, Researcher, Evolution)
- **Ferramentas:** 23 ferramentas registradas (GitHub, filesystem, BI, git)
- **Pipeline:** Design → Codegen → Validation → Staging → Evolution
### Motor Fiscal - Fisco (Python, porta 8002)
- NF-e / NFC-e via nfelib
- NCMs, CFOPs, CESTs, grupos tributários
- Certificados digitais
- Comunicação com SEFAZ
### Motor Contábil (Python, porta 8003)
- Plano de contas
- Lançamentos contábeis
- DRE, Balanço Patrimonial
### Motor BI - Insights (Python, porta 8004)
- Execução SQL
- Geração de gráficos
- Análise com Pandas
- Cache inteligente
### Motor Automação (Python, porta 8005)
- Cron scheduler
- Event bus
- Executor de workflows
### Motor Comunicação (Node.js, porta 8006)
- Unifica XOS CRM + WhatsApp + Email
- Contatos, threads, mensagens unificados
- Filas de atendimento
- Eventos para Knowledge Graph
### Arcádia Plus - ERP Laravel (PHP, porta 8080)
- NF-e/NFC-e/CT-e/MDF-e
- PDV (ponto de venda)
- Cardápio digital
- Ordens de serviço
- Estoque com rastreabilidade
- Integrações e-commerce (WooCommerce, Mercado Livre, NuvemShop)
- Integrações delivery (iFood)
---
## Dev Center XOS - 6 Agentes Autônomos
```
Prompt em Português
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Architect │ ──▶ │ Generator │ ──▶ │ Validator │
│ (Design) │ │ (Codegen) │ │ (Typecheck) │
└──────────────┘ └──────────────┘ └──────────────┘
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Evolution │ ◀── │ Researcher │ ◀── │ Executor │
│ (Aprende) │ │ (Pesquisa) │ │ (Staging) │
└──────────────┘ └──────────────┘ └──────────────┘
```
---
## Módulo Retail (Varejo de Celulares)
### Funcionalidades Core
- **Vendas com IMEI:** Rastreamento individual de aparelhos
- **Trade-in:** Avaliação com checklist de 19 itens
- **Ordens de Serviço:** Gestão completa de assistência técnica
- **Garantia:** Controle de garantias por IMEI
- **Caixa Diário:** Reconciliação de caixa com fechamento
- **Comissões:** Cálculo automático por vendedor
### Checklist Trade-in (19 itens)
1. Liga normalmente
2. Problemas na tela
3. WiFi funcionando
4. Bluetooth funcionando
5. Câmera frontal
6. Câmera traseira
7. Microfone
8. Alto-falante
9. Botões físicos
10. Sensor biométrico
11. Carregamento
12. Bateria saudável
13. GPS funcionando
14. Giroscópio
15. Acelerômetro
16. NFC
17. Resistência à água
18. Face ID / reconhecimento facial
19. Vibração
---
## Banco de Dados (PostgreSQL + Drizzle ORM)
### Tabelas Principais
| Grupo | Tabelas |
|-------|---------|
| **Identidade** | `users`, `profiles`, `tenants` |
| **Produtividade** | `workspace_pages`, `page_blocks`, `dashboard_widgets`, `quick_notes` |
| **Comunicação** | `conversations`, `messages`, `chat_threads`, `chat_messages` |
| **WhatsApp** | `whatsapp_sessions`, `whatsapp_contacts`, `whatsapp_messages`, `whatsapp_tickets` |
| **ERP Core** | `applications`, `erp_connections`, `agent_tasks`, `task_executions` |
| **Conhecimento** | `knowledge_base`, `knowledge_graph_nodes`, `knowledge_graph_edges` |
| **Governança** | `xos_governance_*`, `xos_job_queue`, `xos_agent_metrics` |
| **Pipeline** | `xos_staging_changes`, `xos_dev_pipelines` |
| **Comunicação Unificada** | `comm_contacts`, `comm_threads`, `comm_messages`, `comm_channels` |
| **Varejo** | Via módulos dinâmicos (`/api/modules/retail-reports`) |
| **Financeiro** | Contas, lançamentos, conciliação |
| **Fiscal** | NCMs, CFOPs, notas fiscais |
---
## Integrações Externas
| Serviço | Uso |
|---------|-----|
| **OpenAI** | GPT-4o (Manus, Dev Center), GPT-4o-mini (WhatsApp) |
| **GitHub** | Commits automáticos, análise de repositórios |
| **ERPNext** | Integração com ERP externo (clientes, produtos, vendas) |
| **WhatsApp/Baileys** | Multi-sessão de atendimento |
| **SEFAZ** | NF-e/NFC-e via nfelib |
| **Cloud-DFE** | SDK fiscal (NF-e, NFC-e, CT-e, MDF-e) |
| **WooCommerce** | E-commerce integration |
| **Mercado Livre** | Marketplace |
| **NuvemShop** | E-commerce |
| **iFood** | Delivery (pedidos, cardápio) |
| **Asaas** | Pagamentos, boletos |
---
## Protocolos de Interoperabilidade
| Protocolo | Rota | Descrição |
|-----------|------|-----------|
| **MCP** | `/api/mcp/v1/` | Model Context Protocol - exposição de ferramentas |
| **A2A** | `/api/a2a/v1/` | Agent to Agent - comunicação bidirecional |
| **AP2** | Planejado | Agent Payment Protocol |
| **UCP** | Planejado | Unified Commerce Protocol |
---
## Como Rodar Localmente
### Pré-requisitos
- Node.js 20+
- Python 3.11+
- PostgreSQL 16+
- PHP 8.2+ (opcional, para Arcádia Plus)
### Instalação
```bash
# 1. Extrair o backup
tar xzf arcadia-suite-backup.tar.gz
# 2. Instalar dependências Node
npm install
# 3. Instalar dependências Python
pip install fastapi uvicorn pandas numpy psycopg2-binary nfelib lxml cryptography
# 4. Configurar variáveis de ambiente
cp .env.example .env
# Editar .env com suas credenciais:
# DATABASE_URL=postgresql://user:pass@localhost:5432/arcadia
# OPENAI_API_KEY=sk-...
# GITHUB_TOKEN=ghp_...
# 5. Criar banco de dados
createdb arcadia
# 6. Executar migrations
npx drizzle-kit push
# 7. Iniciar em desenvolvimento
npm run dev
```
### Variáveis de Ambiente Necessárias
| Variável | Descrição |
|----------|-----------|
| `DATABASE_URL` | URL de conexão PostgreSQL |
| `OPENAI_API_KEY` | Chave da API OpenAI |
| `GITHUB_TOKEN` | Token GitHub para integrações |
| `ERPNEXT_URL` | URL do ERPNext (opcional) |
| `ERPNEXT_API_KEY` | Chave API ERPNext (opcional) |
| `ERPNEXT_API_SECRET` | Segredo API ERPNext (opcional) |
| `SESSION_SECRET` | Segredo para sessões Express |
### Credenciais Padrão
- **Usuário:** admin
- **Senha:** admin
- **Role:** master
---
## Estrutura de Diretórios
```
arcadia-suite/
├── client/ # Frontend React
│ ├── src/
│ │ ├── pages/ # 66 páginas
│ │ ├── components/ # Componentes reutilizáveis
│ │ ├── hooks/ # Custom hooks
│ │ └── lib/ # Utilitários
│ └── public/ # Assets estáticos
├── server/ # Backend Express
│ ├── admin/ # Administração
│ ├── autonomous/ # Ferramentas autônomas
│ ├── bi/ # Business Intelligence
│ ├── blackboard/ # Dev Center (6 agentes)
│ ├── chat/ # Chat interno
│ ├── communication/ # Motor de comunicação
│ ├── contabil/ # Motor contábil
│ ├── crm/ # CRM
│ ├── engine-room/ # Casa de Máquinas
│ ├── erp/ # ERP/SOE
│ ├── financeiro/ # Financeiro
│ ├── fisco/ # Fiscal
│ ├── governance/ # Governança XOS
│ ├── ide/ # IDE integrada
│ ├── integrations/ # Integrações externas
│ ├── learning/ # Knowledge Graph
│ ├── manus/ # Agente Manus
│ ├── mcp/ # Model Context Protocol
│ ├── modules/ # Módulos dinâmicos
│ ├── people/ # RH
│ ├── plus/ # Proxy Laravel
│ ├── production/ # Produção
│ ├── python/ # Scripts Python
│ ├── quality/ # Qualidade
│ ├── retail/ # Varejo
│ ├── whatsapp/ # WhatsApp
│ └── xos/ # XOS unificado
├── shared/ # Código compartilhado
│ ├── schema.ts # Schema principal (Drizzle)
│ └── schemas/ # Schemas modulares
├── plus/ # ERP Laravel (PHP)
├── python-service/ # Serviço Python
├── db/ # Configuração do banco
├── migrations/ # Migrations Drizzle
└── docs/ # Documentação
```
---
*Arcádia Suite v2.0 - O Escritório Estratégico para a Empresa Moderna*

47
README.md Normal file
View File

@ -0,0 +1,47 @@
# Arcádia Suite
O Escritório Estratégico para a Empresa Moderna.
Plataforma que centraliza produtividade, inteligência, tomada de decisão e governança, orquestrando ERPs, pessoas e dados.
## Arquitetura
- **Frontend:** React 18 + TypeScript + Tailwind CSS + shadcn/ui (66 páginas)
- **Backend:** Express.js + Socket.IO (38 grupos de rotas API)
- **Inteligência:** OpenAI GPT-4o + 6 Agentes Autônomos (Dev Center XOS)
- **Motores:** Fiscal (8002), Contábil (8003), BI (8004), Automação (8005), Comunicação (8006)
- **ERP Plus:** Laravel/PHP (8080) com NF-e/NFC-e/CT-e/MDF-e
- **Banco:** PostgreSQL 16 + Drizzle ORM + ChromaDB
## Módulos Principais
- SOE (Sistema Operacional Empresarial)
- Financeiro, Contábil, Fiscal
- CRM + WhatsApp Multi-sessão
- Varejo (Celulares + Assistência Técnica)
- Business Intelligence
- Dev Center com Pipeline Autônomo
- Casa de Máquinas (Engine Room)
- Governança XOS
- Knowledge Graph + IA
## Quick Start
```bash
npm install
cp .env.example .env # Configure DATABASE_URL, OPENAI_API_KEY
npx drizzle-kit push
npm run dev
```
**Login padrão:** admin / admin
## Documentação
- [Mapa Geral do Sistema](MAPA_SISTEMA_ARCADIA.md)
- [Relatório Técnico Retail](RELATORIO_TECNICO_RETAIL.md)
- [Mapa Retail](MAPA_GERAL_RETAIL.md)
## Licença
Proprietário - Arcádia Suite

1261
RELATORIO_TECNICO_RETAIL.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,74 +1,89 @@
import { Switch, Route } from "wouter";
import { lazy, Suspense } from "react";
import { queryClient } from "./lib/queryClient";
import { QueryClientProvider } from "@tanstack/react-query";
import { Toaster } from "@/components/ui/toaster";
import { TooltipProvider } from "@/components/ui/tooltip";
import { AuthProvider } from "@/hooks/use-auth";
import { ErpProfileProvider } from "@/contexts/ErpProfileContext";
import { SoeMotorProvider } from "@/contexts/SoeMotorContext";
import { ProtectedRoute } from "@/lib/protected-route";
import { CommandPalette } from "@/components/CommandPalette";
import { KnowledgeCollectorInit } from "@/components/KnowledgeCollectorInit";
import NotFound from "@/pages/not-found";
import AuthPage from "@/pages/auth-page";
import Agent from "@/pages/Agent";
import Admin from "@/pages/Admin";
import Chat from "@/pages/Chat";
import WhatsApp from "@/pages/WhatsApp";
import Automations from "@/pages/Automations";
import BiWorkspace from "@/pages/BiWorkspace";
import ProcessCompass from "@/pages/ProcessCompass";
import WorkspacePage from "@/pages/WorkspacePage";
import AppViewer from "@/pages/AppViewer";
import Crm from "@/pages/Crm";
import Production from "@/pages/Production";
import Support from "@/pages/Support";
import Valuation from "@/pages/Valuation";
import Canvas from "@/pages/Canvas";
import IDE from "@/pages/IDE";
import Scientist from "@/pages/Scientist";
import Knowledge from "@/pages/Knowledge";
import CentralApis from "@/pages/CentralApis";
import ApiTesterPage from "@/pages/ApiTesterPage";
import ApiHub from "@/pages/ApiHub";
import Cockpit from "@/pages/Cockpit";
import Fisco from "@/pages/Fisco";
import People from "@/pages/People";
import Contabil from "@/pages/Contabil";
import ERP from "@/pages/ERP";
import Financeiro from "@/pages/Financeiro";
import Communities from "@/pages/Communities";
import ArcadiaNext from "@/pages/ArcadiaNext";
import QualityModule from "@/pages/QualityModule";
import CommercialEnv from "@/pages/CommercialEnv";
import FieldOperations from "@/pages/FieldOperations";
import TechnicalModule from "@/pages/TechnicalModule";
import SuppliersPortal from "@/pages/SuppliersPortal";
import NPSSurvey from "@/pages/NPSSurvey";
import EngineeringHub from "@/pages/EngineeringHub";
import DocTypeBuilder from "@/pages/DocTypeBuilder";
import PageBuilder from "@/pages/PageBuilder";
import DevelopmentModule from "@/pages/DevelopmentModule";
import ArcadiaRetail from "@/pages/ArcadiaRetail";
import Plus from "@/pages/Plus";
import SuperAdmin from "@/pages/SuperAdmin";
import Marketplace from "@/pages/Marketplace";
import LMS from "@/pages/LMS";
import AppCenter from "@/pages/AppCenter";
import XosCentral from "@/pages/XosCentral";
import XosCrm from "@/pages/XosCrm";
import XosInbox from "@/pages/XosInbox";
import XosTickets from "@/pages/XosTickets";
import Migration from "@/pages/Migration";
import DevCenter from "@/pages/DevCenter";
import XosCampaigns from "@/pages/XosCampaigns";
import XosAutomations from "@/pages/XosAutomations";
import XosSites from "@/pages/XosSites";
import XosGovernance from "@/pages/XosGovernance";
import XosPipeline from "@/pages/XosPipeline";
const Agent = lazy(() => import("@/pages/Agent"));
const Admin = lazy(() => import("@/pages/Admin"));
const Chat = lazy(() => import("@/pages/Chat"));
const WhatsApp = lazy(() => import("@/pages/WhatsApp"));
const Automations = lazy(() => import("@/pages/Automations"));
const BiWorkspace = lazy(() => import("@/pages/BiWorkspace"));
const ProcessCompass = lazy(() => import("@/pages/ProcessCompass"));
const WorkspacePage = lazy(() => import("@/pages/WorkspacePage"));
const AppViewer = lazy(() => import("@/pages/AppViewer"));
const Crm = lazy(() => import("@/pages/Crm"));
const Production = lazy(() => import("@/pages/Production"));
const Support = lazy(() => import("@/pages/Support"));
const Valuation = lazy(() => import("@/pages/Valuation"));
const Canvas = lazy(() => import("@/pages/Canvas"));
const IDE = lazy(() => import("@/pages/IDE"));
const Scientist = lazy(() => import("@/pages/Scientist"));
const Knowledge = lazy(() => import("@/pages/Knowledge"));
const CentralApis = lazy(() => import("@/pages/CentralApis"));
const ApiTesterPage = lazy(() => import("@/pages/ApiTesterPage"));
const ApiHub = lazy(() => import("@/pages/ApiHub"));
const Cockpit = lazy(() => import("@/pages/Cockpit"));
const Fisco = lazy(() => import("@/pages/Fisco"));
const People = lazy(() => import("@/pages/People"));
const Contabil = lazy(() => import("@/pages/Contabil"));
const SOE = lazy(() => import("@/pages/SOE"));
const Financeiro = lazy(() => import("@/pages/Financeiro"));
const Communities = lazy(() => import("@/pages/Communities"));
const ArcadiaNext = lazy(() => import("@/pages/ArcadiaNext"));
const QualityModule = lazy(() => import("@/pages/QualityModule"));
const CommercialEnv = lazy(() => import("@/pages/CommercialEnv"));
const FieldOperations = lazy(() => import("@/pages/FieldOperations"));
const TechnicalModule = lazy(() => import("@/pages/TechnicalModule"));
const SuppliersPortal = lazy(() => import("@/pages/SuppliersPortal"));
const NPSSurvey = lazy(() => import("@/pages/NPSSurvey"));
const EngineeringHub = lazy(() => import("@/pages/EngineeringHub"));
const DocTypeBuilder = lazy(() => import("@/pages/DocTypeBuilder"));
const PageBuilder = lazy(() => import("@/pages/PageBuilder"));
const DevelopmentModule = lazy(() => import("@/pages/DevelopmentModule"));
const ArcadiaRetail = lazy(() => import("@/pages/ArcadiaRetail"));
const Plus = lazy(() => import("@/pages/Plus"));
const SuperAdmin = lazy(() => import("@/pages/SuperAdmin"));
const Marketplace = lazy(() => import("@/pages/Marketplace"));
const LMS = lazy(() => import("@/pages/LMS"));
const AppCenter = lazy(() => import("@/pages/AppCenter"));
const XosCentral = lazy(() => import("@/pages/XosCentral"));
const XosCrm = lazy(() => import("@/pages/XosCrm"));
const XosInbox = lazy(() => import("@/pages/XosInbox"));
const XosTickets = lazy(() => import("@/pages/XosTickets"));
const Migration = lazy(() => import("@/pages/Migration"));
const DevCenter = lazy(() => import("@/pages/DevCenter"));
const XosCampaigns = lazy(() => import("@/pages/XosCampaigns"));
const XosAutomations = lazy(() => import("@/pages/XosAutomations"));
const XosSites = lazy(() => import("@/pages/XosSites"));
const XosGovernance = lazy(() => import("@/pages/XosGovernance"));
const XosPipeline = lazy(() => import("@/pages/XosPipeline"));
function LoadingFallback() {
return (
<div style={{ display: "flex", justifyContent: "center", alignItems: "center", height: "100vh" }}>
<div style={{ textAlign: "center" }}>
<div style={{ width: 40, height: 40, border: "3px solid #e5e7eb", borderTopColor: "#3b82f6", borderRadius: "50%", animation: "spin 1s linear infinite", margin: "0 auto" }} />
<p style={{ marginTop: 16, color: "#6b7280" }}>Carregando...</p>
</div>
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
</div>
);
}
function Router() {
return (
<Suspense fallback={<LoadingFallback />}>
<Switch>
<ProtectedRoute path="/" component={Cockpit} />
<ProtectedRoute path="/agent" component={Agent} />
@ -93,7 +108,8 @@ function Router() {
<ProtectedRoute path="/fisco" component={Fisco} />
<ProtectedRoute path="/people" component={People} />
<ProtectedRoute path="/contabil" component={Contabil} />
<ProtectedRoute path="/erp" component={ERP} />
<ProtectedRoute path="/soe" component={SOE} />
<ProtectedRoute path="/erp" component={SOE} />
<ProtectedRoute path="/financeiro" component={Financeiro} />
<ProtectedRoute path="/communities" component={Communities} />
<ProtectedRoute path="/quality" component={QualityModule} />
@ -129,6 +145,7 @@ function Router() {
<Route path="/auth" component={AuthPage} />
<Route component={NotFound} />
</Switch>
</Suspense>
);
}
@ -136,14 +153,14 @@ function App() {
return (
<QueryClientProvider client={queryClient}>
<AuthProvider>
<ErpProfileProvider>
<SoeMotorProvider>
<TooltipProvider>
<KnowledgeCollectorInit />
<Toaster />
<CommandPalette />
<Router />
</TooltipProvider>
</ErpProfileProvider>
</SoeMotorProvider>
</AuthProvider>
</QueryClientProvider>
);

File diff suppressed because it is too large Load Diff

View File

@ -7,7 +7,7 @@ import {
Calculator, Receipt, UserCog, Building2, Package, Wallet,
Hash, ChevronDown, Circle, MessageSquare
} from "lucide-react";
import browserIcon from "@assets/arcadia_branding/arcadia_suite_icon.png";
const browserIcon = "/arcadia_suite_icon.png";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card } from "@/components/ui/card";
@ -777,16 +777,16 @@ export default function Home() {
<Card
className="p-3 hover:shadow-md transition-all border-slate-200 hover:border-primary/30 bg-white group relative cursor-pointer"
data-testid="card-module-erp"
onClick={() => navigate("/erp")}
data-testid="card-module-soe"
onClick={() => navigate("/soe")}
>
<div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-lg bg-purple-500 flex items-center justify-center text-white shadow-sm">
<Package className="h-5 w-5" />
<Building2 className="h-5 w-5" />
</div>
<div className="flex-1 min-w-0">
<h3 className="font-medium text-sm truncate">Arcádia ERP</h3>
<p className="text-xs text-slate-400 truncate">Gestão Empresarial</p>
<h3 className="font-medium text-sm truncate">Arcádia SOE</h3>
<p className="text-xs text-slate-400 truncate">Sistema Operacional</p>
</div>
<ExternalLink className="h-3.5 w-3.5 text-slate-300 group-hover:text-primary" />
</div>

View File

@ -2,6 +2,7 @@ import express, { type Request, Response, NextFunction } from "express";
import { registerRoutes } from "./routes";
import { serveStatic } from "./static";
import { registerAllTools } from "./autonomous/tools";
import { storage } from "./storage";
import { createServer } from "http";
import { spawn } from "child_process";
import path from "path";
@ -270,8 +271,6 @@ function startShellService(name: string, scriptPath: string, port: number) {
return shellProcess;
}
startShellService("metabase", path.join(process.cwd(), "metabase/start-metabase.sh"), 8088);
const app = express();
const httpServer = createServer(app);
@ -333,6 +332,30 @@ app.use((req, res, next) => {
// It's registered after session middleware to enable SSO authentication
(async () => {
// Seed master user if not exists
try {
const { scrypt, randomBytes } = await import("crypto");
const { promisify } = await import("util");
const scryptAsync = promisify(scrypt);
const existingAdmin = await storage.getUserByUsername("admin");
if (!existingAdmin) {
const salt = randomBytes(16).toString("hex");
const buf = (await scryptAsync("admin", salt, 64)) as Buffer;
const hashedPassword = `${buf.toString("hex")}.${salt}`;
await storage.createUser({
username: "admin",
password: hashedPassword,
name: "Administrador Master",
email: "admin@arcadia.suite",
role: "master",
status: "active",
});
console.log("[Seed] Usuário master 'admin' criado com sucesso");
}
} catch (e: any) {
console.log("[Seed] Verificação de usuário master:", e.message);
}
await registerRoutes(httpServer, app);
await registerAllTools();

View File

@ -13,12 +13,41 @@ import {
retailSellerGoals, retailStoreGoals, retailCommissionClosures, retailCommissionClosureItems,
retailWarehouseStock, retailStockMovements, retailProductSerials,
retailStockTransfers, retailStockTransferItems, retailInventories, retailInventoryItems,
products, purchaseOrders, purchaseOrderItems, suppliers
products, purchaseOrders, purchaseOrderItems, suppliers,
tenantEmpresas, tenants, type TenantFeatures,
posCashMovements, serviceWarranties
} from "@shared/schema";
import { eq, desc, and, ilike, sql, or, asc, inArray, gte, lte } from "drizzle-orm";
import { eq, desc, and, ilike, sql, or, asc, inArray, gte, lte, isNull, between, count, sum } from "drizzle-orm";
import { retailPlusSyncService } from "./plus-sync";
const router = Router();
const requireModule = (moduleKey: keyof TenantFeatures) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
const tenantId = (req.user as any)?.tenantId;
if (!tenantId) {
return res.status(401).json({ error: "Tenant not identified" });
}
const [tenant] = await db.select().from(tenants).where(eq(tenants.id, tenantId));
if (!tenant) {
return res.status(404).json({ error: "Tenant not found" });
}
const features = (tenant?.features as TenantFeatures) || {};
if (!features[moduleKey]) {
return res.status(403).json({
error: `Módulo "${moduleKey}" não está ativo para este tenant`,
moduleKey,
action: "Ative o módulo em Admin → Módulos"
});
}
next();
} catch (error) {
next(error);
}
};
};
const requireAuth = (req: Request, res: Response, next: NextFunction) => {
if (!req.isAuthenticated()) {
return res.status(401).json({ error: "Not authenticated" });
@ -1108,6 +1137,14 @@ router.get("/devices/imei/:imei", async (req: Request, res: Response) => {
router.post("/devices", async (req: Request, res: Response) => {
try {
if (req.body.imei) {
const existing = await db.select({ id: mobileDevices.id }).from(mobileDevices)
.where(eq(mobileDevices.imei, req.body.imei))
.limit(1);
if (existing.length > 0) {
return res.status(400).json({ error: "IMEI já cadastrado no sistema. Cada aparelho deve ter um IMEI único." });
}
}
const [device] = await db.insert(mobileDevices).values(req.body).returning();
await db.insert(deviceHistory).values({
deviceId: device.id,
@ -1516,11 +1553,61 @@ router.put("/service-orders/:id", async (req: Request, res: Response) => {
const laborCost = String(parseFloat(req.body.laborCost || 0));
const totalCost = String(parseFloat(partsCost) + parseFloat(laborCost));
const updatePayload: any = { ...req.body, partsCost, laborCost, totalCost, updatedAt: sql`CURRENT_TIMESTAMP` };
if (req.body.checklistCompletedBy) {
updatePayload.checklistCompletedAt = sql`CURRENT_TIMESTAMP`;
}
const [order] = await db.update(serviceOrders)
.set({ ...req.body, partsCost, laborCost, totalCost, updatedAt: sql`CURRENT_TIMESTAMP` })
.set(updatePayload)
.where(eq(serviceOrders.id, parseInt(req.params.id)))
.returning();
// Auto-deduct parts from stock when OS is completed (RN-02)
if (req.body.status === "completed" || req.body.status === "ready_pickup") {
const items = await db.select().from(serviceOrderItems)
.where(and(
eq(serviceOrderItems.serviceOrderId, order.id),
eq(serviceOrderItems.itemType, "part"),
eq(serviceOrderItems.status, "pending")
));
for (const item of items) {
if (item.itemCode) {
const [product] = await db.select().from(products)
.where(eq(products.code, item.itemCode));
if (product) {
const newQty = Math.max(0, parseFloat(product.stockQty as string || "0") - (item.quantity || 1));
await db.update(products)
.set({ stockQty: String(newQty) })
.where(eq(products.id, product.id));
}
}
await db.update(serviceOrderItems)
.set({ status: "applied" })
.where(eq(serviceOrderItems.id, item.id));
}
// Auto-create warranty when OS is completed
if (req.body.status === "completed" && order.imei) {
const warrantyDays = order.serviceType === "repair" ? 90 : order.serviceType === "maintenance" ? 30 : 60;
const startDate = new Date().toISOString().split('T')[0];
const endDateObj = new Date();
endDateObj.setDate(endDateObj.getDate() + warrantyDays);
await db.insert(serviceWarranties).values({
tenantId: order.tenantId,
storeId: order.storeId,
serviceOrderId: order.id,
deviceId: order.deviceId,
imei: order.imei,
serviceType: order.serviceType || "repair",
warrantyDays,
startDate,
endDate: endDateObj.toISOString().split('T')[0],
customerName: order.customerName,
customerPhone: order.customerPhone,
description: `Garantia automática - OS ${order.orderNumber}`
});
}
}
// Se a OS é de Trade-In e tem uma avaliação vinculada, atualizar o status da avaliação também
if (order.isInternal && order.sourceEvaluationId && req.body.evaluationStatus) {
const evaluationStatusMap: Record<string, string> = {
@ -1651,6 +1738,46 @@ router.post("/service-orders/:id/items", async (req: Request, res: Response) =>
}
});
router.get("/service-orders/:id/items", async (req: Request, res: Response) => {
try {
const items = await db.select().from(serviceOrderItems)
.where(eq(serviceOrderItems.serviceOrderId, parseInt(req.params.id)))
.orderBy(desc(serviceOrderItems.createdAt));
res.json(items);
} catch (error) {
console.error("Error fetching service order items:", error);
res.status(500).json({ error: "Failed to fetch items" });
}
});
router.delete("/service-orders/:id/items/:itemId", async (req: Request, res: Response) => {
try {
const serviceOrderId = parseInt(req.params.id);
const itemId = parseInt(req.params.itemId);
await db.delete(serviceOrderItems).where(
and(
eq(serviceOrderItems.id, itemId),
eq(serviceOrderItems.serviceOrderId, serviceOrderId)
)
);
const items = await db.select().from(serviceOrderItems)
.where(eq(serviceOrderItems.serviceOrderId, serviceOrderId));
const partsCost = items.filter(i => i.itemType === 'part').reduce((sum, i) => sum + parseFloat(i.totalPrice as any), 0);
const laborCost = items.filter(i => i.itemType === 'labor').reduce((sum, i) => sum + parseFloat(i.totalPrice as any), 0);
await db.update(serviceOrders)
.set({ partsCost: String(partsCost), laborCost: String(laborCost), totalCost: String(partsCost + laborCost), updatedAt: sql`CURRENT_TIMESTAMP` })
.where(eq(serviceOrders.id, serviceOrderId));
res.json({ success: true, items });
} catch (error) {
console.error("Error removing service order item:", error);
res.status(500).json({ error: "Failed to remove item" });
}
});
// ========== POS SESSIONS ==========
router.get("/pos-sessions", async (req: Request, res: Response) => {
try {
@ -1726,6 +1853,319 @@ router.put("/pos-sessions/:id/close", async (req: Request, res: Response) => {
}
});
// ========== CASH MOVEMENTS (Sangria/Reforço) ==========
router.get("/cash-movements", async (req: Request, res: Response) => {
try {
const { sessionId } = req.query;
const tenantId = (req.user as any)?.tenantId;
const conditions = [];
if (sessionId) conditions.push(eq(posCashMovements.sessionId, parseInt(sessionId as string)));
if (tenantId) conditions.push(eq(posCashMovements.tenantId, tenantId));
const movements = conditions.length > 0
? await db.select().from(posCashMovements).where(and(...conditions)).orderBy(desc(posCashMovements.createdAt))
: await db.select().from(posCashMovements).orderBy(desc(posCashMovements.createdAt));
res.json(movements);
} catch (error) {
console.error("Error fetching cash movements:", error);
res.status(500).json({ error: "Failed to fetch cash movements" });
}
});
router.post("/cash-movements", async (req: Request, res: Response) => {
try {
const user = (req as any).user;
const [movement] = await db.insert(posCashMovements).values({
...req.body,
performedBy: user?.id,
performedByName: user?.name || user?.username,
tenantId: user?.tenantId
}).returning();
res.json(movement);
} catch (error) {
console.error("Error creating cash movement:", error);
res.status(500).json({ error: "Failed to create cash movement" });
}
});
// ========== SERVICE WARRANTIES ==========
router.get("/warranties", async (req: Request, res: Response) => {
try {
const { imei, status, serviceOrderId } = req.query;
const tenantId = (req.user as any)?.tenantId;
const conditions = [];
if (tenantId) conditions.push(eq(serviceWarranties.tenantId, tenantId));
if (imei) conditions.push(eq(serviceWarranties.imei, imei as string));
if (status) conditions.push(eq(serviceWarranties.status, status as string));
if (serviceOrderId) conditions.push(eq(serviceWarranties.serviceOrderId, parseInt(serviceOrderId as string)));
const warranties = conditions.length > 0
? await db.select().from(serviceWarranties).where(and(...conditions)).orderBy(desc(serviceWarranties.createdAt))
: await db.select().from(serviceWarranties).orderBy(desc(serviceWarranties.createdAt));
res.json(warranties);
} catch (error) {
console.error("Error fetching warranties:", error);
res.status(500).json({ error: "Failed to fetch warranties" });
}
});
router.post("/warranties", async (req: Request, res: Response) => {
try {
const user = (req as any).user;
const startDate = req.body.startDate || new Date().toISOString().split('T')[0];
const warrantyDays = parseInt(req.body.warrantyDays || 90);
const endDateObj = new Date(startDate);
endDateObj.setDate(endDateObj.getDate() + warrantyDays);
const endDate = endDateObj.toISOString().split('T')[0];
const [warranty] = await db.insert(serviceWarranties).values({
...req.body,
startDate,
endDate,
warrantyDays,
tenantId: user?.tenantId
}).returning();
res.json(warranty);
} catch (error) {
console.error("Error creating warranty:", error);
res.status(500).json({ error: "Failed to create warranty" });
}
});
router.get("/warranties/check/:imei", async (req: Request, res: Response) => {
try {
const tenantId = (req.user as any)?.tenantId;
const today = new Date().toISOString().split('T')[0];
const conditions = [
eq(serviceWarranties.imei, req.params.imei),
eq(serviceWarranties.status, "active"),
gte(serviceWarranties.endDate, today)
];
if (tenantId) conditions.push(eq(serviceWarranties.tenantId, tenantId));
const warranties = await db.select().from(serviceWarranties)
.where(and(...conditions))
.orderBy(desc(serviceWarranties.endDate));
res.json({ hasActiveWarranty: warranties.length > 0, warranties });
} catch (error) {
console.error("Error checking warranty:", error);
res.status(500).json({ error: "Failed to check warranty" });
}
});
router.put("/warranties/:id/claim", async (req: Request, res: Response) => {
try {
const [warranty] = await db.update(serviceWarranties)
.set({ status: "claimed", claimedAt: sql`CURRENT_TIMESTAMP`, claimNotes: req.body.notes })
.where(eq(serviceWarranties.id, parseInt(req.params.id)))
.returning();
res.json(warranty);
} catch (error) {
console.error("Error claiming warranty:", error);
res.status(500).json({ error: "Failed to claim warranty" });
}
});
// ========== STOCK ALERTS ==========
router.get("/stock-alerts", async (req: Request, res: Response) => {
try {
const tenantId = (req.user as any)?.tenantId;
const conditions = [
eq(products.status, "active"),
sql`CAST(${products.stockQty} AS NUMERIC) <= CAST(${products.minStock} AS NUMERIC)`,
sql`CAST(${products.minStock} AS NUMERIC) > 0`
];
if (tenantId) conditions.push(eq(products.tenantId, tenantId));
const lowStockProducts = await db.select().from(products)
.where(and(...conditions))
.orderBy(asc(products.name));
res.json(lowStockProducts);
} catch (error) {
console.error("Error fetching stock alerts:", error);
res.status(500).json({ error: "Failed to fetch stock alerts" });
}
});
// ========== REPORTS ==========
router.get("/reports/os-by-status", async (req: Request, res: Response) => {
try {
const tenantId = (req.user as any)?.tenantId;
if (!tenantId) return res.status(403).json({ error: "Tenant not identified" });
const result = await db.execute(sql`
SELECT status, COUNT(*) as count,
COALESCE(SUM(CAST(total_cost AS NUMERIC)), 0) as total_value
FROM service_orders WHERE tenant_id = ${tenantId}
GROUP BY status ORDER BY count DESC
`);
res.json(result.rows || result);
} catch (error) {
console.error("Error fetching OS report:", error);
res.status(500).json({ error: "Failed to fetch report" });
}
});
router.get("/reports/os-by-technician", async (req: Request, res: Response) => {
try {
const tenantId = (req.user as any)?.tenantId;
if (!tenantId) return res.status(403).json({ error: "Tenant not identified" });
const result = await db.execute(sql`
SELECT technician_name,
COUNT(*) as total_os,
COUNT(*) FILTER (WHERE status = 'completed') as completed,
COUNT(*) FILTER (WHERE status IN ('open','diagnosis','in_repair')) as in_progress,
COALESCE(SUM(CAST(total_cost AS NUMERIC)) FILTER (WHERE status = 'completed'), 0) as total_revenue
FROM service_orders
WHERE technician_name IS NOT NULL AND tenant_id = ${tenantId}
GROUP BY technician_name ORDER BY total_os DESC
`);
res.json(result.rows || result);
} catch (error) {
console.error("Error fetching technician report:", error);
res.status(500).json({ error: "Failed to fetch report" });
}
});
router.get("/reports/sales-by-seller", async (req: Request, res: Response) => {
try {
const { dateFrom, dateTo } = req.query;
const tenantId = (req.user as any)?.tenantId;
if (!tenantId) return res.status(403).json({ error: "Tenant not identified" });
let dateFilter = sql`TRUE`;
if (dateFrom && dateTo) {
dateFilter = sql`created_at >= ${dateFrom}::date AND created_at <= (${dateTo}::date + INTERVAL '1 day')`;
} else {
dateFilter = sql`DATE(created_at) >= CURRENT_DATE - INTERVAL '30 days'`;
}
const tenantFilter = sql`AND tenant_id = ${tenantId}`;
const result = await db.execute(sql`
SELECT sold_by,
COUNT(*) as total_sales,
COALESCE(SUM(CAST(total_amount AS NUMERIC)), 0) as total_revenue,
COALESCE(AVG(CAST(total_amount AS NUMERIC)), 0) as avg_ticket,
COUNT(DISTINCT DATE(created_at)) as active_days
FROM pos_sales
WHERE status = 'completed' AND ${dateFilter} ${tenantFilter}
GROUP BY sold_by ORDER BY total_revenue DESC
`);
res.json(result.rows || result);
} catch (error) {
console.error("Error fetching sales report:", error);
res.status(500).json({ error: "Failed to fetch report" });
}
});
router.get("/reports/margin-by-imei", async (req: Request, res: Response) => {
try {
const result = await db.execute(sql`
SELECT d.id, d.brand, d.model, d.imei, d.condition,
CAST(d.purchase_price AS NUMERIC) as cost,
CAST(d.selling_price AS NUMERIC) as sale_price,
CAST(d.selling_price AS NUMERIC) - CAST(d.purchase_price AS NUMERIC) as margin,
CASE WHEN CAST(d.purchase_price AS NUMERIC) > 0
THEN ROUND(((CAST(d.selling_price AS NUMERIC) - CAST(d.purchase_price AS NUMERIC)) / CAST(d.purchase_price AS NUMERIC)) * 100, 2)
ELSE 0 END as margin_percent,
d.status
FROM mobile_devices d
WHERE d.purchase_price IS NOT NULL AND CAST(d.purchase_price AS NUMERIC) > 0
ORDER BY margin DESC
`);
res.json(result.rows || result);
} catch (error) {
console.error("Error fetching margin report:", error);
res.status(500).json({ error: "Failed to fetch report" });
}
});
router.get("/reports/daily-cash", async (req: Request, res: Response) => {
try {
const { date } = req.query;
const tenantId = (req.user as any)?.tenantId;
if (!tenantId) return res.status(403).json({ error: "Tenant not identified" });
const targetDate = date ? String(date) : new Date().toISOString().split('T')[0];
const tenantFilter = sql`AND tenant_id = ${tenantId}`;
const summaryResult = await db.execute(sql`
SELECT
COALESCE(SUM(CAST(total_amount AS NUMERIC)), 0) as total_sales,
COUNT(*) as sale_count,
COALESCE(SUM(CAST(total_amount AS NUMERIC)) FILTER (WHERE payment_method = 'cash'), 0) as cash_total,
COALESCE(SUM(CAST(total_amount AS NUMERIC)) FILTER (WHERE payment_method IN ('credit','debit')), 0) as card_total,
COALESCE(SUM(CAST(total_amount AS NUMERIC)) FILTER (WHERE payment_method = 'pix'), 0) as pix_total,
COALESCE(SUM(CAST(total_amount AS NUMERIC)) FILTER (WHERE payment_method = 'combined'), 0) as combined_total
FROM pos_sales
WHERE status = 'completed' AND DATE(created_at) = ${targetDate}::date ${tenantFilter}
`);
const movementsResult = await db.execute(sql`
SELECT
COALESCE(SUM(CAST(amount AS NUMERIC)) FILTER (WHERE type = 'withdrawal'), 0) as withdrawals,
COALESCE(SUM(CAST(amount AS NUMERIC)) FILTER (WHERE type = 'reinforcement'), 0) as reinforcements
FROM pos_cash_movements
WHERE DATE(created_at) = ${targetDate}::date ${tenantFilter}
`);
const salesResult = await db.execute(sql`
SELECT id, sale_number, customer_name, total_amount, payment_method, sold_by,
discount_amount, subtotal, created_at, notes
FROM pos_sales
WHERE status = 'completed' AND DATE(created_at) = ${targetDate}::date ${tenantFilter}
ORDER BY created_at DESC
`);
const bySellerResult = await db.execute(sql`
SELECT
sold_by,
COUNT(*) as sale_count,
COALESCE(SUM(CAST(total_amount AS NUMERIC)), 0) as total,
COALESCE(SUM(CAST(total_amount AS NUMERIC)) FILTER (WHERE payment_method = 'cash'), 0) as cash_total,
COALESCE(SUM(CAST(total_amount AS NUMERIC)) FILTER (WHERE payment_method IN ('credit','debit')), 0) as card_total,
COALESCE(SUM(CAST(total_amount AS NUMERIC)) FILTER (WHERE payment_method = 'credit'), 0) as credit_total,
COALESCE(SUM(CAST(total_amount AS NUMERIC)) FILTER (WHERE payment_method = 'debit'), 0) as debit_total,
COALESCE(SUM(CAST(total_amount AS NUMERIC)) FILTER (WHERE payment_method = 'pix'), 0) as pix_total,
COALESCE(SUM(CAST(total_amount AS NUMERIC)) FILTER (WHERE payment_method = 'combined'), 0) as combined_total,
COALESCE(SUM(CAST(discount_amount AS NUMERIC)), 0) as total_discount
FROM pos_sales
WHERE status = 'completed' AND DATE(created_at) = ${targetDate}::date ${tenantFilter}
GROUP BY sold_by
ORDER BY total DESC
`);
const summary = (summaryResult.rows as any[])?.[0] || {};
const movements = (movementsResult.rows as any[])?.[0] || {};
res.json({
...summary,
withdrawals: movements.withdrawals || 0,
reinforcements: movements.reinforcements || 0,
sales: salesResult.rows || [],
bySeller: bySellerResult.rows || [],
date: targetDate
});
} catch (error) {
console.error("Error fetching daily cash report:", error);
res.status(500).json({ error: "Failed to fetch report" });
}
});
router.get("/reports/stock-turnover", async (req: Request, res: Response) => {
try {
const result = await db.execute(sql`
SELECT p.id, p.code, p.name, p.category,
CAST(p.stock_qty AS NUMERIC) as current_stock,
CAST(p.min_stock AS NUMERIC) as min_stock,
COALESCE((SELECT COUNT(*) FROM pos_sale_items psi WHERE psi.item_code = p.code AND psi.created_at >= CURRENT_DATE - INTERVAL '30 days'), 0) as sales_30d,
CASE WHEN CAST(p.stock_qty AS NUMERIC) > 0
THEN ROUND(COALESCE((SELECT COUNT(*) FROM pos_sale_items psi WHERE psi.item_code = p.code AND psi.created_at >= CURRENT_DATE - INTERVAL '30 days'), 0)::numeric / CAST(p.stock_qty AS NUMERIC), 2)
ELSE 0 END as turnover_ratio
FROM products p
WHERE p.status = 'active'
ORDER BY turnover_ratio DESC
LIMIT 50
`);
res.json(result.rows || result);
} catch (error) {
console.error("Error fetching stock turnover:", error);
res.status(500).json({ error: "Failed to fetch report" });
}
});
// ========== POS SALES ==========
router.get("/sales", async (req: Request, res: Response) => {
try {
@ -2078,8 +2518,19 @@ router.put("/transfers/:id/receive", async (req: Request, res: Response) => {
// ========== PDV PRODUCTS ==========
router.get("/pdv-products", async (req: Request, res: Response) => {
try {
const tenantId = (req.user as any)?.tenantId;
const conditions = [
eq(products.status, "active"),
or(
eq(products.trackingType, "none"),
isNull(products.trackingType)
)
];
if (tenantId) {
conditions.push(eq(products.tenantId, tenantId));
}
const allProducts = await db.select().from(products)
.where(eq(products.status, "active"))
.where(and(...conditions))
.orderBy(asc(products.name));
res.json(allProducts);
} catch (error) {
@ -2898,7 +3349,7 @@ router.post("/evaluations/:id/approve-and-process", async (req: Request, res: Re
// 3. Record in IMEI history
await db.insert(imeiHistory).values({
tenantId: evaluation.tenantId,
deviceId: null as any, // Will be created when OS is finalized
deviceId: null as any,
imei: evaluation.imei,
action: "trade_in_approved",
newStatus: "in_revision",
@ -2909,12 +3360,47 @@ router.post("/evaluations/:id/approve-and-process", async (req: Request, res: Re
createdBy: (req.user as any)?.id,
createdByName: (req.user as any)?.name || "Sistema",
});
// 4. Generate customer credit if person and value are valid
let credit = null;
const personId = evaluation.personId;
const estimatedValueNum = evaluation.estimatedValue ? parseFloat(evaluation.estimatedValue) : 0;
if (personId && estimatedValueNum > 0) {
let customerCpf = null;
let customerName = evaluation.customerName || "Cliente";
const [person] = await db.select().from(persons)
.where(eq(persons.id, personId))
.limit(1);
if (person) {
customerCpf = person.cpfCnpj;
customerName = person.fullName;
}
[credit] = await db.insert(customerCredits).values({
storeId: evaluation.storeId || undefined,
personId: personId,
customerName,
customerCpf: customerCpf || undefined,
amount: evaluation.estimatedValue!,
remainingAmount: evaluation.estimatedValue!,
origin: "trade_in",
originId: evaluation.id,
description: `Trade-In: ${evaluation.brand} ${evaluation.model} (IMEI: ${evaluation.imei})`,
status: "active",
createdBy: (req.user as any)?.id
}).returning();
await db.update(deviceEvaluations)
.set({ creditGenerated: true, creditId: credit.id })
.where(eq(deviceEvaluations.id, evaluationId));
}
res.json({
success: true,
message: "Trade-In aprovado e O.S. Interna criada com sucesso",
evaluation: { ...evaluation, status: "approved" },
serviceOrder,
credit,
nextStep: "Realize a revisão/manutenção do dispositivo e finalize a O.S. para entrada no estoque"
});
} catch (error) {
@ -4604,5 +5090,129 @@ router.post("/customer-credits/:creditId/use", async (req: Request, res: Respons
}
});
// ========== PLUS ERP SYNC ROUTES ==========
const plusSync = retailPlusSyncService;
router.get("/plus/status", async (req: Request, res: Response) => {
try {
const status = await plusSync.getPlusStatus();
res.json(status);
} catch (error) {
console.error("Error checking Plus status:", error);
res.status(500).json({ error: "Failed to check Plus connection" });
}
});
router.post("/plus/sync/customers", requireModule("plus"), async (req: Request, res: Response) => {
try {
const { tenantId, empresaId } = req.body;
if (!tenantId) return res.status(400).json({ error: "tenantId required" });
const result = await plusSync.syncAllPersonsToPlus(tenantId, empresaId);
res.json(result);
} catch (error) {
console.error("Error syncing customers to Plus:", error);
res.status(500).json({ error: "Failed to sync customers" });
}
});
router.post("/plus/sync/sales", requireModule("plus"), async (req: Request, res: Response) => {
try {
const { saleId } = req.body;
if (!saleId) return res.status(400).json({ error: "saleId required" });
const result = await plusSync.syncSaleToPlus(saleId);
res.json(result);
} catch (error) {
console.error("Error syncing sale to Plus:", error);
res.status(500).json({ error: "Failed to sync sale" });
}
});
router.post("/plus/sync/nfe", requireModule("plus"), async (req: Request, res: Response) => {
try {
const { saleId, tipo } = req.body;
if (!saleId) return res.status(400).json({ error: "saleId required" });
const result = await plusSync.emitirNFeSale(saleId, tipo || "nfce");
res.json(result);
} catch (error) {
console.error("Error emitting NF-e:", error);
res.status(500).json({ error: "Failed to emit NF-e" });
}
});
router.post("/plus/import/customers", requireModule("plus"), async (req: Request, res: Response) => {
try {
const { tenantId, empresaId } = req.body;
if (!tenantId) return res.status(400).json({ error: "tenantId required" });
const result = await plusSync.importClientesFromPlus(tenantId, empresaId);
res.json(result);
} catch (error) {
console.error("Error importing customers from Plus:", error);
res.status(500).json({ error: "Failed to import customers" });
}
});
router.post("/plus/import/products", requireModule("plus"), async (req: Request, res: Response) => {
try {
const { tenantId, empresaId } = req.body;
if (!tenantId) return res.status(400).json({ error: "tenantId required" });
const result = await plusSync.importProdutosFromPlus(tenantId, empresaId);
res.json(result);
} catch (error) {
console.error("Error importing products from Plus:", error);
res.status(500).json({ error: "Failed to import products" });
}
});
router.get("/plus/empresas", async (req: Request, res: Response) => {
try {
const tenantId = parseInt(req.query.tenantId as string) || 1;
const empresas = await db.select().from(tenantEmpresas)
.where(and(eq(tenantEmpresas.tenantId, tenantId), eq(tenantEmpresas.status, "active")));
res.json(empresas);
} catch (error) {
console.error("Error fetching empresas:", error);
res.status(500).json({ error: "Failed to fetch empresas" });
}
});
router.post("/plus/empresas", async (req: Request, res: Response) => {
try {
const tenantId = parseInt(req.body.tenantId as string) || 1;
const { razaoSocial, nomeFantasia, cnpj, tipo } = req.body;
if (!razaoSocial || !cnpj) return res.status(400).json({ error: "razaoSocial and cnpj required" });
const [empresa] = await db.insert(tenantEmpresas).values({
tenantId,
razaoSocial,
nomeFantasia: nomeFantasia || razaoSocial,
cnpj,
tipo: tipo || "matriz",
status: "active",
}).returning();
res.json(empresa);
} catch (error) {
console.error("Error creating empresa:", error);
res.status(500).json({ error: "Failed to create empresa" });
}
});
router.post("/plus/empresas/:id/bind", async (req: Request, res: Response) => {
try {
const empresaLocalId = parseInt(req.params.id);
const { plusEmpresaId } = req.body;
if (!plusEmpresaId) return res.status(400).json({ error: "plusEmpresaId required" });
const [updated] = await db.update(tenantEmpresas)
.set({ plusEmpresaId })
.where(eq(tenantEmpresas.id, empresaLocalId))
.returning();
res.json(updated);
} catch (error) {
console.error("Error binding empresa to Plus:", error);
res.status(500).json({ error: "Failed to bind empresa" });
}
});
export default router;

View File

@ -5,7 +5,7 @@ import { insertApplicationSchema } from "@shared/schema";
import { z } from "zod";
import { setupAuth } from "./auth";
import { registerChatRoutes } from "./replit_integrations/chat";
import { registerErpRoutes } from "./erp/routes";
import { registerSoeRoutes, registerErpRoutes } from "./erp/routes";
import { registerInternalChatRoutes } from "./chat/routes";
import { setupChatSocket } from "./chat/socket";
import { setupCommunitySocket } from "./communities/socket";
@ -57,6 +57,7 @@ import autonomousRoutes from "./autonomous/routes";
import blackboardRoutes from "./blackboard/routes";
import pipelineRoutes from "./blackboard/pipelineRoutes";
import { startAllAgents } from "./blackboard/agents";
import { loadModuleRoutes } from "./modules/loader";
export async function registerRoutes(
httpServer: Server,
@ -72,7 +73,7 @@ export async function registerRoutes(
setupMetabaseProxy(app);
registerChatRoutes(app);
registerErpRoutes(app);
registerSoeRoutes(app);
registerInternalChatRoutes(app);
setupChatSocket(httpServer);
setupCommunitySocket(httpServer);
@ -120,6 +121,9 @@ export async function registerRoutes(
app.use("/api/blackboard", blackboardRoutes);
app.use("/api/xos/pipeline", pipelineRoutes);
// Auto-loader de módulos criados pelo Dev Center
await loadModuleRoutes(app);
// Iniciar os 6 agentes do Blackboard
startAllAgents();

View File

@ -3,6 +3,9 @@ import { pgTable, text, varchar, primaryKey, serial, integer, timestamp, numeric
import { createInsertSchema } from "drizzle-zod";
import { z } from "zod";
// Re-exportar schemas modulares criados pelo Dev Center
export * from "./schemas/index";
export const users = pgTable("users", {
id: varchar("id").primaryKey().default(sql`gen_random_uuid()`),
username: text("username").notNull().unique(),
@ -832,6 +835,14 @@ export type TenantFeatures = {
biblioteca: boolean;
bibliotecaPublish: boolean;
suporteN3: boolean;
retail: boolean;
plus: boolean;
fisco: boolean;
cockpit: boolean;
compass: boolean;
production: boolean;
support: boolean;
xosCrm: boolean;
};
// Tenants (Master/Parceiros/Clientes)
@ -863,6 +874,49 @@ export const tenants = pgTable("tenants", {
// Contato comercial
commercialContact: text("commercial_contact"),
commercialPhone: text("commercial_phone"),
// Dados empresariais (unificado com CRM)
cnpj: text("cnpj"),
tradeName: text("trade_name"),
address: text("address"),
city: text("city"),
state: text("state"),
segment: text("segment"),
notes: text("notes"),
source: text("source"),
});
// Empresas do Tenant (Matriz + Filiais)
export const tenantEmpresas = pgTable("tenant_empresas", {
id: serial("id").primaryKey(),
tenantId: integer("tenant_id").notNull().references(() => tenants.id, { onDelete: "cascade" }),
razaoSocial: text("razao_social").notNull(),
nomeFantasia: text("nome_fantasia"),
cnpj: text("cnpj").notNull(),
ie: text("ie"),
im: text("im"),
email: text("email"),
phone: text("phone"),
tipo: text("tipo").default("filial"), // matriz, filial
status: text("status").default("active"), // active, inactive
cep: text("cep"),
logradouro: text("logradouro"),
numero: text("numero"),
complemento: text("complemento"),
bairro: text("bairro"),
cidade: text("cidade"),
uf: text("uf"),
codigoIbge: text("codigo_ibge"),
// Fiscal
regimeTributario: text("regime_tributario"), // simples, presumido, real
certificadoDigitalId: integer("certificado_digital_id"),
ambienteFiscal: text("ambiente_fiscal").default("homologacao"), // producao, homologacao
serieNfe: integer("serie_nfe").default(1),
serieNfce: integer("serie_nfce").default(1),
// Plus ERP link
plusEmpresaId: integer("plus_empresa_id"),
// Metadata
createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),
updatedAt: timestamp("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(),
});
// Tenant Users (Membership)
@ -927,6 +981,7 @@ export const insertTenantUserSchema = createInsertSchema(tenantUsers).omit({ id:
export const insertTenantPlanSchema = createInsertSchema(tenantPlans).omit({ id: true, createdAt: true });
export const insertPartnerClientSchema = createInsertSchema(partnerClients).omit({ id: true, startedAt: true });
export const insertPartnerCommissionSchema = createInsertSchema(partnerCommissions).omit({ id: true, createdAt: true });
export const insertTenantEmpresaSchema = createInsertSchema(tenantEmpresas).omit({ id: true, createdAt: true, updatedAt: true });
export type Tenant = typeof tenants.$inferSelect;
export type InsertTenant = z.infer<typeof insertTenantSchema>;
@ -938,6 +993,8 @@ export type PartnerClient = typeof partnerClients.$inferSelect;
export type InsertPartnerClient = z.infer<typeof insertPartnerClientSchema>;
export type PartnerCommission = typeof partnerCommissions.$inferSelect;
export type InsertPartnerCommission = z.infer<typeof insertPartnerCommissionSchema>;
export type TenantEmpresa = typeof tenantEmpresas.$inferSelect;
export type InsertTenantEmpresa = z.infer<typeof insertTenantEmpresaSchema>;
// ==========================================
// PROCESS COMPASS - CONSULTING BACK-OFFICE
@ -5078,6 +5135,7 @@ export const serviceOrders = pgTable("service_orders", {
export const serviceOrderItems = pgTable("service_order_items", {
id: serial("id").primaryKey(),
serviceOrderId: integer("service_order_id").references(() => serviceOrders.id).notNull(),
productId: integer("product_id").references(() => products.id),
itemType: varchar("item_type", { length: 20 }).default("part"), // part, labor, accessory
itemCode: varchar("item_code", { length: 50 }),
itemName: varchar("item_name", { length: 200 }).notNull(),
@ -5137,6 +5195,12 @@ export const posSales = pgTable("pos_sales", {
status: varchar("status", { length: 20 }).default("completed"), // pending, completed, cancelled, refunded
soldBy: varchar("sold_by"),
notes: text("notes"),
plusVendaId: integer("plus_venda_id"),
plusNfeChave: varchar("plus_nfe_chave", { length: 60 }),
plusSyncStatus: varchar("plus_sync_status", { length: 20 }).default("pending"), // pending, synced, error, not_applicable
plusSyncError: text("plus_sync_error"),
plusSyncedAt: timestamp("plus_synced_at"),
empresaId: integer("empresa_id").references(() => tenantEmpresas.id),
createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),
});
@ -5900,6 +5964,51 @@ export const insertTradeInTransferDocumentSchema = createInsertSchema(tradeInTra
export type TradeInTransferDocument = typeof tradeInTransferDocuments.$inferSelect;
export type InsertTradeInTransferDocument = z.infer<typeof insertTradeInTransferDocumentSchema>;
// POS Cash Movements - Sangria e Reforço de Caixa
export const posCashMovements = pgTable("pos_cash_movements", {
id: serial("id").primaryKey(),
tenantId: integer("tenant_id").references(() => tenants.id),
sessionId: integer("session_id").references(() => posSessions.id).notNull(),
storeId: integer("store_id").references(() => retailStores.id).notNull(),
type: varchar("type", { length: 20 }).notNull(), // withdrawal (sangria), reinforcement (reforço)
amount: numeric("amount", { precision: 12, scale: 2 }).notNull(),
reason: text("reason"),
performedBy: varchar("performed_by"),
performedByName: varchar("performed_by_name", { length: 200 }),
authorizedBy: varchar("authorized_by"),
authorizedByName: varchar("authorized_by_name", { length: 200 }),
createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),
});
export const insertPosCashMovementSchema = createInsertSchema(posCashMovements).omit({ id: true, createdAt: true });
export type PosCashMovement = typeof posCashMovements.$inferSelect;
export type InsertPosCashMovement = z.infer<typeof insertPosCashMovementSchema>;
// Service Warranties - Garantias de Serviço vinculadas à OS e IMEI
export const serviceWarranties = pgTable("service_warranties", {
id: serial("id").primaryKey(),
tenantId: integer("tenant_id").references(() => tenants.id),
storeId: integer("store_id").references(() => retailStores.id),
serviceOrderId: integer("service_order_id").references(() => serviceOrders.id).notNull(),
deviceId: integer("device_id").references(() => mobileDevices.id),
imei: varchar("imei", { length: 20 }),
serviceType: varchar("service_type", { length: 50 }).notNull(),
warrantyDays: integer("warranty_days").notNull(),
startDate: date("start_date").notNull(),
endDate: date("end_date").notNull(),
customerName: varchar("customer_name", { length: 200 }),
customerPhone: varchar("customer_phone", { length: 20 }),
description: text("description"),
status: varchar("status", { length: 20 }).default("active"), // active, expired, claimed, voided
claimedAt: timestamp("claimed_at"),
claimNotes: text("claim_notes"),
createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),
});
export const insertServiceWarrantySchema = createInsertSchema(serviceWarranties).omit({ id: true, createdAt: true });
export type ServiceWarranty = typeof serviceWarranties.$inferSelect;
export type InsertServiceWarranty = z.infer<typeof insertServiceWarrantySchema>;
// Customer Credits - Créditos de Cliente (Trade-In, Devoluções, etc.)
export const customerCredits = pgTable("customer_credits", {
id: serial("id").primaryKey(),
@ -5956,6 +6065,9 @@ export const persons = pgTable("persons", {
erpnextCustomerId: varchar("erpnext_customer_id", { length: 140 }),
erpnextSupplierId: varchar("erpnext_supplier_id", { length: 140 }),
erpnextEmployeeId: varchar("erpnext_employee_id", { length: 140 }),
// Plus Sync Fields
plusClienteId: integer("plus_cliente_id"),
plusFornecedorId: integer("plus_fornecedor_id"),
lastSyncAt: timestamp("last_sync_at"),
createdAt: timestamp("created_at").default(sql`CURRENT_TIMESTAMP`).notNull(),
updatedAt: timestamp("updated_at").default(sql`CURRENT_TIMESTAMP`).notNull(),