feat: Arcádia Suite v2.0 - Sistema completo com 66 páginas, 6 motores, módulo Retail
This commit is contained in:
parent
065ab19a17
commit
ec173da844
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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*
|
||||
|
|
@ -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
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -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
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
112
shared/schema.ts
112
shared/schema.ts
|
|
@ -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(),
|
||||
|
|
|
|||
Loading…
Reference in New Issue