Compare commits
9 Commits
abdd176cc0
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| 836298889f | |||
| cd48e8c458 | |||
| 44a84301a3 | |||
| bc03f5dc1d | |||
| 0b2748a579 | |||
| 3113f9afbd | |||
| 46767b5fe2 | |||
| d906d9e715 | |||
| 6e080720cd |
@@ -1,9 +1,15 @@
|
|||||||
-- ============================================================
|
-- ============================================================
|
||||||
-- CHATC2 - MIGRAÇÕES DE BANCO DE DADOS (Firebird)
|
-- CHATC2 - MIGRAÇÕES DE BANCO DE DADOS (Firebird)
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|
||||||
-- Execute este arquivo no seu banco .FDB usando:
|
-- Execute este arquivo no seu banco .FDB usando:
|
||||||
-- isql -user SYSDBA -pass masterkey localhost:SEU_BANCO.FDB -i migracoes.sql
|
-- IBExpert: Tools > Execute SQL Script > External terminator: ^
|
||||||
-- Ou cole no IBExpert / FlameRobin e execute statement por statement.
|
-- isql (linha de comando):
|
||||||
|
-- isql -user SYSDBA -pass masterkey localhost:SEU.FDB
|
||||||
|
-- SET TERM ^ ;
|
||||||
|
-- INPUT migracoes.sql;
|
||||||
|
-- SET TERM ; ^
|
||||||
|
-- EXIT;
|
||||||
--
|
--
|
||||||
-- Cada bloco é idempotente: se a tabela/coluna já existe, o erro é ignorado.
|
-- Cada bloco é idempotente: se a tabela/coluna já existe, o erro é ignorado.
|
||||||
-- ============================================================
|
-- ============================================================
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
-- ============================================================
|
||||||
|
-- CHATC2 - SCRIPT DE CRIAÇÃO DO BANCO (IBExpert)
|
||||||
|
-- ============================================================
|
||||||
|
-- Execute cada bloco separadamente no IBExpert SQL Editor.
|
||||||
|
-- Se algum comando falhar com "already exists", ignore e continue.
|
||||||
|
-- ============================================================
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- TABELA DE CONTROLE DE MIGRAÇÕES
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_CONTROLE_MIGRACOES (
|
||||||
|
MIG_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
MIG_DESCRICAO VARCHAR(200),
|
||||||
|
MIG_DATA_EXECUCAO TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MIG_STATUS CHAR(1) DEFAULT 'A'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 1. USU_TIPO_CHAT em USUARIOS
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE USUARIOS ADD USU_TIPO_CHAT CHAR(1) DEFAULT 'A';
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 2. Tabela CHATC2_INSTANCIAS
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_INSTANCIAS (
|
||||||
|
INS_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
INS_EMPRESA_ID INTEGER NOT NULL,
|
||||||
|
INS_NOME VARCHAR(100),
|
||||||
|
INS_URL VARCHAR(500),
|
||||||
|
INS_API_KEY VARCHAR(500),
|
||||||
|
INS_INSTANCE_NAME VARCHAR(100),
|
||||||
|
INS_STATUS CHAR(1) DEFAULT 'D',
|
||||||
|
INS_QR_CODE BLOB SUB_TYPE 0,
|
||||||
|
INS_DT_CONEXAO TIMESTAMP,
|
||||||
|
INS_DT_CADASTRO TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INS_SITUACAO CHAR(1) DEFAULT 'A'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 3. Tabela CHATC2_EQUIPES
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_EQUIPES (
|
||||||
|
EQU_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
EQU_EMPRESA_ID INTEGER NOT NULL,
|
||||||
|
EQU_NOME VARCHAR(100),
|
||||||
|
EQU_ORDEM INTEGER DEFAULT 0,
|
||||||
|
EQU_SITUACAO CHAR(1) DEFAULT 'A'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 4. Tabela CHATC2_USU_EQUIPES
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_USU_EQUIPES (
|
||||||
|
EQU_EQUIPE_ID INTEGER NOT NULL,
|
||||||
|
EQU_USUARIO_ID INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (EQU_EQUIPE_ID, EQU_USUARIO_ID)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 5. Tabela CHATC2_ETIQUETAS
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_ETIQUETAS (
|
||||||
|
ETI_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
ETI_EMPRESA_ID INTEGER NOT NULL,
|
||||||
|
ETI_NOME VARCHAR(50),
|
||||||
|
ETI_COR VARCHAR(7) DEFAULT '#667eea',
|
||||||
|
ETI_SITUACAO CHAR(1) DEFAULT 'A'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 6. Tabela CHATC2_CONVERSAS
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_CONVERSAS (
|
||||||
|
CON_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
CON_EMPRESA_ID INTEGER NOT NULL,
|
||||||
|
CON_INSTANCIA_ID INTEGER,
|
||||||
|
CON_CLIENTE_ID INTEGER,
|
||||||
|
CON_NUMERO VARCHAR(20),
|
||||||
|
CON_NOME_CONTATO VARCHAR(100),
|
||||||
|
CON_STATUS CHAR(1) DEFAULT 'E',
|
||||||
|
CON_USUARIO_ID INTEGER,
|
||||||
|
CON_EQUIPE_ID INTEGER,
|
||||||
|
CON_DT_INICIO TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CON_DT_FINAL TIMESTAMP,
|
||||||
|
CON_DT_ULTIMA_MSG TIMESTAMP,
|
||||||
|
CON_SAUDACAO_ENVIADA CHAR(1) DEFAULT 'N',
|
||||||
|
CON_CSAT_ENVIADO CHAR(1) DEFAULT 'N',
|
||||||
|
CON_PRIMEIRA_MSG TIMESTAMP,
|
||||||
|
CON_ORIGEM VARCHAR(20) DEFAULT 'whatsapp',
|
||||||
|
CON_SITUACAO CHAR(1) DEFAULT 'A'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 7. Tabela CHATC2_CONVERSAS_MENSAGENS
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_CONVERSAS_MENSAGENS (
|
||||||
|
CME_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
CME_CONVERSA_ID INTEGER NOT NULL,
|
||||||
|
CME_REMETENTE CHAR(1) DEFAULT 'C',
|
||||||
|
CME_USUARIO_ID INTEGER,
|
||||||
|
CME_MENSAGEM BLOB SUB_TYPE 1,
|
||||||
|
CME_TEXTO VARCHAR(4000),
|
||||||
|
CME_TIPO VARCHAR(20) DEFAULT 'text',
|
||||||
|
CME_DT_ENVIO TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
CME_PRIVADA CHAR(1) DEFAULT 'N',
|
||||||
|
CME_LIDA CHAR(1) DEFAULT 'N',
|
||||||
|
CME_MIDIA_ID INTEGER,
|
||||||
|
CME_SITUACAO CHAR(1) DEFAULT 'A'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 8. Tabela CHATC2_MENSAGENS_ATENDIMENTOS
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_MENSAGENS_ATENDIMENTOS (
|
||||||
|
MAT_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
MAT_CONVERSA_ID INTEGER,
|
||||||
|
MAT_MENSAGEM_ID INTEGER,
|
||||||
|
MAT_NOME_ARQUIVO VARCHAR(255),
|
||||||
|
MAT_TIPO_ARQUIVO VARCHAR(50),
|
||||||
|
MAT_ARQUIVO BLOB SUB_TYPE 0,
|
||||||
|
MAT_DT_CADASTRO TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
MAT_SITUACAO CHAR(1) DEFAULT 'A'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 9. Tabela CHATC2_CONVERSAS_ETIQUETAS
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_CONVERSAS_ETIQUETAS (
|
||||||
|
CET_CONVERSA_ID INTEGER NOT NULL,
|
||||||
|
CET_ETIQUETA_ID INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY (CET_CONVERSA_ID, CET_ETIQUETA_ID)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 10. Tabela CHATC2_CSAT_AVALIACOES
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_CSAT_AVALIACOES (
|
||||||
|
CSA_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
CSA_CONVERSA_ID INTEGER,
|
||||||
|
CSA_EMPRESA_ID INTEGER,
|
||||||
|
CSA_CLIENTE_ID INTEGER,
|
||||||
|
CSA_NOTA INTEGER,
|
||||||
|
CSA_COMENTARIO VARCHAR(500),
|
||||||
|
CSA_DT_AVALIACAO TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 11. Tabela CHATC2_CONFIGURACOES_EMPRESA
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_CONFIGURACOES_EMPRESA (
|
||||||
|
CFE_EMPRESA_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
CFE_INSTANCIA_PADRAO_ID INTEGER,
|
||||||
|
CFE_FOTO_CELULAR CHAR(1) DEFAULT 'N',
|
||||||
|
CFE_SAUDACAO_ATIVA CHAR(1) DEFAULT 'S',
|
||||||
|
CFE_SAUDACAO_MENSAGEM VARCHAR(500),
|
||||||
|
CFE_CSAT_ATIVO CHAR(1) DEFAULT 'N',
|
||||||
|
CFE_CSAT_MENSAGEM VARCHAR(500) DEFAULT 'Avalie seu atendimento de 1 a 5 estrelas'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 12. EQU_ORDEM em CHATC2_EQUIPES (já incluso no CREATE TABLE acima)
|
||||||
|
-- ============================================================
|
||||||
|
-- Já existe. Pule este comando.
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 13. Campos de triagem
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_ENVIAR_NOME_USUARIO CHAR(1) DEFAULT 'N';
|
||||||
|
ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_TRIAGEM_ATIVA CHAR(1) DEFAULT 'N';
|
||||||
|
ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_TRIAGEM_MSG_WELCOME VARCHAR(500);
|
||||||
|
ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_TRIAGEM_MSG_AFTER VARCHAR(500);
|
||||||
|
ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_TRIAGEM_BOLETO_NUMERO VARCHAR(10) DEFAULT '0';
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 14. CON_MENU_ESTADO em conversas
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE CHATC2_CONVERSAS ADD CON_MENU_ESTADO VARCHAR(100);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 15. CON_ETIQUETAS_DESC em conversas
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE CHATC2_CONVERSAS ADD CON_ETIQUETAS_DESC VARCHAR(500);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 16. Tabela CHATC2_MENUS_EMPRESA
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_MENUS_EMPRESA (
|
||||||
|
MNE_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
MNE_EMPRESA_ID INTEGER,
|
||||||
|
MNE_EQUIPE_ID INTEGER,
|
||||||
|
MNE_MENU_PAI_ID INTEGER,
|
||||||
|
MNE_ORDEM INTEGER DEFAULT 0,
|
||||||
|
MNE_TITULO VARCHAR(100),
|
||||||
|
MNE_TIPO CHAR(1) DEFAULT 'M',
|
||||||
|
MNE_TEXTO BLOB SUB_TYPE TEXT,
|
||||||
|
MNE_ACAO_ROTA VARCHAR(100),
|
||||||
|
MNE_ACAO_METODO VARCHAR(10),
|
||||||
|
MNE_ACAO_PROMPT VARCHAR(300),
|
||||||
|
MNE_SITUACAO CHAR(1) DEFAULT 'A'
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 17. Novos campos em conversas
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE CHATC2_CONVERSAS ADD CON_USUARIO_NOME VARCHAR(100);
|
||||||
|
ALTER TABLE CHATC2_CONVERSAS ADD CON_EQUIPE_NOME VARCHAR(100);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 18. Etiquetas nos menus
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE CHATC2_MENUS_EMPRESA ADD MNE_ETIQUETA_IDS VARCHAR(200);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 19. Transcrição de áudio
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE CHATC2_MENSAGENS_ATENDIMENTOS ADD MAT_TRANSCRICAO BLOB SUB_TYPE TEXT;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 20. Generators (sequences)
|
||||||
|
-- ============================================================
|
||||||
|
CREATE GENERATOR GEN_CHATC2_CONVERSAS;
|
||||||
|
CREATE GENERATOR GEN_CONVERSAS_MENSAGENS;
|
||||||
|
CREATE GENERATOR GEN_MENSAGENS_ATENDIMENTOS;
|
||||||
|
CREATE GENERATOR GEN_CHATC2_EQUIPES;
|
||||||
|
CREATE GENERATOR GEN_CHATC2_ETIQUETAS;
|
||||||
|
CREATE GENERATOR GEN_CHATC2_INSTANCIAS;
|
||||||
|
CREATE GENERATOR GEN_CHATC2_MENUS_EMPRESA;
|
||||||
|
CREATE GENERATOR GEN_CHATC2_CSAT_AVALIACOES;
|
||||||
|
CREATE GENERATOR GEN_CHATC2_MOTIVOS_ATENDIMENTO;
|
||||||
|
CREATE GENERATOR GEN_CHATC2_AGENDAMENTOS;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 21. Índices
|
||||||
|
-- ============================================================
|
||||||
|
CREATE INDEX IDX_CLIENTES_CELULAR ON CLIENTES (CLI_CELULAR);
|
||||||
|
CREATE INDEX IDX_CLIENTES_FONE1 ON CLIENTES (CLI_FONE1);
|
||||||
|
CREATE INDEX IDX_CLIENTES_FONE2 ON CLIENTES (CLI_FONE2);
|
||||||
|
CREATE INDEX IDX_CLIENTES_EMPRESA ON CLIENTES (CLI_EMPRESA_ID);
|
||||||
|
CREATE INDEX IDX_CONV_NUMERO_EMPRESA ON CHATC2_CONVERSAS (CON_NUMERO, CON_EMPRESA_ID);
|
||||||
|
CREATE INDEX IDX_CONV_STATUS ON CHATC2_CONVERSAS (CON_STATUS);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 22. Mensagem e etiqueta por equipe
|
||||||
|
-- ============================================================
|
||||||
|
ALTER TABLE CHATC2_EQUIPES ADD EQU_MENSAGEM VARCHAR(500);
|
||||||
|
ALTER TABLE CHATC2_EQUIPES ADD EQU_ETIQUETA_ID INTEGER;
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 23. Tabela de agendamentos
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE CHATC2_CONVERSAS_AGENDAMENTOS (
|
||||||
|
CAG_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
CAG_CONVERSA_ID INTEGER NOT NULL,
|
||||||
|
CAG_EMPRESA_ID INTEGER,
|
||||||
|
CAG_DATA DATE NOT NULL,
|
||||||
|
CAG_HORA TIME,
|
||||||
|
CAG_NOTA VARCHAR(300),
|
||||||
|
CAG_STATUS CHAR(1) DEFAULT 'P',
|
||||||
|
CAG_USUARIO_ID INTEGER,
|
||||||
|
CAG_DT_CRIACAO TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- 24. Fluxo de resolução
|
||||||
|
-- ============================================================
|
||||||
|
CREATE TABLE "CHATC2_MOTIVOS_ATENDIMENTO" (
|
||||||
|
MOT_CODIGO_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
MOT_EMPRESA_ID INTEGER,
|
||||||
|
MOT_DESCRICAO VARCHAR(150),
|
||||||
|
MOT_SITUACAO CHAR(1) DEFAULT 'A'
|
||||||
|
);
|
||||||
|
ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_MOTIVO_VISUALIZAR CHAR(1) DEFAULT 'N';
|
||||||
|
ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_MOTIVO_OBRIGATORIO CHAR(1) DEFAULT 'N';
|
||||||
|
ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_RESOLUCAO_VISUALIZAR CHAR(1) DEFAULT 'N';
|
||||||
|
ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_RESOLUCAO_OBRIGATORIO CHAR(1) DEFAULT 'N';
|
||||||
|
ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_ENVIAR_BOLETO CHAR(1) DEFAULT 'N';
|
||||||
|
ALTER TABLE CHATC2_CONFIGURACOES_EMPRESA ADD CFE_RESOLUCAO_SALVAR_MSC CHAR(1) DEFAULT 'N';
|
||||||
|
ALTER TABLE CHATC2_CONVERSAS ADD CON_MOTIVO_ID INTEGER;
|
||||||
|
ALTER TABLE CHATC2_CONVERSAS ADD CON_RESOLUCAO VARCHAR(4000);
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- REGISTRA MIGRAÇÕES COMO EXECUTADAS
|
||||||
|
-- ============================================================
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (1, 'USU_TIPO_CHAT');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (2, 'Tabela CHATC2_INSTANCIAS');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (3, 'Tabela CHATC2_EQUIPES');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (4, 'Tabela CHATC2_USU_EQUIPES');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (5, 'Tabela CHATC2_ETIQUETAS');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (6, 'Tabela CHATC2_CONVERSAS');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (7, 'Tabela CHATC2_CONVERSAS_MENSAGENS');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (8, 'Tabela CHATC2_MENSAGENS_ATENDIMENTOS');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (9, 'Tabela CHATC2_CONVERSAS_ETIQUETAS');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (10, 'Tabela CHATC2_CSAT_AVALIACOES');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (11, 'Tabela CHATC2_CONFIGURACOES_EMPRESA');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (12, 'EQU_ORDEM');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (13, 'Campos de triagem');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (14, 'CON_MENU_ESTADO');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (15, 'CON_ETIQUETAS_DESC');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (16, 'Tabela CHATC2_MENUS_EMPRESA');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (17, 'CON_USUARIO_NOME e CON_EQUIPE_NOME');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (18, 'MNE_ETIQUETA_IDS');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (19, 'MAT_TRANSCRICAO');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (20, 'Generators');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (21, 'Índices');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (22, 'Mensagem/etiqueta por equipe');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (23, 'Agendamentos');
|
||||||
|
INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (24, 'Fluxo de resolução');
|
||||||
@@ -201,8 +201,14 @@ async function executarMigracoes(alias, silencioso) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (erros === 0) {
|
if (erros === 0) {
|
||||||
|
// Executa callback after() se existir (ex: inicializar generators)
|
||||||
if (typeof mig.after === 'function') {
|
if (typeof mig.after === 'function') {
|
||||||
try { await mig.after(alias); } catch (_) {}
|
try {
|
||||||
|
await mig.after(alias);
|
||||||
|
} catch (afterErr) {
|
||||||
|
if (!silencioso) console.log(` ⚠️ after() falhou: ${afterErr.message.substring(0, 120)}`);
|
||||||
|
erros++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await registrarMigracao(alias, mig.id, mig.descricao);
|
await registrarMigracao(alias, mig.id, mig.descricao);
|
||||||
executadas++;
|
executadas++;
|
||||||
|
|||||||
@@ -50,6 +50,30 @@ const databases = {
|
|||||||
pageSize: 4096,
|
pageSize: 4096,
|
||||||
wireCrypt: 1,
|
wireCrypt: 1,
|
||||||
},
|
},
|
||||||
|
novo: {
|
||||||
|
driver: 'firebird',
|
||||||
|
host: '177.70.23.53',
|
||||||
|
port: 3050,
|
||||||
|
database: 'sigef_web_novo',
|
||||||
|
user: process.env.DB_USER || 'SYSDBA',
|
||||||
|
password: process.env.DB_PASSWORD || 'masterkey',
|
||||||
|
encoding: process.env.DB_ENCODING || 'UTF-8',
|
||||||
|
lowercase_keys: false,
|
||||||
|
pageSize: 4096,
|
||||||
|
wireCrypt: 1,
|
||||||
|
},
|
||||||
|
paz_suprema: {
|
||||||
|
driver: 'firebird',
|
||||||
|
host: '177.70.23.53',
|
||||||
|
port: 3050,
|
||||||
|
database: 'sigef_web_paz_suprema',
|
||||||
|
user: process.env.DB_USER || 'SYSDBA',
|
||||||
|
password: process.env.DB_PASSWORD || 'masterkey',
|
||||||
|
encoding: process.env.DB_ENCODING || 'UTF-8',
|
||||||
|
lowercase_keys: false,
|
||||||
|
pageSize: 4096,
|
||||||
|
wireCrypt: 1,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function salvarCustomDatabases() {
|
function salvarCustomDatabases() {
|
||||||
|
|||||||
@@ -736,11 +736,12 @@ class ChatController {
|
|||||||
// Salva resolução na tabela MENSAGENS_CLIENTES como aviso (se configurado)
|
// Salva resolução na tabela MENSAGENS_CLIENTES como aviso (se configurado)
|
||||||
if (isS(fl.CFE_RESOLUCAO_SALVAR_MSC) && resolucao && c.CON_CLIENTE_ID) {
|
if (isS(fl.CFE_RESOLUCAO_SALVAR_MSC) && resolucao && c.CON_CLIENTE_ID) {
|
||||||
try {
|
try {
|
||||||
|
const mscId = await db.nextId(alias, 'GEN_MENSAGEM_CLIENTES_ID');
|
||||||
await db.execute(alias,
|
await db.execute(alias,
|
||||||
`INSERT INTO MENSAGENS_CLIENTES (MSC_CLIENTE_ID, MSC_USUARIO_ID, MSC_DT_MENSAGEM, MSC_AVISO, MSC_MENSAGEM)
|
`INSERT INTO MENSAGEM_CLIENTES (MSC_CODIGO_ID, MSC_CLIENTE_ID, MSC_USUARIO_ID, MSC_DT_MENSAGEM, MSC_AVISO, MSC_MENSAGEM)
|
||||||
VALUES (?, ?, CURRENT_TIMESTAMP, 'S', ?)`,
|
VALUES (?, ?, ?, CURRENT_TIMESTAMP, 'S', ?)`,
|
||||||
[c.CON_CLIENTE_ID, c.CON_USUARIO_ID || null, resolucao]);
|
[mscId, c.CON_CLIENTE_ID, c.CON_USUARIO_ID || null, resolucao]);
|
||||||
console.log('[Resolucao] Aviso salvo em MENSAGENS_CLIENTES para cliente', c.CON_CLIENTE_ID);
|
console.log('[Resolucao] Aviso salvo em MENSAGEM_CLIENTES para cliente', c.CON_CLIENTE_ID);
|
||||||
} catch (mscErr) {
|
} catch (mscErr) {
|
||||||
console.error('[Resolucao] Erro ao salvar em MENSAGENS_CLIENTES:', mscErr.message);
|
console.error('[Resolucao] Erro ao salvar em MENSAGENS_CLIENTES:', mscErr.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,31 @@ const https = require('https');
|
|||||||
const http = require('http');
|
const http = require('http');
|
||||||
const { listAliases } = db;
|
const { listAliases } = db;
|
||||||
const TriageController = require('./triageController');
|
const TriageController = require('./triageController');
|
||||||
|
const mediaService = require('../services/mediaService');
|
||||||
|
|
||||||
|
// ponytail: anti-loop — limita respostas automáticas por conversa
|
||||||
|
const triagemRateLimit = new Map();
|
||||||
|
const TRIAGEM_MAX = 5; // max respostas auto em 60s
|
||||||
|
const TRIAGEM_WINDOW = 60000; // janela de 60 segundos
|
||||||
|
|
||||||
|
function triagemExcedeu(conversaId) {
|
||||||
|
const agora = Date.now();
|
||||||
|
const entry = triagemRateLimit.get(conversaId);
|
||||||
|
if (!entry || agora - entry.inicio > TRIAGEM_WINDOW) {
|
||||||
|
triagemRateLimit.set(conversaId, { count: 1, inicio: agora });
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
entry.count++;
|
||||||
|
return entry.count > TRIAGEM_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpeza periódica
|
||||||
|
setInterval(() => {
|
||||||
|
const agora = Date.now();
|
||||||
|
for (const [k, v] of triagemRateLimit) {
|
||||||
|
if (agora - v.inicio > TRIAGEM_WINDOW * 2) triagemRateLimit.delete(k);
|
||||||
|
}
|
||||||
|
}, 60000).unref();
|
||||||
|
|
||||||
// Cache de instancia -> alias para evitar consultas repetidas
|
// Cache de instancia -> alias para evitar consultas repetidas
|
||||||
var cacheInstanciaAlias = {};
|
var cacheInstanciaAlias = {};
|
||||||
@@ -262,6 +287,27 @@ class EvolutionController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listar instâncias de uma empresa
|
||||||
|
/**
|
||||||
|
* Busca instâncias já conectadas no servidor Evolution.
|
||||||
|
* POST /api/:alias/evolution/fetch-instances
|
||||||
|
*/
|
||||||
|
static async fetchInstances(req, res) {
|
||||||
|
try {
|
||||||
|
const { url, apiKey } = req.body;
|
||||||
|
if (!url || !apiKey) return res.status(400).json({ success: false, error: 'URL e API Key obrigatórios.' });
|
||||||
|
const result = await evolutionRequest(url, apiKey, '/instance/fetchInstances', 'GET');
|
||||||
|
if (Array.isArray(result)) {
|
||||||
|
res.json({ success: true, data: result.map(r => ({ instanceName: r.instanceName || r.name, connectionStatus: r.connectionStatus || r.status })) });
|
||||||
|
} else if (result && result.instance) {
|
||||||
|
res.json({ success: true, data: [{ instanceName: result.instance.instanceName || result.instance.name, connectionStatus: result.instance.connectionStatus || result.instance.status }] });
|
||||||
|
} else {
|
||||||
|
res.json({ success: true, data: [] });
|
||||||
|
}
|
||||||
|
} catch (err) { res.status(500).json({ success: false, error: err.message }); }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listar instâncias de uma empresa
|
* Listar instâncias de uma empresa
|
||||||
* GET /api/:alias/evolution/instances?empresaId=
|
* GET /api/:alias/evolution/instances?empresaId=
|
||||||
@@ -531,17 +577,30 @@ class EvolutionController {
|
|||||||
var u = new URL(fotoUrl);
|
var u = new URL(fotoUrl);
|
||||||
var lib = u.protocol === 'https:' ? https : http;
|
var lib = u.protocol === 'https:' ? https : http;
|
||||||
lib.get(fotoUrl, { timeout: 15000 }, function(imgRes) {
|
lib.get(fotoUrl, { timeout: 15000 }, function(imgRes) {
|
||||||
|
// Valida Content-Type
|
||||||
|
var ct = (imgRes.headers['content-type'] || '').toLowerCase();
|
||||||
|
if (ct && !ct.startsWith('image/')) {
|
||||||
|
console.log('[Foto] Content-Type invalido:', ct, '- recusando');
|
||||||
|
return reject(new Error('Not an image: ' + ct));
|
||||||
|
}
|
||||||
var chunks = [];
|
var chunks = [];
|
||||||
imgRes.on('data', function(c) { chunks.push(c); });
|
imgRes.on('data', function(c) { chunks.push(c); });
|
||||||
imgRes.on('end', function() { resolve(Buffer.concat(chunks)); });
|
imgRes.on('end', function() { resolve(Buffer.concat(chunks)); });
|
||||||
}).on('error', reject).on('timeout', function() { this.destroy(); reject(new Error('Timeout')); });
|
}).on('error', reject).on('timeout', function() { this.destroy(); reject(new Error('Timeout')); });
|
||||||
});
|
});
|
||||||
if (fotoBuffer && fotoBuffer.length > 100) {
|
// Valida buffer: tamanho realista (500B a 2MB) e header de imagem
|
||||||
|
if (fotoBuffer && fotoBuffer.length > 500 && fotoBuffer.length < 2 * 1024 * 1024) {
|
||||||
|
if (!mediaService.validarHeaderMidia(fotoBuffer, 'image/jpeg')) {
|
||||||
|
console.log('[Foto] Buffer nao tem header de imagem valido - recusando. Header:', fotoBuffer.slice(0, 4).toString('hex'));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
await db.execute(alias,
|
await db.execute(alias,
|
||||||
'UPDATE CLIENTES SET CLI_FOTO = ? WHERE CLI_CODIGO_ID = ?',
|
'UPDATE CLIENTES SET CLI_FOTO = ? WHERE CLI_CODIGO_ID = ?',
|
||||||
[fotoBuffer.toString('base64'), clienteId]);
|
[fotoBuffer.toString('base64'), clienteId]);
|
||||||
console.log('[Foto] Foto atualizada para cliente', clienteId, '- bytes:', fotoBuffer.length);
|
console.log('[Foto] Foto atualizada para cliente', clienteId, '- bytes:', fotoBuffer.length);
|
||||||
return true;
|
return true;
|
||||||
|
} else if (fotoBuffer) {
|
||||||
|
console.log('[Foto] Buffer com tamanho invalido:', fotoBuffer ? fotoBuffer.length : 0);
|
||||||
}
|
}
|
||||||
} catch(imgErr) {
|
} catch(imgErr) {
|
||||||
console.error('[Foto] Erro ao baixar imagem:', (imgErr.message || '').substring(0, 120));
|
console.error('[Foto] Erro ao baixar imagem:', (imgErr.message || '').substring(0, 120));
|
||||||
@@ -860,8 +919,8 @@ async function processWebhook(alias, body) {
|
|||||||
clienteId = resultadoGlobal.cliente.CLI_CODIGO_ID;
|
clienteId = resultadoGlobal.cliente.CLI_CODIGO_ID;
|
||||||
nomeCliente = (resultadoGlobal.cliente.CLI_NOME || '').trim();
|
nomeCliente = (resultadoGlobal.cliente.CLI_NOME || '').trim();
|
||||||
aliasEfetivo = resultadoGlobal.alias;
|
aliasEfetivo = resultadoGlobal.alias;
|
||||||
// Atualiza foto do contato
|
// ponytail: CLI_FOTO desativado (causa erro no ERP)
|
||||||
EvolutionController.atualizarFotoContato(aliasEfetivo, clienteId, numero, empresaId).catch(function() {});
|
// EvolutionController.atualizarFotoContato(aliasEfetivo, clienteId, numero, empresaId).catch(function() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usa nome do cliente do BD se encontrado, senão usa o nome do WhatsApp
|
// Usa nome do cliente do BD se encontrado, senão usa o nome do WhatsApp
|
||||||
@@ -934,8 +993,8 @@ async function processWebhook(alias, body) {
|
|||||||
'UPDATE CHATC2_CONVERSAS SET CON_CLIENTE_ID = ?, CON_NOME_CONTATO = ? WHERE CON_CODIGO_ID = ?',
|
'UPDATE CHATC2_CONVERSAS SET CON_CLIENTE_ID = ?, CON_NOME_CONTATO = ? WHERE CON_CODIGO_ID = ?',
|
||||||
[cliId, nomeCliente, conversa.CON_CODIGO_ID]);
|
[cliId, nomeCliente, conversa.CON_CODIGO_ID]);
|
||||||
conversa.CON_CLIENTE_ID = cliId;
|
conversa.CON_CLIENTE_ID = cliId;
|
||||||
// Atualiza foto APENAS na primeira vinculação (não a cada mensagem)
|
// ponytail: CLI_FOTO desativado
|
||||||
EvolutionController.atualizarFotoContato(aliasEfetivo, cliId, numero, empresaId).catch(function() {});
|
// EvolutionController.atualizarFotoContato(aliasEfetivo, cliId, numero, empresaId).catch(function() {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// NOTA: foto NÃO é mais atualizada a cada mensagem para conversas já vinculadas.
|
// NOTA: foto NÃO é mais atualizada a cada mensagem para conversas já vinculadas.
|
||||||
@@ -1280,8 +1339,15 @@ async function processWebhook(alias, body) {
|
|||||||
var emTriagem = conversa.CON_MENU_ESTADO &&
|
var emTriagem = conversa.CON_MENU_ESTADO &&
|
||||||
(!conversa.CON_USUARIO_ID || !conversa.CON_EQUIPE_ID);
|
(!conversa.CON_USUARIO_ID || !conversa.CON_EQUIPE_ID);
|
||||||
if (!isNovaConversa && messageText && messageType === 'conversation' && !data.key?.fromMe && emTriagem) {
|
if (!isNovaConversa && messageText && messageType === 'conversation' && !data.key?.fromMe && emTriagem) {
|
||||||
TriageController.processResponse(alias, conversa.CON_CODIGO_ID, empresaId, instanciaId, numero, messageText)
|
if (triagemExcedeu(conversa.CON_CODIGO_ID)) {
|
||||||
.catch(function(e) { console.error('[Triagem] Erro ao processar resposta:', e.message); });
|
console.log('[Webhook] Loop detectado na conversa', conversa.CON_CODIGO_ID, '- triagem pausada. Aguardando atendente.');
|
||||||
|
await db.execute(alias,
|
||||||
|
"UPDATE CHATC2_CONVERSAS SET CON_MENU_ESTADO = NULL WHERE CON_CODIGO_ID = ?",
|
||||||
|
[conversa.CON_CODIGO_ID]);
|
||||||
|
} else {
|
||||||
|
TriageController.processResponse(alias, conversa.CON_CODIGO_ID, empresaId, instanciaId, numero, messageText)
|
||||||
|
.catch(function(e) { console.error('[Triagem] Erro ao processar resposta:', e.message); });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ const GENERATORS = [
|
|||||||
'GEN_CHATC2_CSAT_AVALIACOES',
|
'GEN_CHATC2_CSAT_AVALIACOES',
|
||||||
'GEN_CHATC2_MOTIVOS_ATENDIMENTO',
|
'GEN_CHATC2_MOTIVOS_ATENDIMENTO',
|
||||||
'GEN_CHATC2_AGENDAMENTOS',
|
'GEN_CHATC2_AGENDAMENTOS',
|
||||||
|
'GEN_MENSAGEM_CLIENTES_ID',
|
||||||
];
|
];
|
||||||
|
|
||||||
async function nextId(alias, generatorName) {
|
async function nextId(alias, generatorName) {
|
||||||
|
|||||||
@@ -280,6 +280,14 @@ body { background:var(--surface-3); display:flex; min-height:100vh; }
|
|||||||
<div class="form-group"><label for="conApiKey">API Key</label><input type="text" id="conApiKey" placeholder="API Key"></div>
|
<div class="form-group"><label for="conApiKey">API Key</label><input type="text" id="conApiKey" placeholder="API Key"></div>
|
||||||
<div class="form-group"><label for="conInstance">Nome da Instância (Evolution)</label><input type="text" id="conInstance" placeholder="Ex: empresa1-wpp"></div>
|
<div class="form-group"><label for="conInstance">Nome da Instância (Evolution)</label><input type="text" id="conInstance" placeholder="Ex: empresa1-wpp"></div>
|
||||||
<div id="conexaoFeedback" class="feedback-msg"></div>
|
<div id="conexaoFeedback" class="feedback-msg"></div>
|
||||||
|
<div class="form-group" id="instanciasExistentesGroup" style="display:none">
|
||||||
|
<label>Instâncias já conectadas no servidor</label>
|
||||||
|
<div id="instanciasExistentesList" style="max-height:150px;overflow-y:auto;border:2px solid var(--border);border-radius:8px;padding:8px">
|
||||||
|
<button class="btn-sm" onclick="buscarInstanciasExistentes()" style="width:100%;padding:8px;font-size:12px">🔍 Buscar instâncias no servidor</button>
|
||||||
|
</div>
|
||||||
|
<span style="font-size:11px;color:var(--text-faint)">Clique em uma instância existente para reutilizá-la.</span>
|
||||||
|
</div>
|
||||||
|
<div id="conexaoFeedback" class="feedback-msg"></div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button class="btn-secondary" onclick="fecharModal('modalConexao')">Cancelar</button>
|
<button class="btn-secondary" onclick="fecharModal('modalConexao')">Cancelar</button>
|
||||||
<button class="btn-primary" onclick="salvarConexao()" id="btnSalvarConexao">Conectar</button>
|
<button class="btn-primary" onclick="salvarConexao()" id="btnSalvarConexao">Conectar</button>
|
||||||
@@ -954,6 +962,45 @@ window.mostrarModalConexao = function() {
|
|||||||
document.getElementById('modalConexaoTitle').textContent = 'Nova Conexão WhatsApp';
|
document.getElementById('modalConexaoTitle').textContent = 'Nova Conexão WhatsApp';
|
||||||
document.getElementById('btnSalvarConexao').textContent = 'Conectar';
|
document.getElementById('btnSalvarConexao').textContent = 'Conectar';
|
||||||
document.getElementById('modalConexao').classList.add('show');
|
document.getElementById('modalConexao').classList.add('show');
|
||||||
|
document.getElementById('instanciasExistentesGroup').style.display = 'block';
|
||||||
|
document.getElementById('instanciasExistentesList').innerHTML = '<button class="btn-sm" onclick="buscarInstanciasExistentes()" style="width:100%;padding:8px;font-size:12px">🔍 Buscar instâncias no servidor</button>';
|
||||||
|
};
|
||||||
|
|
||||||
|
window.buscarInstanciasExistentes = async function() {
|
||||||
|
var list = document.getElementById('instanciasExistentesList');
|
||||||
|
var url = document.getElementById('conUrl').value.trim();
|
||||||
|
var apiKey = document.getElementById('conApiKey').value.trim();
|
||||||
|
if (!url || !apiKey) { list.innerHTML = '<span style="font-size:12px;color:var(--text-faint)">Preencha URL e API Key primeiro</span>'; return; }
|
||||||
|
list.innerHTML = '<span style="font-size:12px;color:var(--text-faint)">Buscando...</span>';
|
||||||
|
try {
|
||||||
|
var r = await fetch('/api/' + alias + '/evolution/fetch-instances', {
|
||||||
|
method: 'POST', body: JSON.stringify({ url: url, apiKey: apiKey }),
|
||||||
|
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token }
|
||||||
|
});
|
||||||
|
var d = await r.json();
|
||||||
|
if (d.success && d.data && d.data.length > 0) {
|
||||||
|
list.innerHTML = d.data.map(function(i) {
|
||||||
|
var nm = (i.instanceName || i.name || '');
|
||||||
|
var st = i.connectionStatus === 'open' ? '🟢' : '🔴';
|
||||||
|
return '<div data-instance="' + nm.replace(/"/g, '"') + '" class="instancia-existente" style="padding:6px 8px;border-radius:4px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:6px;border-bottom:1px solid var(--border);color:var(--text-primary)">' + st + ' <strong>' + nm + '</strong><span style="margin-left:auto;font-size:11px;color:var(--text-muted)">' + (i.connectionStatus || i.status || '-') + '</span></div>';
|
||||||
|
}).join('');
|
||||||
|
// Delegation: click em qualquer .instancia-existente
|
||||||
|
list.querySelectorAll('.instancia-existente').forEach(function(el) {
|
||||||
|
el.addEventListener('click', function() { reutilizarInstancia(this.dataset.instance); });
|
||||||
|
});
|
||||||
|
} else { list.innerHTML = '<span style="font-size:12px;color:var(--text-faint)">Nenhuma instância no servidor</span>'; }
|
||||||
|
} catch(e) { list.innerHTML = '<span style="font-size:12px;color:var(--danger)">Erro ao buscar</span>'; }
|
||||||
|
};
|
||||||
|
|
||||||
|
window.reutilizarInstancia = function(nm) {
|
||||||
|
document.getElementById('conInstance').value = nm;
|
||||||
|
var existente = document.getElementById('conInstanceExistente');
|
||||||
|
if (!existente) {
|
||||||
|
existente = document.createElement('span'); existente.id = 'conInstanceExistente';
|
||||||
|
existente.style.cssText = 'font-size:11px;color:#059669;display:block;margin-top:3px';
|
||||||
|
document.getElementById('conInstance').parentNode.appendChild(existente);
|
||||||
|
}
|
||||||
|
existente.textContent = '✓ Reutilizando: ' + nm;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.gerarQRCode = async function(id) {
|
window.gerarQRCode = async function(id) {
|
||||||
@@ -1007,8 +1054,9 @@ window.salvarConexao = async function() {
|
|||||||
INS_INSTANCE_NAME: document.getElementById('conInstance').value.trim(),
|
INS_INSTANCE_NAME: document.getElementById('conInstance').value.trim(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data.INS_NOME || !data.INS_URL || !data.INS_API_KEY || !data.INS_INSTANCE_NAME)
|
if (!data.INS_NOME || !data.INS_URL || !data.INS_API_KEY || !data.INS_INSTANCE_NAME) {
|
||||||
mostrarFeedbackModal('conexaoFeedback', 'Preencha todos os campos', 'erro'); return;
|
mostrarFeedbackModal('conexaoFeedback', 'Preencha todos os campos', 'erro'); return;
|
||||||
|
}
|
||||||
|
|
||||||
var r;
|
var r;
|
||||||
if (editId) {
|
if (editId) {
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ router.post('/:alias/evolution/connect', EvolutionController.connect);
|
|||||||
router.put('/:alias/evolution/instances/:id', EvolutionController.updateInstance);
|
router.put('/:alias/evolution/instances/:id', EvolutionController.updateInstance);
|
||||||
router.delete('/:alias/evolution/instances/:id', EvolutionController.deleteInstance);
|
router.delete('/:alias/evolution/instances/:id', EvolutionController.deleteInstance);
|
||||||
router.post('/:alias/evolution/qrcode/:id', EvolutionController.generateQRCode);
|
router.post('/:alias/evolution/qrcode/:id', EvolutionController.generateQRCode);
|
||||||
|
router.post('/:alias/evolution/fetch-instances', EvolutionController.fetchInstances);
|
||||||
router.post('/:alias/evolution/refresh-photo/:conversaId', EvolutionController.refreshPhoto);
|
router.post('/:alias/evolution/refresh-photo/:conversaId', EvolutionController.refreshPhoto);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ router.get('/api/:alias/metrics', authenticateToken, (req, res) => {
|
|||||||
uptimeSeconds: Math.floor(process.uptime()),
|
uptimeSeconds: Math.floor(process.uptime()),
|
||||||
memory: process.memoryUsage(),
|
memory: process.memoryUsage(),
|
||||||
metrics: metrics.getAll(),
|
metrics: metrics.getAll(),
|
||||||
cache: require('../services/cacheService').stats(),
|
|
||||||
pool: require('../database').poolStats(),
|
pool: require('../database').poolStats(),
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
/**
|
|
||||||
* Cache em memória com TTL.
|
|
||||||
*
|
|
||||||
* Simples, sem dependências externas. Usado para evitar consultas repetidas
|
|
||||||
* ao banco para dados que mudam raramente (ex: configurações da empresa).
|
|
||||||
*
|
|
||||||
* Uso:
|
|
||||||
* const cache = require('../services/cacheService');
|
|
||||||
* let valor = cache.get('chave');
|
|
||||||
* if (!valor) {
|
|
||||||
* valor = await db.query(...);
|
|
||||||
* cache.set('chave', valor, 60000); // TTL 60s
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
|
|
||||||
const store = new Map();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Armazena um valor no cache.
|
|
||||||
* @param {string} key
|
|
||||||
* @param {*} value
|
|
||||||
* @param {number} [ttlMs=60000] - Tempo de vida em ms (padrão: 1 minuto)
|
|
||||||
*/
|
|
||||||
function set(key, value, ttlMs = 60000) {
|
|
||||||
store.set(key, {
|
|
||||||
value,
|
|
||||||
expiresAt: Date.now() + ttlMs,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recupera um valor do cache. Retorna undefined se expirado ou inexistente.
|
|
||||||
* @param {string} key
|
|
||||||
* @returns {*|undefined}
|
|
||||||
*/
|
|
||||||
function get(key) {
|
|
||||||
const entry = store.get(key);
|
|
||||||
if (!entry) return undefined;
|
|
||||||
if (Date.now() > entry.expiresAt) {
|
|
||||||
store.delete(key);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return entry.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalida uma chave específica.
|
|
||||||
* @param {string} key
|
|
||||||
*/
|
|
||||||
function del(key) {
|
|
||||||
store.delete(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalida todas as chaves que começam com o prefixo.
|
|
||||||
* Útil para invalidar todo o cache de uma empresa (ex: 'config:5:').
|
|
||||||
* @param {string} prefix
|
|
||||||
*/
|
|
||||||
function delByPrefix(prefix) {
|
|
||||||
for (const key of store.keys()) {
|
|
||||||
if (key.startsWith(prefix)) {
|
|
||||||
store.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retorna estatísticas do cache para debug.
|
|
||||||
*/
|
|
||||||
function stats() {
|
|
||||||
let total = 0, valid = 0;
|
|
||||||
const now = Date.now();
|
|
||||||
for (const [, entry] of store) {
|
|
||||||
total++;
|
|
||||||
if (entry.expiresAt > now) valid++;
|
|
||||||
}
|
|
||||||
return { total, valid, expired: total - valid };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limpeza periódica de entradas expiradas (evita vazamento de memória)
|
|
||||||
const intervalo = setInterval(() => {
|
|
||||||
const now = Date.now();
|
|
||||||
for (const [key, entry] of store) {
|
|
||||||
if (entry.expiresAt <= now) store.delete(key);
|
|
||||||
}
|
|
||||||
}, 60000);
|
|
||||||
if (intervalo.unref) intervalo.unref();
|
|
||||||
|
|
||||||
module.exports = { set, get, del, delByPrefix, stats };
|
|
||||||
@@ -1,56 +1,31 @@
|
|||||||
/**
|
/**
|
||||||
* Serviço de configuração da empresa com cache.
|
* Serviço de configuração da empresa (sem cache).
|
||||||
*
|
*
|
||||||
* As flags CFE_* (triagem, saudação, CSAT, etc.) são lidas em toda operação
|
* Lê direto do banco — sem TTL, sem stale data.
|
||||||
* de chat/triagem/webhook. Este serviço armazena em cache (TTL 2min) e
|
* As flags CFE_* são consultadas a cada operação, então refletem
|
||||||
* invalida automaticamente quando as configurações são alteradas.
|
* imediatamente qualquer alteração salva em Settings.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const db = require('../database');
|
const db = require('../database');
|
||||||
const cache = require('./cacheService');
|
|
||||||
|
|
||||||
const TTL = 2 * 60 * 1000; // 2 minutos
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retorna a configuração completa de uma empresa (cacheada).
|
* Retorna a configuração completa de uma empresa (direto do banco).
|
||||||
* Invalidação: chamar invalidarConfig(alias, empresaId) após salvar settings.
|
|
||||||
*
|
*
|
||||||
* @param {string} alias
|
* @param {string} alias
|
||||||
* @param {number} empresaId
|
* @param {number} empresaId
|
||||||
* @returns {Promise<object>} Linha da tabela CHATC2_CONFIGURACOES_EMPRESA
|
* @returns {Promise<object>}
|
||||||
*/
|
*/
|
||||||
async function getConfig(alias, empresaId) {
|
async function getConfig(alias, empresaId) {
|
||||||
const key = 'cfg:' + alias + ':' + empresaId;
|
|
||||||
const cached = cache.get(key);
|
|
||||||
if (cached) return cached;
|
|
||||||
|
|
||||||
// Garante que o registro existe (insere se não existir)
|
|
||||||
await garantirRegistro(alias, empresaId);
|
await garantirRegistro(alias, empresaId);
|
||||||
|
|
||||||
const result = await db.query(alias,
|
const result = await db.query(alias,
|
||||||
'SELECT * FROM CHATC2_CONFIGURACOES_EMPRESA WHERE CFE_EMPRESA_ID = ?',
|
'SELECT * FROM CHATC2_CONFIGURACOES_EMPRESA WHERE CFE_EMPRESA_ID = ?',
|
||||||
[empresaId]);
|
[empresaId]);
|
||||||
|
return result[0] || {};
|
||||||
const config = result[0] || {};
|
|
||||||
cache.set(key, config, TTL);
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** No-op: não há cache para invalidar */
|
||||||
* Invalida o cache de configuração de uma empresa.
|
function invalidarConfig() {}
|
||||||
* Deve ser chamado após salvar alterações nas configurações.
|
|
||||||
*
|
|
||||||
* @param {string} alias
|
|
||||||
* @param {number} empresaId
|
|
||||||
*/
|
|
||||||
function invalidarConfig(alias, empresaId) {
|
|
||||||
cache.del('cfg:' + alias + ':' + empresaId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Garante que existe um registro de configuração para a empresa.
|
|
||||||
* Idempotente — se já existe, não faz nada.
|
|
||||||
*/
|
|
||||||
async function garantirRegistro(alias, empresaId) {
|
async function garantirRegistro(alias, empresaId) {
|
||||||
try {
|
try {
|
||||||
const existe = await db.query(alias,
|
const existe = await db.query(alias,
|
||||||
@@ -63,7 +38,6 @@ async function garantirRegistro(alias, empresaId) {
|
|||||||
`, [empresaId]);
|
`, [empresaId]);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Tabela pode não existir ainda — ignora
|
|
||||||
if (!e.message.includes('unknown') && !e.message.includes('não encontrado')) {
|
if (!e.message.includes('unknown') && !e.message.includes('não encontrado')) {
|
||||||
console.error('[ConfigService] Erro ao garantir registro:', e.message.substring(0, 100));
|
console.error('[ConfigService] Erro ao garantir registro:', e.message.substring(0, 100));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user