Merge pull request 'Adição de migrations' (#1) from dev into master
Reviewed-on: https://chaleiradev.sothistelecom.com/Sothis/omnichannel-deploy/pulls/1
This commit is contained in:
commit
f4e6dc9fb0
44
.env.example
Normal file
44
.env.example
Normal file
@ -0,0 +1,44 @@
|
||||
# Deploy (docker-compose) environment variables
|
||||
#
|
||||
# Docker Compose sobe somente frontend e backend.
|
||||
# O PostgreSQL deve existir fora do compose, em uma instancia local, VM, RDS,
|
||||
# container separado ou banco corporativo.
|
||||
|
||||
# App database connection (used by backend)
|
||||
DB_HOST=db.empresa.local
|
||||
DB_PORT=5432
|
||||
DB_USER=omnichannel
|
||||
DB_PASSWORD=change-me
|
||||
DB_NAME=omnichannel
|
||||
|
||||
# Backend HTTP/JWT
|
||||
NODE_ENV=development
|
||||
PORT=3001
|
||||
FRONTEND_URL=http://localhost:4000
|
||||
JWT_SECRET=change-this-long-random-secret
|
||||
JWT_EXPIRES_IN=8h
|
||||
REQUEST_BODY_LIMIT=25mb
|
||||
|
||||
# Auth providers: ldap,microsoft or only one of them
|
||||
AUTH_PROVIDERS=ldap,microsoft
|
||||
|
||||
# LDAP / Active Directory
|
||||
LDAP_ENABLED=true
|
||||
LDAP_URL=ldaps://servidor-ad:636
|
||||
LDAP_DOMAIN=empresa.com.br
|
||||
LDAP_USER_DN_TEMPLATE={{username}}@empresa.com.br
|
||||
LDAP_SEARCH_BASE=DC=empresa,DC=com
|
||||
LDAP_SEARCH_FILTER=(&(objectClass=user)(sAMAccountName={{username}}))
|
||||
LDAP_TIMEOUT_MS=5000
|
||||
|
||||
# Optional LDAP bind account when search requires service credentials
|
||||
# LDAP_BIND_DN=CN=ldap-reader,OU=Users,DC=empresa,DC=com
|
||||
# LDAP_BIND_PASSWORD=change-me
|
||||
|
||||
# Microsoft Entra ID OAuth
|
||||
MICROSOFT_ENABLED=false
|
||||
MICROSOFT_TENANT_ID=common
|
||||
MICROSOFT_CLIENT_ID=
|
||||
MICROSOFT_CLIENT_SECRET=
|
||||
MICROSOFT_REDIRECT_URI=http://localhost:4001/auth/oauth/microsoft/callback
|
||||
MICROSOFT_SUCCESS_REDIRECT_URL=http://localhost:4000/login
|
||||
16
.gitignore
vendored
16
.gitignore
vendored
@ -1,5 +1,17 @@
|
||||
node_modules
|
||||
dist
|
||||
frontend/node_modules
|
||||
frontend/dist
|
||||
.env*
|
||||
*.log
|
||||
.DS_Store
|
||||
|
||||
# Backend Specific Ignore
|
||||
backend/node_modules
|
||||
backend/dist
|
||||
backend/whatsapp-session
|
||||
backend/whatsapp-chats-persist.json
|
||||
backend/all-chats-dump.json
|
||||
backend/test-api-out.json
|
||||
|
||||
# Frontend Specific Ignore
|
||||
frontend/node_modules
|
||||
frontend/dist
|
||||
182
README.md
182
README.md
@ -1,49 +1,107 @@
|
||||
# Omnichannel Sothis
|
||||
|
||||
Protótipo visual do frontend MVP do sistema Omnichannel da Sothis.
|
||||
Plataforma omnichannel para atendimento com foco inicial em WhatsApp. O sistema combina atendimento em tempo real, Agente Virtual para triagem, filas por especialidade, abertura ativa por template, agenda de contatos, painéis operacionais e administração de usuários/perfis.
|
||||
|
||||
O foco desta versão é apresentação de produto: a aplicação simula fluxos reais de atendimento com dados mockados, UX moderna e navegação entre telas principais.
|
||||
O projeto foi construído para validar e evoluir um MVP de atendimento corporativo, com perfis de agente, supervisor e administrador.
|
||||
|
||||
## O que existe hoje
|
||||
## Principais Recursos
|
||||
|
||||
- Frontend em React + Vite dentro de `frontend/`
|
||||
- Docker Compose na raiz para subir o frontend desta apresentação
|
||||
- Telas implementadas:
|
||||
- Login
|
||||
- Home / Dashboard
|
||||
- Chat
|
||||
- Call / Softphone mock
|
||||
- Novo Atendimento
|
||||
- Login corporativo via LDAP/Active Directory.
|
||||
- Estrutura para Microsoft OAuth / Entra ID.
|
||||
- JWT próprio da aplicação com perfis e especialidades.
|
||||
- Atendimento WhatsApp em tempo real via `whatsapp-web.js`.
|
||||
- Socket.IO para atualização de chats/mensagens.
|
||||
- Agente Virtual Sothis para triagem e roteamento.
|
||||
- Fila por especialidade.
|
||||
- Assumir, liberar, transferir e fechar atendimento.
|
||||
- Abertura ativa com templates aprovados.
|
||||
- Agenda de contatos com WhatsApp, telefone/SMS, email, etiqueta e observação.
|
||||
- Painel do agente.
|
||||
- Painel operacional do supervisor.
|
||||
- Painel administrativo com usuários, acessos, templates, IA, canais e configurações.
|
||||
- Conteúdos da IA e regras/travas.
|
||||
- Migrations SQL versionadas.
|
||||
|
||||
## Estrutura esperada do ecossistema
|
||||
## Stack Técnica
|
||||
|
||||
Hoje este repositório cobre o frontend e um `docker-compose.yml` local para desenvolvimento/apresentação.
|
||||
### Backend
|
||||
|
||||
Para rodar o ambiente completo no futuro, a separação esperada é:
|
||||
- Node.js 20+ recomendado.
|
||||
- NestJS `^11.1`.
|
||||
- TypeScript `^6.0`.
|
||||
- PostgreSQL via `pg`.
|
||||
- Socket.IO `^4.8`.
|
||||
- `whatsapp-web.js` `^1.34`.
|
||||
- LDAP via `ldapts`.
|
||||
- JWT via `jsonwebtoken`.
|
||||
- Logs com `winston`.
|
||||
|
||||
- `frontend`: interface do produto
|
||||
- `backend`: API e regras de negócio
|
||||
- `deploy`: repositório raiz de infraestrutura/orquestração, onde ficará o `docker-compose` final com frontend, backend, banco e demais serviços
|
||||
### Frontend
|
||||
|
||||
## Como rodar somente o frontend
|
||||
- React `^18.3`.
|
||||
- Vite `^5.4`.
|
||||
- React Router `^6.30`.
|
||||
- Socket.IO Client `^4.8`.
|
||||
|
||||
### Opção 1: com Docker
|
||||
### Banco
|
||||
|
||||
Na raiz deste projeto:
|
||||
- PostgreSQL 16 recomendado.
|
||||
- O banco não é gerenciado pelo `docker-compose.yml` deste repositório.
|
||||
- As migrations ficam em `database/migrations`.
|
||||
|
||||
### Docker
|
||||
|
||||
O Docker Compose da raiz sobe somente:
|
||||
|
||||
- `backend`
|
||||
- `frontend`
|
||||
|
||||
O banco deve ser externo ao compose: VM, banco corporativo, RDS, container separado ou PostgreSQL local gerenciado fora deste projeto.
|
||||
|
||||
## Estrutura do Repositório
|
||||
|
||||
```txt
|
||||
omnichannel/
|
||||
├── backend/ # API NestJS e regras de negócio
|
||||
├── frontend/ # Interface React/Vite
|
||||
├── database/migrations/ # Migrations SQL
|
||||
├── docs/ # Wiki operacional e arquitetura
|
||||
├── docker-compose.yml # Sobe backend e frontend
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Como Subir com Docker Compose
|
||||
|
||||
1. Configure `.env.development` na raiz com os dados do banco externo.
|
||||
2. Garanta que o PostgreSQL externo esteja acessível a partir do container backend.
|
||||
3. Suba backend e frontend:
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Depois acesse:
|
||||
URLs padrão:
|
||||
|
||||
```text
|
||||
http://localhost:3000
|
||||
- Frontend: `http://localhost:4000`
|
||||
- Backend: `http://localhost:4001`
|
||||
|
||||
Health:
|
||||
|
||||
```bash
|
||||
curl http://localhost:4001/health
|
||||
```
|
||||
|
||||
### Opção 2: com Node local
|
||||
## Como Rodar em Desenvolvimento
|
||||
|
||||
Entre na pasta do frontend:
|
||||
Backend:
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Frontend:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
@ -51,39 +109,63 @@ npm install
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Depois acesse:
|
||||
URLs comuns:
|
||||
|
||||
```text
|
||||
http://localhost:3000
|
||||
- Frontend Vite: `http://localhost:5173`
|
||||
- Backend: `http://localhost:3001`
|
||||
|
||||
## Banco e Migrations
|
||||
|
||||
As migrations SQL estão em:
|
||||
|
||||
```txt
|
||||
database/migrations
|
||||
```
|
||||
|
||||
## Como gerar build do frontend
|
||||
Elas representam a intenção de schema final/evolutivo do produto, mas o projeto ainda precisa de um runner formal para aplicar tudo em ordem em ambientes novos.
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm run build
|
||||
```
|
||||
Para ambiente novo, antes de subir backend para uso real:
|
||||
|
||||
## Para rodar o ambiente completo
|
||||
1. criar o banco PostgreSQL;
|
||||
2. aplicar as migrations em ordem;
|
||||
3. validar tabelas principais;
|
||||
4. criar/atribuir usuário admin;
|
||||
5. subir backend e frontend.
|
||||
|
||||
Quando a solução estiver separada em múltiplos repositórios, o fluxo esperado será:
|
||||
Detalhes em:
|
||||
|
||||
1. Fazer `pull` do repositório `frontend`
|
||||
2. Fazer `pull` do repositório `backend`
|
||||
3. Fazer `pull` do repositório `deploy`
|
||||
4. Entrar no repositório `deploy` (raiz de infraestrutura)
|
||||
5. Subir tudo com:
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Em outras palavras: o `docker compose` definitivo do ambiente completo deve ser executado a partir do repositório `deploy`, que será a raiz de orquestração.
|
||||
- [Deploy e operação](./docs/deploy.md)
|
||||
- [Database](./backend/docs/database.md)
|
||||
|
||||
## Documentação
|
||||
|
||||
A documentação funcional do frontend está em [`frontend/docs`](./frontend/docs):
|
||||
Wiki raiz:
|
||||
|
||||
- visão geral do projeto
|
||||
- documentação por módulo/tela
|
||||
- documentação em formato narrativo/RPG para explicar os casos de uso
|
||||
- [docs/README.md](./docs/README.md)
|
||||
- [Deploy e operação](./docs/deploy.md)
|
||||
- [Arquitetura geral](./docs/arquitetura.md)
|
||||
- [Fluxos end-to-end](./docs/fluxos-end-to-end.md)
|
||||
- [Regras de negócio](./docs/regras-negocio.md)
|
||||
- [ADRs](./docs/adrs.md)
|
||||
- [Ambientes](./docs/ambientes.md)
|
||||
- [Runbook](./docs/runbook.md)
|
||||
|
||||
Backend:
|
||||
|
||||
- [backend/docs/README.md](./backend/docs/README.md)
|
||||
- [Auth](./backend/docs/auth.md)
|
||||
- [WhatsApp](./backend/docs/whatsapp.md)
|
||||
- [Admin](./backend/docs/admin.md)
|
||||
- [Swagger/OpenAPI](./backend/docs/swagger.md)
|
||||
|
||||
## Estado Atual e Próximos Passos
|
||||
|
||||
O produto já foi validado em demo com cliente final. Antes de produção real, os principais fechamentos são:
|
||||
|
||||
- implementar guards JWT no backend;
|
||||
- validar autorização por perfil no backend;
|
||||
- formalizar runner de migrations;
|
||||
- configurar backup/restore do banco externo;
|
||||
- persistir sessão WhatsApp em volume;
|
||||
- criar Swagger com DTOs;
|
||||
- adicionar testes nos fluxos críticos.
|
||||
|
||||
1
backend
Submodule
1
backend
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 8790ce70d05d0256ded89ea8fb9335afad41bfa8
|
||||
0
database/Dockerfile
Normal file
0
database/Dockerfile
Normal file
112
database/migrations/001_auth.sql
Normal file
112
database/migrations/001_auth.sql
Normal file
@ -0,0 +1,112 @@
|
||||
-- ============================================================
|
||||
-- Migration 001: Módulo de Autenticação
|
||||
-- Tabelas: usuários, provedores, perfis de acesso e auditoria
|
||||
-- ============================================================
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Tabela: usuarios
|
||||
-- Representa qualquer pessoa que acessa o sistema,
|
||||
-- independente de como ela se autenticou
|
||||
-- ------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS usuarios (
|
||||
id SERIAL PRIMARY KEY,
|
||||
nome VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) UNIQUE, -- pode ser nulo em contas só com LDAP sem email
|
||||
ativo BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_usuarios_email ON usuarios (email);
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Tabela: usuarios_provedores
|
||||
-- Vincula um usuário a um ou mais provedores de autenticação
|
||||
-- Um mesmo usuário pode logar via LDAP e via Microsoft
|
||||
-- ------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS usuarios_provedores (
|
||||
id SERIAL PRIMARY KEY,
|
||||
-- FK para o usuário correspondente
|
||||
usuario_id INTEGER NOT NULL REFERENCES usuarios (id) ON DELETE CASCADE,
|
||||
-- Provedor de autenticação: 'ldap' | 'microsoft' | 'google' | etc.
|
||||
provedor VARCHAR(50) NOT NULL,
|
||||
-- ID do usuário dentro do provedor (azure_id, username do AD, sub do Google...)
|
||||
provedor_user_id VARCHAR(255) NOT NULL,
|
||||
-- Evita duplicidade do mesmo provedor pro mesmo usuário
|
||||
CONSTRAINT uq_provedor_user UNIQUE (provedor, provedor_user_id),
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_usuarios_provedores_usuario ON usuarios_provedores (usuario_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_usuarios_provedores_lookup ON usuarios_provedores (provedor, provedor_user_id);
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Tabela: perfis_acesso
|
||||
-- Define os papéis disponíveis no sistema
|
||||
-- ------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS perfis_acesso (
|
||||
id SERIAL PRIMARY KEY,
|
||||
nome VARCHAR(100) NOT NULL UNIQUE,
|
||||
descricao TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Tabela: usuarios_perfis
|
||||
-- Relacionamento entre usuários e perfis (muitos-para-muitos)
|
||||
-- Um usuário pode ter mais de um perfil se necessário
|
||||
-- ------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS usuarios_perfis (
|
||||
id SERIAL PRIMARY KEY,
|
||||
usuario_id INTEGER NOT NULL REFERENCES usuarios (id) ON DELETE CASCADE,
|
||||
perfil_id INTEGER NOT NULL REFERENCES perfis_acesso (id) ON DELETE CASCADE,
|
||||
|
||||
CONSTRAINT uq_usuario_perfil UNIQUE (usuario_id, perfil_id),
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_usuarios_perfis_usuario ON usuarios_perfis (usuario_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_usuarios_perfis_perfil ON usuarios_perfis (perfil_id);
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Tabela: logs_auditoria
|
||||
-- Registra ações relevantes feitas por usuários ou pelo sistema
|
||||
-- usuario_id NULL = ação do sistema (ex: tentativa de login falha)
|
||||
-- ------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS logs_auditoria (
|
||||
id SERIAL PRIMARY KEY,
|
||||
usuario_id INTEGER REFERENCES usuarios (id) ON DELETE SET NULL,
|
||||
|
||||
-- Ação realizada — ex: 'LOGIN_LDAP', 'LOGIN_MICROSOFT', 'LOGIN_FALHOU', 'USUARIO_CRIADO'
|
||||
acao VARCHAR(100) NOT NULL,
|
||||
|
||||
-- Dados extras livres — ex: { "ip": "...", "provedor": "microsoft", "motivo": "..." }
|
||||
detalhes JSONB,
|
||||
|
||||
ip_origem VARCHAR(45),
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_usuario ON logs_auditoria (usuario_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_acao ON logs_auditoria (acao);
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_created_at ON logs_auditoria (created_at);
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Dados iniciais: perfis de acesso
|
||||
-- ON CONFLICT garante que pode rodar mais de uma vez sem erro
|
||||
-- ------------------------------------------------------------
|
||||
INSERT INTO perfis_acesso (nome, descricao) VALUES
|
||||
('Agente', 'Atendente responsável por responder e encaminhar chamados'),
|
||||
('Supervisor', 'Gestor com visibilidade de filas e relatórios'),
|
||||
('Admin', 'Administrador com acesso total ao sistema')
|
||||
ON CONFLICT (nome) DO NOTHING;
|
||||
63
database/migrations/002_area.sql
Normal file
63
database/migrations/002_area.sql
Normal file
@ -0,0 +1,63 @@
|
||||
-- ============================================================
|
||||
-- Migration 002: Modulo de Areas
|
||||
-- Tabelas: areas e relacionamento usuarios_areas
|
||||
-- ============================================================
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Tabela: areas
|
||||
-- Representa as areas operacionais do atendimento
|
||||
-- Ex: Suporte, Financeiro, Comercial
|
||||
-- ------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS areas (
|
||||
id SERIAL PRIMARY KEY,
|
||||
nome VARCHAR(120) NOT NULL UNIQUE,
|
||||
descricao TEXT,
|
||||
responsavel_usuario_id INTEGER REFERENCES usuarios (id) ON DELETE SET NULL,
|
||||
ativo BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_areas_nome ON areas (nome);
|
||||
CREATE INDEX IF NOT EXISTS idx_areas_responsavel ON areas (responsavel_usuario_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_areas_ativo ON areas (ativo);
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Tabela: usuarios_areas
|
||||
-- Relacionamento muitos-para-muitos entre usuarios e areas
|
||||
-- Um usuario pode atuar em mais de uma area e uma area pode ter
|
||||
-- varios usuarios.
|
||||
-- ------------------------------------------------------------
|
||||
CREATE TABLE IF NOT EXISTS usuarios_areas (
|
||||
id SERIAL PRIMARY KEY,
|
||||
usuario_id INTEGER NOT NULL REFERENCES usuarios (id) ON DELETE CASCADE,
|
||||
area_id INTEGER NOT NULL REFERENCES areas (id) ON DELETE CASCADE,
|
||||
funcao VARCHAR(80),
|
||||
principal BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ativo BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
|
||||
CONSTRAINT uq_usuario_area UNIQUE (usuario_id, area_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_usuarios_areas_usuario ON usuarios_areas (usuario_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_usuarios_areas_area ON usuarios_areas (area_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_usuarios_areas_ativo ON usuarios_areas (ativo);
|
||||
|
||||
-- Garante que cada usuario tenha no maximo uma area principal.
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_usuario_area_principal
|
||||
ON usuarios_areas (usuario_id)
|
||||
WHERE principal = TRUE;
|
||||
|
||||
|
||||
-- ------------------------------------------------------------
|
||||
-- Dados iniciais: areas padrao para o MVP
|
||||
-- ------------------------------------------------------------
|
||||
INSERT INTO areas (nome, descricao) VALUES
|
||||
('Suporte', 'Atendimento operacional e resolucao de duvidas tecnicas'),
|
||||
('Financeiro', 'Atendimento relacionado a cobrancas, pagamentos e notas'),
|
||||
('Comercial', 'Atendimento de vendas, propostas e relacionamento comercial')
|
||||
ON CONFLICT (nome) DO NOTHING;
|
||||
62
database/migrations/003_demo_access.sql
Normal file
62
database/migrations/003_demo_access.sql
Normal file
@ -0,0 +1,62 @@
|
||||
-- ============================================================
|
||||
-- Migration 003: Usuarios de demonstracao e acessos iniciais
|
||||
-- Perfis: Admin, Supervisor e Agente
|
||||
-- Areas: Suporte, Financeiro e Comercial
|
||||
-- ============================================================
|
||||
|
||||
|
||||
INSERT INTO usuarios (nome, email, ativo) VALUES
|
||||
('Admin Demo', 'admin@sothis.com.br', TRUE),
|
||||
('Supervisor Demo', 'supervisor@sothis.com.br', TRUE),
|
||||
('Atendente Demo', 'atendente@sothis.com.br', TRUE)
|
||||
ON CONFLICT (email) DO UPDATE SET
|
||||
nome = EXCLUDED.nome,
|
||||
ativo = TRUE,
|
||||
updated_at = NOW();
|
||||
|
||||
|
||||
INSERT INTO usuarios_provedores (usuario_id, provedor, provedor_user_id)
|
||||
SELECT u.id, provider.provedor, provider.provedor_user_id
|
||||
FROM usuarios u
|
||||
JOIN (
|
||||
VALUES
|
||||
('admin@sothis.com.br', 'ldap', 'admin'),
|
||||
('admin@sothis.com.br', 'microsoft', 'admin@sothis.com.br'),
|
||||
('supervisor@sothis.com.br', 'ldap', 'supervisor'),
|
||||
('supervisor@sothis.com.br', 'microsoft', 'supervisor@sothis.com.br'),
|
||||
('atendente@sothis.com.br', 'ldap', 'atendente'),
|
||||
('atendente@sothis.com.br', 'microsoft', 'atendente@sothis.com.br')
|
||||
) AS provider(email, provedor, provedor_user_id) ON provider.email = u.email
|
||||
ON CONFLICT (provedor, provedor_user_id)
|
||||
DO UPDATE SET usuario_id = EXCLUDED.usuario_id;
|
||||
|
||||
|
||||
INSERT INTO usuarios_perfis (usuario_id, perfil_id)
|
||||
SELECT u.id, p.id
|
||||
FROM usuarios u
|
||||
JOIN (
|
||||
VALUES
|
||||
('admin@sothis.com.br', 'Admin'),
|
||||
('supervisor@sothis.com.br', 'Supervisor'),
|
||||
('atendente@sothis.com.br', 'Agente')
|
||||
) AS access(email, perfil) ON access.email = u.email
|
||||
JOIN perfis_acesso p ON p.nome = access.perfil
|
||||
ON CONFLICT (usuario_id, perfil_id) DO NOTHING;
|
||||
|
||||
|
||||
INSERT INTO usuarios_areas (usuario_id, area_id, funcao, principal, ativo)
|
||||
SELECT u.id, a.id, access.funcao, TRUE, TRUE
|
||||
FROM usuarios u
|
||||
JOIN (
|
||||
VALUES
|
||||
('admin@sothis.com.br', 'Suporte', 'Administrador'),
|
||||
('supervisor@sothis.com.br', 'Suporte', 'Supervisor'),
|
||||
('atendente@sothis.com.br', 'Suporte', 'Atendente')
|
||||
) AS access(email, area, funcao) ON access.email = u.email
|
||||
JOIN areas a ON a.nome = access.area
|
||||
ON CONFLICT (usuario_id, area_id)
|
||||
DO UPDATE SET
|
||||
funcao = EXCLUDED.funcao,
|
||||
principal = TRUE,
|
||||
ativo = TRUE,
|
||||
updated_at = NOW();
|
||||
8
database/migrations/004_whatsapp.sql
Normal file
8
database/migrations/004_whatsapp.sql
Normal file
@ -0,0 +1,8 @@
|
||||
CREATE TABLE IF NOT EXISTS whatsapp_chat_atribuicoes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
chat_id VARCHAR(255) NOT NULL,
|
||||
user_id SERIAL REFERENCES usuarios(id) ON DELETE CASCADE,
|
||||
area_id SERIAL REFERENCES areas(id) ON DELETE SET NULL,
|
||||
assigned_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(chat_id)
|
||||
);
|
||||
14
database/migrations/005_templates.sql
Normal file
14
database/migrations/005_templates.sql
Normal file
@ -0,0 +1,14 @@
|
||||
CREATE TABLE IF NOT EXISTS whatsapp_templates (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
INSERT INTO whatsapp_templates (name, content) VALUES
|
||||
('aviso_fatura', 'Olá, {nome}. Estamos entrando em contato para lembrá-lo que a sua fatura está programada para {data}.'),
|
||||
('boas_vindas', 'Olá, {nome}! Obrigado por entrar em contato conosco. Como podemos te ajudar hoje?'),
|
||||
('lembrete_consulta', 'Olá, {nome}. Gostaríamos de confirmar o seu agendamento para {data}. Está confirmado?'),
|
||||
('suporte_tecnico', 'Olá, {nome}. Sou o atendente e irei te auxiliar no seu suporte sob protocolo {protocolo}.')
|
||||
ON CONFLICT (name) DO NOTHING;
|
||||
41
database/migrations/006_whatsapp_assignment_queue.sql
Normal file
41
database/migrations/006_whatsapp_assignment_queue.sql
Normal file
@ -0,0 +1,41 @@
|
||||
-- ============================================================
|
||||
-- Migration 006: Fila e controle de atribuicao do WhatsApp
|
||||
-- Tabela: whatsapp_chat_atribuicoes
|
||||
-- ============================================================
|
||||
|
||||
-- A atribuicao passa a representar dois estados principais:
|
||||
-- 1. queued: conversa esta na fila de uma area, sem atendente definido
|
||||
-- 2. assigned: conversa foi assumida ou transferida diretamente para um atendente
|
||||
--
|
||||
-- A janela de atendimento e controlada por expires_at. Ao expirar, a aplicacao
|
||||
-- trata a proxima mensagem como um novo ciclo de conversa.
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ALTER COLUMN user_id DROP NOT NULL,
|
||||
ALTER COLUMN area_id DROP NOT NULL;
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ADD COLUMN IF NOT EXISTS status VARCHAR(40) NOT NULL DEFAULT 'assigned',
|
||||
ADD COLUMN IF NOT EXISTS conversation_started_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
ADD COLUMN IF NOT EXISTS expires_at TIMESTAMP WITH TIME ZONE DEFAULT (CURRENT_TIMESTAMP + INTERVAL '24 hours'),
|
||||
ADD COLUMN IF NOT EXISTS transfer_note TEXT,
|
||||
ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
UPDATE whatsapp_chat_atribuicoes
|
||||
SET
|
||||
status = CASE
|
||||
WHEN user_id IS NULL THEN 'queued'
|
||||
ELSE 'assigned'
|
||||
END,
|
||||
conversation_started_at = COALESCE(conversation_started_at, assigned_at, CURRENT_TIMESTAMP),
|
||||
expires_at = COALESCE(expires_at, assigned_at + INTERVAL '24 hours', CURRENT_TIMESTAMP + INTERVAL '24 hours'),
|
||||
updated_at = COALESCE(updated_at, assigned_at, CURRENT_TIMESTAMP);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_area_status
|
||||
ON whatsapp_chat_atribuicoes (area_id, status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_user_status
|
||||
ON whatsapp_chat_atribuicoes (user_id, status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_expires_at
|
||||
ON whatsapp_chat_atribuicoes (expires_at);
|
||||
12
database/migrations/007_whatsapp_triage_state.sql
Normal file
12
database/migrations/007_whatsapp_triage_state.sql
Normal file
@ -0,0 +1,12 @@
|
||||
-- ============================================================
|
||||
-- Migration 007: Estado de triagem automatica do Omnino
|
||||
-- Tabela: whatsapp_chat_atribuicoes
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ADD COLUMN IF NOT EXISTS routing_attempts INTEGER NOT NULL DEFAULT 0,
|
||||
ADD COLUMN IF NOT EXISTS last_routed_message_id VARCHAR(255),
|
||||
ADD COLUMN IF NOT EXISTS last_bot_sent_at TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_triage
|
||||
ON whatsapp_chat_atribuicoes (status, routing_attempts);
|
||||
14
database/migrations/008_agent_notes.sql
Normal file
14
database/migrations/008_agent_notes.sql
Normal file
@ -0,0 +1,14 @@
|
||||
-- ============================================================
|
||||
-- Migration 008: Notas pessoais dos atendentes
|
||||
-- Tabela: agent_notes
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agent_notes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES usuarios(id) ON DELETE CASCADE,
|
||||
text TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_notes_user_created_at
|
||||
ON agent_notes (user_id, created_at DESC);
|
||||
18
database/migrations/009_customer_contacts.sql
Normal file
18
database/migrations/009_customer_contacts.sql
Normal file
@ -0,0 +1,18 @@
|
||||
-- ============================================================
|
||||
-- Migration 009: Agenda de contatos dos clientes
|
||||
-- Tabela: customer_contacts
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS customer_contacts (
|
||||
chat_id VARCHAR(255) PRIMARY KEY,
|
||||
phone VARCHAR(80) NOT NULL,
|
||||
name VARCHAR(255),
|
||||
company VARCHAR(255),
|
||||
note TEXT,
|
||||
updated_by_user_id INTEGER REFERENCES usuarios(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_customer_contacts_updated_at
|
||||
ON customer_contacts (updated_at DESC);
|
||||
36
database/migrations/010_agenda_contatos.sql
Normal file
36
database/migrations/010_agenda_contatos.sql
Normal file
@ -0,0 +1,36 @@
|
||||
-- ============================================================
|
||||
-- Migration 010: Agenda geral de contatos
|
||||
-- Tabela: agenda_contatos
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agenda_contatos (
|
||||
chat_id VARCHAR(255) PRIMARY KEY,
|
||||
phone VARCHAR(80) NOT NULL,
|
||||
name VARCHAR(255),
|
||||
company VARCHAR(255),
|
||||
note TEXT,
|
||||
updated_by_user_id INTEGER REFERENCES usuarios(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('public.customer_contacts') IS NOT NULL THEN
|
||||
INSERT INTO agenda_contatos (
|
||||
chat_id, phone, name, company, note, updated_by_user_id, created_at, updated_at
|
||||
)
|
||||
SELECT chat_id, phone, name, company, note, updated_by_user_id, created_at, updated_at
|
||||
FROM customer_contacts
|
||||
ON CONFLICT (chat_id) DO UPDATE SET
|
||||
phone = EXCLUDED.phone,
|
||||
name = EXCLUDED.name,
|
||||
company = EXCLUDED.company,
|
||||
note = EXCLUDED.note,
|
||||
updated_by_user_id = EXCLUDED.updated_by_user_id,
|
||||
updated_at = EXCLUDED.updated_at;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agenda_contatos_updated_at
|
||||
ON agenda_contatos (updated_at DESC);
|
||||
19
database/migrations/011_whatsapp_opening_templates.sql
Normal file
19
database/migrations/011_whatsapp_opening_templates.sql
Normal file
@ -0,0 +1,19 @@
|
||||
-- ============================================================
|
||||
-- Migration 011: Templates de abertura ativa do WhatsApp
|
||||
-- Tabela: whatsapp_templates
|
||||
-- ============================================================
|
||||
|
||||
INSERT INTO whatsapp_templates (name, content) VALUES
|
||||
('abertura_atendimento_padrao', 'Ola, {nome}. Tudo bem? Estamos entrando em contato pelo atendimento. Podemos seguir por aqui?'),
|
||||
('abertura_retorno_contato', 'Ola, {nome}. Estamos retornando seu contato para dar continuidade ao seu atendimento.'),
|
||||
('abertura_suporte', 'Ola, {nome}. Aqui e do suporte. Estamos entrando em contato para te ajudar com sua solicitacao.'),
|
||||
('abertura_financeiro', 'Ola, {nome}. Aqui e do financeiro. Estamos entrando em contato para tratar de uma informacao importante sobre seu atendimento.'),
|
||||
('abertura_comercial', 'Ola, {nome}. Aqui e do comercial. Estamos entrando em contato para conversar sobre sua solicitacao.'),
|
||||
('abertura_confirmacao_dados', 'Ola, {nome}. Precisamos confirmar alguns dados para seguir com seu atendimento.'),
|
||||
('abertura_contato_agendado', 'Ola, {nome}. Este contato foi combinado anteriormente e estamos disponiveis para seguir.'),
|
||||
('abertura_pos_atendimento', 'Ola, {nome}. Estamos fazendo um acompanhamento sobre seu atendimento recente.'),
|
||||
('abertura_aviso_importante', 'Ola, {nome}. Temos uma informacao importante para compartilhar com voce.'),
|
||||
('abertura_contato_inicial', 'Ola, {nome}. Vamos iniciar seu atendimento por este canal.')
|
||||
ON CONFLICT (name) DO UPDATE SET
|
||||
content = EXCLUDED.content,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
10
database/migrations/012_whatsapp_awaiting_customer_reply.sql
Normal file
10
database/migrations/012_whatsapp_awaiting_customer_reply.sql
Normal file
@ -0,0 +1,10 @@
|
||||
-- ============================================================
|
||||
-- Migration 012: Bloqueio apos abertura ativa do WhatsApp
|
||||
-- Tabela: whatsapp_chat_atribuicoes
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ADD COLUMN IF NOT EXISTS awaiting_customer_reply BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_awaiting_customer_reply
|
||||
ON whatsapp_chat_atribuicoes (awaiting_customer_reply, status);
|
||||
21
database/migrations/014_whatsapp_template_workflow.sql
Normal file
21
database/migrations/014_whatsapp_template_workflow.sql
Normal file
@ -0,0 +1,21 @@
|
||||
-- ============================================================
|
||||
-- Migration 014: Workflow de aprovação de templates WhatsApp
|
||||
-- Tabela: whatsapp_templates
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE whatsapp_templates
|
||||
ADD COLUMN IF NOT EXISTS area_id INTEGER REFERENCES areas (id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS status VARCHAR(40) NOT NULL DEFAULT 'approved',
|
||||
ADD COLUMN IF NOT EXISTS requested_by_role VARCHAR(40),
|
||||
ADD COLUMN IF NOT EXISTS admin_approved_at TIMESTAMP WITH TIME ZONE,
|
||||
ADD COLUMN IF NOT EXISTS meta_submitted_at TIMESTAMP WITH TIME ZONE,
|
||||
ADD COLUMN IF NOT EXISTS meta_approved_at TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
UPDATE whatsapp_templates
|
||||
SET
|
||||
status = COALESCE(status, 'approved'),
|
||||
meta_approved_at = COALESCE(meta_approved_at, updated_at, created_at, CURRENT_TIMESTAMP)
|
||||
WHERE status = 'approved';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_templates_area ON whatsapp_templates (area_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_templates_status ON whatsapp_templates (status);
|
||||
52
database/migrations/015_agent_presence_pause.sql
Normal file
52
database/migrations/015_agent_presence_pause.sql
Normal file
@ -0,0 +1,52 @@
|
||||
-- ============================================================
|
||||
-- Migration 015: Presenca do agente e reserva de chamados em pausa
|
||||
-- Tabelas:
|
||||
-- agent_presence
|
||||
-- whatsapp_chat_atribuicoes
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agent_presence (
|
||||
user_id INTEGER PRIMARY KEY REFERENCES usuarios(id) ON DELETE CASCADE,
|
||||
status VARCHAR(40) NOT NULL DEFAULT 'offline',
|
||||
paused_at TIMESTAMP WITH TIME ZONE,
|
||||
last_seen_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'chk_agent_presence_status'
|
||||
) THEN
|
||||
ALTER TABLE agent_presence
|
||||
ADD CONSTRAINT chk_agent_presence_status
|
||||
CHECK (status IN ('available', 'paused', 'offline'));
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
INSERT INTO agent_presence (user_id, status, last_seen_at, updated_at)
|
||||
SELECT id, 'available', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
FROM usuarios
|
||||
ON CONFLICT (user_id) DO NOTHING;
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ADD COLUMN IF NOT EXISTS reserved_user_id INTEGER REFERENCES usuarios(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS reserved_at TIMESTAMP WITH TIME ZONE,
|
||||
ADD COLUMN IF NOT EXISTS pause_released_at TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_presence_status
|
||||
ON agent_presence (status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_presence_paused_at
|
||||
ON agent_presence (paused_at)
|
||||
WHERE status = 'paused';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_reserved_user
|
||||
ON whatsapp_chat_atribuicoes (reserved_user_id, status)
|
||||
WHERE reserved_user_id IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_reserved_queue
|
||||
ON whatsapp_chat_atribuicoes (area_id, status, reserved_user_id)
|
||||
WHERE status = 'queued';
|
||||
99
database/migrations/016_hr_decision_tree_keywords.sql
Normal file
99
database/migrations/016_hr_decision_tree_keywords.sql
Normal file
@ -0,0 +1,99 @@
|
||||
-- ============================================================
|
||||
-- Migration 016: Arvore de decisao por especialidade para RH
|
||||
-- Tabelas:
|
||||
-- area_routing_keywords
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS area_routing_keywords (
|
||||
id SERIAL PRIMARY KEY,
|
||||
area_id INTEGER NOT NULL REFERENCES areas(id) ON DELETE CASCADE,
|
||||
keyword VARCHAR(160) NOT NULL,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_area_routing_keyword UNIQUE (area_id, keyword)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_area_routing_keywords_area
|
||||
ON area_routing_keywords (area_id, active);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_area_routing_keywords_keyword
|
||||
ON area_routing_keywords (keyword);
|
||||
|
||||
INSERT INTO areas (nome, descricao) VALUES
|
||||
('Benefícios', 'Duvidas de RH sobre beneficios, convenios, vale transporte e vale refeicao'),
|
||||
('Ponto', 'Ajustes de ponto, banco de horas, atrasos e jornada'),
|
||||
('Holerite', 'Holerite, folha de pagamento, descontos e demonstrativos'),
|
||||
('Férias', 'Ferias, abono, programacao e saldo de descanso'),
|
||||
('Recrutamento', 'Vagas internas, candidatura, entrevista e processo seletivo')
|
||||
ON CONFLICT (nome) DO UPDATE SET
|
||||
descricao = EXCLUDED.descricao,
|
||||
ativo = TRUE,
|
||||
updated_at = NOW();
|
||||
|
||||
INSERT INTO area_routing_keywords (area_id, keyword)
|
||||
SELECT a.id, keyword
|
||||
FROM areas a
|
||||
JOIN (
|
||||
VALUES
|
||||
('Benefícios', 'beneficio'),
|
||||
('Benefícios', 'beneficios'),
|
||||
('Benefícios', 'vale refeicao'),
|
||||
('Benefícios', 'vale alimentacao'),
|
||||
('Benefícios', 'vale transporte'),
|
||||
('Benefícios', 'convenio'),
|
||||
('Benefícios', 'plano de saude'),
|
||||
('Benefícios', 'odonto'),
|
||||
('Ponto', 'ponto'),
|
||||
('Ponto', 'espelho de ponto'),
|
||||
('Ponto', 'banco de horas'),
|
||||
('Ponto', 'atraso'),
|
||||
('Ponto', 'jornada'),
|
||||
('Ponto', 'batida'),
|
||||
('Ponto', 'marcacao'),
|
||||
('Holerite', 'holerite'),
|
||||
('Holerite', 'folha'),
|
||||
('Holerite', 'pagamento'),
|
||||
('Holerite', 'salario'),
|
||||
('Holerite', 'desconto'),
|
||||
('Holerite', 'demonstrativo'),
|
||||
('Férias', 'ferias'),
|
||||
('Férias', 'abono'),
|
||||
('Férias', 'descanso'),
|
||||
('Férias', 'saldo de ferias'),
|
||||
('Férias', 'programar ferias'),
|
||||
('Recrutamento', 'vaga'),
|
||||
('Recrutamento', 'vagas'),
|
||||
('Recrutamento', 'processo seletivo'),
|
||||
('Recrutamento', 'entrevista'),
|
||||
('Recrutamento', 'curriculo'),
|
||||
('Recrutamento', 'candidatura')
|
||||
) AS seed(area_nome, keyword) ON seed.area_nome = a.nome
|
||||
ON CONFLICT (area_id, keyword) DO UPDATE SET
|
||||
active = TRUE,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
|
||||
INSERT INTO whatsapp_templates (name, content, area_id, status, requested_by_role, admin_approved_at, meta_submitted_at, meta_approved_at, updated_at)
|
||||
SELECT template.name, template.content, a.id, 'approved', 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||
FROM areas a
|
||||
JOIN (
|
||||
VALUES
|
||||
('rh_abertura_beneficios', 'Ola, {nome}. Aqui e o Atendimento Sothis RH. Recebemos sua solicitacao sobre beneficios e vamos te apoiar por aqui.'),
|
||||
('rh_abertura_ponto', 'Ola, {nome}. Aqui e o Atendimento Sothis RH. Vamos te ajudar com sua solicitacao sobre ponto, jornada ou banco de horas.'),
|
||||
('rh_abertura_holerite', 'Ola, {nome}. Aqui e o Atendimento Sothis RH. Vamos te ajudar com holerite, folha ou demonstrativo de pagamento.'),
|
||||
('rh_abertura_ferias', 'Ola, {nome}. Aqui e o Atendimento Sothis RH. Vamos te apoiar com sua solicitacao sobre ferias.'),
|
||||
('rh_abertura_recrutamento', 'Ola, {nome}. Aqui e o Atendimento Sothis RH. Vamos te orientar sobre vagas, candidatura ou processo seletivo.')
|
||||
) AS template(name, content) ON template.name LIKE
|
||||
CASE a.nome
|
||||
WHEN 'Benefícios' THEN '%beneficios'
|
||||
WHEN 'Ponto' THEN '%ponto'
|
||||
WHEN 'Holerite' THEN '%holerite'
|
||||
WHEN 'Férias' THEN '%ferias'
|
||||
WHEN 'Recrutamento' THEN '%recrutamento'
|
||||
END
|
||||
ON CONFLICT (name) DO UPDATE SET
|
||||
content = EXCLUDED.content,
|
||||
area_id = EXCLUDED.area_id,
|
||||
status = 'approved',
|
||||
meta_approved_at = CURRENT_TIMESTAMP,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
145
database/migrations/017_configurable_triage_flow.sql
Normal file
145
database/migrations/017_configurable_triage_flow.sql
Normal file
@ -0,0 +1,145 @@
|
||||
-- ============================================================
|
||||
-- Migration 017: Fluxo configuravel de triagem do Agente Virtual Sothis
|
||||
-- Tabelas:
|
||||
-- bot_triage_flows
|
||||
-- bot_triage_audiences
|
||||
-- bot_triage_intents
|
||||
-- whatsapp_chat_atribuicoes
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_triage_flows (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(160) NOT NULL UNIQUE,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
greeting_message TEXT NOT NULL,
|
||||
audience_question TEXT NOT NULL,
|
||||
intent_question_template TEXT NOT NULL,
|
||||
fallback_message TEXT NOT NULL,
|
||||
fallback_area_id INTEGER REFERENCES areas(id) ON DELETE SET NULL,
|
||||
max_attempts INTEGER NOT NULL DEFAULT 2,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_triage_audiences (
|
||||
id SERIAL PRIMARY KEY,
|
||||
flow_id INTEGER NOT NULL REFERENCES bot_triage_flows(id) ON DELETE CASCADE,
|
||||
label VARCHAR(160) NOT NULL,
|
||||
keywords TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 1,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_triage_intents (
|
||||
id SERIAL PRIMARY KEY,
|
||||
audience_id INTEGER NOT NULL REFERENCES bot_triage_audiences(id) ON DELETE CASCADE,
|
||||
label VARCHAR(160) NOT NULL,
|
||||
area_id INTEGER NOT NULL REFERENCES areas(id) ON DELETE CASCADE,
|
||||
keywords TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 1,
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ADD COLUMN IF NOT EXISTS triage_flow_id INTEGER REFERENCES bot_triage_flows(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS triage_audience_id INTEGER REFERENCES bot_triage_audiences(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS triage_step VARCHAR(40);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_triage_flows_active
|
||||
ON bot_triage_flows (active);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_triage_audiences_flow
|
||||
ON bot_triage_audiences (flow_id, active, sort_order);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_triage_intents_audience
|
||||
ON bot_triage_intents (audience_id, active, sort_order);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_triage_flow
|
||||
ON whatsapp_chat_atribuicoes (triage_flow_id, triage_audience_id, triage_step);
|
||||
|
||||
INSERT INTO areas (nome, descricao) VALUES
|
||||
('Documentos RH', 'Documentos para ex-colaboradores, informe de rendimentos e comprovantes'),
|
||||
('Rescisao', 'Rescisao, FGTS, verbas rescisorias e encerramento de contrato')
|
||||
ON CONFLICT (nome) DO UPDATE SET
|
||||
descricao = EXCLUDED.descricao,
|
||||
ativo = TRUE,
|
||||
updated_at = NOW();
|
||||
|
||||
INSERT INTO bot_triage_flows (
|
||||
name,
|
||||
active,
|
||||
greeting_message,
|
||||
audience_question,
|
||||
intent_question_template,
|
||||
fallback_message,
|
||||
fallback_area_id,
|
||||
max_attempts,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
'RH CAOA - Atendimento Sothis',
|
||||
TRUE,
|
||||
'Ola! Sou o Agente Virtual Sothis. Vou te direcionar para o atendimento correto de RH.',
|
||||
'Digite o numero da opcao que melhor descreve voce:',
|
||||
'Perfeito. Agora digite o numero do assunto que voce precisa:',
|
||||
'Nao consegui identificar com seguranca. Vou encaminhar seu atendimento para o suporte de RH.',
|
||||
a.id,
|
||||
2,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM areas a
|
||||
WHERE a.nome = 'Suporte'
|
||||
ON CONFLICT (name) DO UPDATE SET
|
||||
active = TRUE,
|
||||
greeting_message = EXCLUDED.greeting_message,
|
||||
audience_question = EXCLUDED.audience_question,
|
||||
intent_question_template = EXCLUDED.intent_question_template,
|
||||
fallback_message = EXCLUDED.fallback_message,
|
||||
fallback_area_id = EXCLUDED.fallback_area_id,
|
||||
max_attempts = EXCLUDED.max_attempts,
|
||||
updated_at = CURRENT_TIMESTAMP;
|
||||
|
||||
WITH flow AS (
|
||||
SELECT id FROM bot_triage_flows WHERE name = 'RH CAOA - Atendimento Sothis'
|
||||
)
|
||||
INSERT INTO bot_triage_audiences (flow_id, label, keywords, sort_order, active, updated_at)
|
||||
SELECT flow.id, seed.label, seed.keywords, seed.sort_order, TRUE, CURRENT_TIMESTAMP
|
||||
FROM flow
|
||||
JOIN (
|
||||
VALUES
|
||||
('Sou colaborador ativo', 'colaborador, funcionario, matricula, holerite, ferias, ponto, beneficios', 1),
|
||||
('Sou ex-colaborador', 'ex colaborador, ex-colaborador, rescisao, fgts, informe de rendimentos, desligamento', 2),
|
||||
('Sou candidato a uma vaga', 'candidato, vaga, curriculo, processo seletivo, entrevista, candidatura', 3)
|
||||
) AS seed(label, keywords, sort_order) ON TRUE
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
WITH audiences AS (
|
||||
SELECT bta.id, bta.label
|
||||
FROM bot_triage_audiences bta
|
||||
INNER JOIN bot_triage_flows btf ON btf.id = bta.flow_id
|
||||
WHERE btf.name = 'RH CAOA - Atendimento Sothis'
|
||||
),
|
||||
seed AS (
|
||||
SELECT * FROM (
|
||||
VALUES
|
||||
('Sou colaborador ativo', 'Beneficios', 'beneficios, vale refeicao, vale transporte, convenio, plano de saude', 'Benefícios', 1),
|
||||
('Sou colaborador ativo', 'Holerite', 'holerite, folha, salario, pagamento, desconto', 'Holerite', 2),
|
||||
('Sou colaborador ativo', 'Ferias', 'ferias, abono, descanso, saldo de ferias', 'Férias', 3),
|
||||
('Sou colaborador ativo', 'Ponto', 'ponto, espelho de ponto, banco de horas, jornada, batida', 'Ponto', 4),
|
||||
('Sou ex-colaborador', 'Documentos', 'documento, documentos, comprovante, informe de rendimentos', 'Documentos RH', 1),
|
||||
('Sou ex-colaborador', 'FGTS e rescisao', 'fgts, rescisao, verbas rescisorias, desligamento', 'Rescisao', 2),
|
||||
('Sou ex-colaborador', 'Informe de rendimentos', 'informe, rendimentos, imposto de renda, ir', 'Documentos RH', 3),
|
||||
('Sou candidato a uma vaga', 'Status da vaga', 'status, vaga, retorno, resultado', 'Recrutamento', 1),
|
||||
('Sou candidato a uma vaga', 'Reagendar entrevista', 'reagendar, entrevista, agenda, horario', 'Recrutamento', 2),
|
||||
('Sou candidato a uma vaga', 'Nova candidatura', 'nova candidatura, curriculo, candidatar, oportunidade', 'Recrutamento', 3)
|
||||
) AS rows(audience_label, label, keywords, area_nome, sort_order)
|
||||
)
|
||||
INSERT INTO bot_triage_intents (audience_id, label, area_id, keywords, sort_order, active, updated_at)
|
||||
SELECT audiences.id, seed.label, areas.id, seed.keywords, seed.sort_order, TRUE, CURRENT_TIMESTAMP
|
||||
FROM seed
|
||||
INNER JOIN audiences ON audiences.label = seed.audience_label
|
||||
INNER JOIN areas ON areas.nome = seed.area_nome
|
||||
ON CONFLICT DO NOTHING;
|
||||
56
database/migrations/018_triage_resolution_step.sql
Normal file
56
database/migrations/018_triage_resolution_step.sql
Normal file
@ -0,0 +1,56 @@
|
||||
-- ============================================================
|
||||
-- Migration 018: Etapa de resposta antes da escalacao para fila
|
||||
-- Tabelas:
|
||||
-- bot_triage_flows
|
||||
-- bot_triage_intents
|
||||
-- whatsapp_chat_atribuicoes
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE bot_triage_flows
|
||||
ADD COLUMN IF NOT EXISTS resolution_question TEXT NOT NULL DEFAULT 'Essa informacao resolveu sua duvida? Responda 1 para encerrar ou 2 para falar com um especialista.';
|
||||
|
||||
ALTER TABLE bot_triage_intents
|
||||
ADD COLUMN IF NOT EXISTS response_message TEXT,
|
||||
ADD COLUMN IF NOT EXISTS resolution_question TEXT,
|
||||
ADD COLUMN IF NOT EXISTS escalation_message TEXT NOT NULL DEFAULT 'Certo, vou encaminhar seu atendimento para um especialista no assunto.';
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ADD COLUMN IF NOT EXISTS triage_intent_id INTEGER REFERENCES bot_triage_intents(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_triage_intent
|
||||
ON whatsapp_chat_atribuicoes (triage_intent_id, triage_step);
|
||||
|
||||
UPDATE bot_triage_intents
|
||||
SET response_message = CASE label
|
||||
WHEN 'Beneficios' THEN 'Para consultar beneficios, verifique no portal de RH a area Beneficios. La voce encontra vale refeicao, vale transporte, convenio medico, odontologico e regras de elegibilidade.'
|
||||
WHEN 'Holerite' THEN 'Seu holerite fica disponivel no portal de RH na area Folha de pagamento. Normalmente ele pode ser consultado por competencia, com detalhamento de salario, descontos e beneficios.'
|
||||
WHEN 'Ferias' THEN 'Para ferias, consulte saldo e periodos disponiveis no portal de RH. A solicitacao precisa respeitar a politica interna e o fluxo de aprovacao do gestor.'
|
||||
WHEN 'Ponto' THEN 'Para ponto, confira o espelho de ponto e solicite correcao de batida quando necessario. Ajustes de jornada e banco de horas seguem aprovacao do gestor.'
|
||||
WHEN 'Documentos' THEN 'Documentos de ex-colaborador podem ser solicitados pelo canal de RH. Informe o documento desejado e mantenha seus dados de contato atualizados.'
|
||||
WHEN 'FGTS e rescisao' THEN 'Para FGTS e rescisao, o time de RH valida o status do desligamento, prazos legais e documentos pendentes antes de retornar.'
|
||||
WHEN 'Informe de rendimentos' THEN 'O informe de rendimentos e disponibilizado conforme calendario fiscal. Caso nao encontre, o RH pode apoiar com a segunda via.'
|
||||
WHEN 'Status da vaga' THEN 'Para status de vaga, acompanhe o processo seletivo pelo canal informado na candidatura. O RH pode consultar a etapa atual quando houver identificacao do candidato.'
|
||||
WHEN 'Reagendar entrevista' THEN 'Para reagendar entrevista, informe nome completo, vaga e melhor disponibilidade. O time de recrutamento avalia a agenda.'
|
||||
WHEN 'Nova candidatura' THEN 'Para nova candidatura, acesse o portal de vagas e mantenha seu curriculo atualizado. O RH pode orientar sobre oportunidades abertas.'
|
||||
ELSE COALESCE(response_message, 'Tenho uma orientacao inicial para esse assunto. Se ainda precisar de ajuda, posso encaminhar para um especialista.')
|
||||
END
|
||||
WHERE response_message IS NULL;
|
||||
|
||||
UPDATE bot_triage_intents
|
||||
SET resolution_question = 'Isso resolveu sua duvida? Responda 1 para encerrar ou 2 para falar com um especialista de RH.'
|
||||
WHERE resolution_question IS NULL;
|
||||
|
||||
UPDATE bot_triage_intents
|
||||
SET escalation_message = CASE label
|
||||
WHEN 'Beneficios' THEN 'Certo, vou te encaminhar para um especialista em beneficios.'
|
||||
WHEN 'Holerite' THEN 'Certo, vou te encaminhar para um especialista em holerite e folha.'
|
||||
WHEN 'Ferias' THEN 'Certo, vou te encaminhar para um especialista em ferias.'
|
||||
WHEN 'Ponto' THEN 'Certo, vou te encaminhar para um especialista em ponto.'
|
||||
WHEN 'Documentos' THEN 'Certo, vou te encaminhar para um especialista em documentos de RH.'
|
||||
WHEN 'FGTS e rescisao' THEN 'Certo, vou te encaminhar para um especialista em rescisao.'
|
||||
WHEN 'Informe de rendimentos' THEN 'Certo, vou te encaminhar para um especialista em documentos de RH.'
|
||||
WHEN 'Status da vaga' THEN 'Certo, vou te encaminhar para o time de recrutamento.'
|
||||
WHEN 'Reagendar entrevista' THEN 'Certo, vou te encaminhar para o time de recrutamento.'
|
||||
WHEN 'Nova candidatura' THEN 'Certo, vou te encaminhar para o time de recrutamento.'
|
||||
ELSE escalation_message
|
||||
END;
|
||||
127
database/migrations/019_bot_flow_builder.sql
Normal file
127
database/migrations/019_bot_flow_builder.sql
Normal file
@ -0,0 +1,127 @@
|
||||
-- ============================================================
|
||||
-- Migration 019: Flow Builder visual do bot
|
||||
-- Tabelas:
|
||||
-- bot_flow_versions
|
||||
-- bot_flow_nodes
|
||||
-- whatsapp_chat_atribuicoes
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_flow_versions (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(160) NOT NULL DEFAULT 'Fluxo RH Sothis',
|
||||
status VARCHAR(30) NOT NULL DEFAULT 'draft',
|
||||
version_number INTEGER NOT NULL DEFAULT 0,
|
||||
root_node_id INTEGER,
|
||||
published_at TIMESTAMP WITH TIME ZONE,
|
||||
snapshot JSONB,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS bot_flow_nodes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
version_id INTEGER NOT NULL REFERENCES bot_flow_versions(id) ON DELETE CASCADE,
|
||||
parent_id INTEGER REFERENCES bot_flow_nodes(id) ON DELETE CASCADE,
|
||||
node_type VARCHAR(30) NOT NULL CHECK (node_type IN ('greeting', 'question', 'agent', 'close')),
|
||||
title VARCHAR(160) NOT NULL,
|
||||
message_text TEXT,
|
||||
keywords TEXT,
|
||||
fallback_message TEXT,
|
||||
fallback_attempts INTEGER NOT NULL DEFAULT 2,
|
||||
fallback_area_id INTEGER REFERENCES areas(id) ON DELETE SET NULL,
|
||||
area_id INTEGER REFERENCES areas(id) ON DELETE SET NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 1,
|
||||
position_x INTEGER NOT NULL DEFAULT 0,
|
||||
position_y INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'fk_bot_flow_versions_root'
|
||||
) THEN
|
||||
ALTER TABLE bot_flow_versions
|
||||
ADD CONSTRAINT fk_bot_flow_versions_root
|
||||
FOREIGN KEY (root_node_id) REFERENCES bot_flow_nodes(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE whatsapp_chat_atribuicoes
|
||||
ADD COLUMN IF NOT EXISTS triage_builder_version_id INTEGER REFERENCES bot_flow_versions(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS triage_builder_node_id INTEGER REFERENCES bot_flow_nodes(id) ON DELETE SET NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_flow_versions_status
|
||||
ON bot_flow_versions (status, published_at DESC, id DESC);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_bot_flow_nodes_version_parent
|
||||
ON bot_flow_nodes (version_id, parent_id, sort_order, id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_atribuicoes_builder_node
|
||||
ON whatsapp_chat_atribuicoes (triage_builder_version_id, triage_builder_node_id);
|
||||
|
||||
WITH inserted_version AS (
|
||||
INSERT INTO bot_flow_versions (name, status, version_number, updated_at)
|
||||
SELECT 'Fluxo RH Sothis', 'draft', 0, CURRENT_TIMESTAMP
|
||||
WHERE NOT EXISTS (SELECT 1 FROM bot_flow_versions WHERE status = 'draft')
|
||||
RETURNING id
|
||||
),
|
||||
draft_version AS (
|
||||
SELECT id FROM inserted_version
|
||||
UNION ALL
|
||||
SELECT id
|
||||
FROM (
|
||||
SELECT id
|
||||
FROM bot_flow_versions
|
||||
WHERE status = 'draft'
|
||||
AND NOT EXISTS (SELECT 1 FROM inserted_version)
|
||||
ORDER BY id ASC
|
||||
LIMIT 1
|
||||
) existing_draft
|
||||
),
|
||||
inserted_root AS (
|
||||
INSERT INTO bot_flow_nodes (
|
||||
version_id,
|
||||
parent_id,
|
||||
node_type,
|
||||
title,
|
||||
message_text,
|
||||
keywords,
|
||||
fallback_message,
|
||||
fallback_attempts,
|
||||
sort_order,
|
||||
position_x,
|
||||
position_y,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
draft_version.id,
|
||||
NULL,
|
||||
'greeting',
|
||||
'Saudacao inicial',
|
||||
'Ola! Sou o Agente Virtual Sothis. Vou te direcionar para o atendimento correto de RH. Digite o numero da opcao que melhor descreve voce:\n\n1 - Sou colaborador ativo\n2 - Sou ex-colaborador\n3 - Sou candidato a uma vaga',
|
||||
NULL,
|
||||
'Nao consegui identificar seu perfil. Digite 1 para colaborador ativo, 2 para ex-colaborador ou 3 para candidato.',
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
CURRENT_TIMESTAMP
|
||||
FROM draft_version
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM bot_flow_nodes node
|
||||
WHERE node.version_id = draft_version.id
|
||||
AND node.node_type = 'greeting'
|
||||
)
|
||||
RETURNING id, version_id
|
||||
)
|
||||
UPDATE bot_flow_versions version
|
||||
SET root_node_id = inserted_root.id,
|
||||
updated_at = CURRENT_TIMESTAMP
|
||||
FROM inserted_root
|
||||
WHERE version.id = inserted_root.version_id
|
||||
AND version.root_node_id IS NULL;
|
||||
@ -0,0 +1,12 @@
|
||||
-- ============================================================
|
||||
-- Migration 020: No terminal de encerramento pelo bot
|
||||
-- Tabelas:
|
||||
-- bot_flow_nodes
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE bot_flow_nodes
|
||||
DROP CONSTRAINT IF EXISTS bot_flow_nodes_node_type_check;
|
||||
|
||||
ALTER TABLE bot_flow_nodes
|
||||
ADD CONSTRAINT bot_flow_nodes_node_type_check
|
||||
CHECK (node_type IN ('greeting', 'question', 'agent', 'close'));
|
||||
41
database/migrations/021_admin_audit_ai_contents.sql
Normal file
41
database/migrations/021_admin_audit_ai_contents.sql
Normal file
@ -0,0 +1,41 @@
|
||||
-- ============================================================
|
||||
-- Migration 021: Auditoria e conteúdos da IA
|
||||
-- Tabelas:
|
||||
-- admin_audit_logs
|
||||
-- ai_knowledge_contents
|
||||
-- ============================================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS admin_audit_logs (
|
||||
id SERIAL PRIMARY KEY,
|
||||
actor_user_id INTEGER REFERENCES usuarios(id) ON DELETE SET NULL,
|
||||
actor_name VARCHAR(180),
|
||||
action VARCHAR(120) NOT NULL,
|
||||
target_type VARCHAR(80),
|
||||
target_id VARCHAR(120),
|
||||
details TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_admin_audit_logs_created_at
|
||||
ON admin_audit_logs (created_at DESC, id DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ai_knowledge_contents (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title VARCHAR(220) NOT NULL,
|
||||
area_id INTEGER REFERENCES areas(id) ON DELETE SET NULL,
|
||||
filename VARCHAR(260),
|
||||
mimetype VARCHAR(160),
|
||||
file_size INTEGER,
|
||||
content_base64 TEXT,
|
||||
status VARCHAR(40) NOT NULL DEFAULT 'available',
|
||||
notes TEXT,
|
||||
created_by_user_id INTEGER REFERENCES usuarios(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_knowledge_contents_area
|
||||
ON ai_knowledge_contents (area_id, status);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_ai_knowledge_contents_created_at
|
||||
ON ai_knowledge_contents (created_at DESC, id DESC);
|
||||
14
database/migrations/022_whatsapp_template_category.sql
Normal file
14
database/migrations/022_whatsapp_template_category.sql
Normal file
@ -0,0 +1,14 @@
|
||||
-- ============================================================
|
||||
-- Migration 022: Categoria de templates WhatsApp
|
||||
-- Tabela: whatsapp_templates
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE whatsapp_templates
|
||||
ADD COLUMN IF NOT EXISTS category VARCHAR(40) NOT NULL DEFAULT 'UTILITY';
|
||||
|
||||
UPDATE whatsapp_templates
|
||||
SET category = 'UTILITY'
|
||||
WHERE category IS NULL OR category = '';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_whatsapp_templates_category
|
||||
ON whatsapp_templates (category);
|
||||
11
database/migrations/023_agenda_contact_channels.sql
Normal file
11
database/migrations/023_agenda_contact_channels.sql
Normal file
@ -0,0 +1,11 @@
|
||||
-- ============================================================
|
||||
-- Migration 023: Canais adicionais da agenda de contatos
|
||||
-- Tabela: agenda_contatos
|
||||
-- ============================================================
|
||||
|
||||
ALTER TABLE agenda_contatos
|
||||
ADD COLUMN IF NOT EXISTS call_sms_phone VARCHAR(80),
|
||||
ADD COLUMN IF NOT EXISTS email VARCHAR(255);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agenda_contatos_email
|
||||
ON agenda_contatos (email);
|
||||
1
deploy-dev.bat
Normal file
1
deploy-dev.bat
Normal file
@ -0,0 +1 @@
|
||||
ssh desenvolvimento@10.0.120.75 -p 60000 "/home/desenvolvimento/scripts/deploy-omnichannel-dev.sh"
|
||||
@ -4,4 +4,13 @@ services:
|
||||
context: ./frontend
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "4000:3000"
|
||||
|
||||
backend:
|
||||
build:
|
||||
context: ./backend
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "4001:3001"
|
||||
env_file:
|
||||
- .env.development
|
||||
|
||||
1
frontend
Submodule
1
frontend
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7dc07c2a806d6352d2a84c333f09974d997918b0
|
||||
Loading…
Reference in New Issue
Block a user