bc03f5dc1d
- fix: migration #25 agora so cria o generator (tabela ja existe no ERP) - fix: insert usa MENSAGEM_CLIENTES (singular) e GEN_MENSAGEM_CLIENTES - fix: database.js whitelist atualizado para GEN_MENSAGEM_CLIENTES
236 lines
15 KiB
JavaScript
236 lines
15 KiB
JavaScript
/**
|
|
* Runner de migrações — executável via CLI ou automaticamente no boot.
|
|
*
|
|
* Exporta uma função `executarMigracoes(alias)` que aplica todas as migrações
|
|
* pendentes de forma idempotente. Usado por server.js no boot e por
|
|
* scripts/migracoes.js para execução manual.
|
|
*/
|
|
|
|
const db = require('../src/database');
|
|
|
|
// ============================================================
|
|
// LISTA DE MIGRAÇÕES
|
|
// ============================================================
|
|
// SEMPRE adicione NOVAS migrações no FINAL do array, com ID sequencial.
|
|
// NUNCA remova ou modifique migrações já existentes.
|
|
|
|
const MIGRACOES = [
|
|
{ id: 1, descricao: 'Adicionar USU_TIPO_CHAT em USUARIOS (A=Atendente, G=Gerente)', sql: ['ALTER TABLE USUARIOS ADD USU_TIPO_CHAT CHAR(1) DEFAULT \'A\''] },
|
|
{ id: 2, descricao: 'Criar tabela CHATC2_INSTANCIAS para conexões WhatsApp', sql: [`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')`] },
|
|
{ id: 3, descricao: 'Criar tabela CHATC2_EQUIPES', sql: [`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')`] },
|
|
{ id: 4, descricao: 'Criar tabela CHATC2_USU_EQUIPES (relação N:N)', sql: ['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))'] },
|
|
{ id: 5, descricao: 'Criar tabela CHATC2_ETIQUETAS', sql: [`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')`] },
|
|
{ id: 6, descricao: 'Criar tabela CHATC2_CONVERSAS', sql: [`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')`] },
|
|
{ id: 7, descricao: 'Criar tabela CHATC2_CONVERSAS_MENSAGENS', sql: [`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')`] },
|
|
{ id: 8, descricao: 'Criar tabela CHATC2_MENSAGENS_ATENDIMENTOS para mídias', sql: [`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')`] },
|
|
{ id: 9, descricao: 'Criar tabela CHATC2_CONVERSAS_ETIQUETAS', sql: ['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))'] },
|
|
{ id: 10, descricao: 'Criar tabela CHATC2_CSAT_AVALIACOES', sql: [`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)`] },
|
|
{ id: 11, descricao: 'Criar tabela CHATC2_CONFIGURACOES_EMPRESA (base)', sql: [`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')`] },
|
|
{ id: 12, descricao: 'Adicionar EQU_ORDEM em CHATC2_EQUIPES (ordenação)', sql: ['ALTER TABLE CHATC2_EQUIPES ADD EQU_ORDEM INTEGER DEFAULT 0'] },
|
|
{ id: 13, descricao: 'Adicionar campos de triagem em CHATC2_CONFIGURACOES_EMPRESA', sql: ['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\''] },
|
|
{ id: 14, descricao: 'Adicionar CON_MENU_ESTADO em CHATC2_CONVERSAS (navegação triagem)', sql: ['ALTER TABLE CHATC2_CONVERSAS ADD CON_MENU_ESTADO VARCHAR(100)'] },
|
|
{ id: 15, descricao: 'Adicionar CON_ETIQUETAS_DESC em CHATC2_CONVERSAS', sql: ['ALTER TABLE CHATC2_CONVERSAS ADD CON_ETIQUETAS_DESC VARCHAR(500)'] },
|
|
{ id: 16, descricao: 'Criar tabela CHATC2_MENUS_EMPRESA para fluxo personalizado', sql: [`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')`] },
|
|
{ id: 17, descricao: 'Adicionar CON_USUARIO_NOME e CON_EQUIPE_NOME em CHATC2_CONVERSAS', sql: ['ALTER TABLE CHATC2_CONVERSAS ADD CON_USUARIO_NOME VARCHAR(100)', 'ALTER TABLE CHATC2_CONVERSAS ADD CON_EQUIPE_NOME VARCHAR(100)'] },
|
|
{ id: 18, descricao: 'Adicionar MNE_ETIQUETA_IDS em CHATC2_MENUS_EMPRESA', sql: ['ALTER TABLE CHATC2_MENUS_EMPRESA ADD MNE_ETIQUETA_IDS VARCHAR(200)'] },
|
|
{ id: 19, descricao: 'Adicionar MAT_TRANSCRICAO em CHATC2_MENSAGENS_ATENDIMENTOS', sql: ['ALTER TABLE CHATC2_MENSAGENS_ATENDIMENTOS ADD MAT_TRANSCRICAO BLOB SUB_TYPE TEXT'] },
|
|
{ id: 20, descricao: 'Criar generators Firebird para eliminar race condition',
|
|
sql: [
|
|
'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',
|
|
],
|
|
async after(alias) {
|
|
const generators = [
|
|
{ gen: 'GEN_CHATC2_CONVERSAS', tabela: 'CHATC2_CONVERSAS', col: 'CON_CODIGO_ID' },
|
|
{ gen: 'GEN_CONVERSAS_MENSAGENS', tabela: 'CHATC2_CONVERSAS_MENSAGENS', col: 'CME_CODIGO_ID' },
|
|
{ gen: 'GEN_MENSAGENS_ATENDIMENTOS', tabela: 'CHATC2_MENSAGENS_ATENDIMENTOS', col: 'MAT_CODIGO_ID' },
|
|
{ gen: 'GEN_CHATC2_EQUIPES', tabela: 'CHATC2_EQUIPES', col: 'EQU_CODIGO_ID' },
|
|
{ gen: 'GEN_CHATC2_ETIQUETAS', tabela: 'CHATC2_ETIQUETAS', col: 'ETI_CODIGO_ID' },
|
|
{ gen: 'GEN_CHATC2_INSTANCIAS', tabela: 'CHATC2_INSTANCIAS', col: 'INS_CODIGO_ID' },
|
|
{ gen: 'GEN_CHATC2_MENUS_EMPRESA', tabela: 'CHATC2_MENUS_EMPRESA', col: 'MNE_CODIGO_ID' },
|
|
{ gen: 'GEN_CHATC2_CSAT_AVALIACOES', tabela: 'CHATC2_CSAT_AVALIACOES', col: 'CSA_CODIGO_ID' },
|
|
{ gen: 'GEN_CHATC2_MOTIVOS_ATENDIMENTO', tabela: '"CHATC2_MOTIVOS_ATENDIMENTO"', col: 'MOT_CODIGO_ID' },
|
|
];
|
|
for (const g of generators) {
|
|
try {
|
|
const r = await db.query(alias, `SELECT COALESCE(MAX(${g.col}), 0) + 1 AS VAL FROM ${g.tabela}`);
|
|
await db.execute(alias, `SET GENERATOR ${g.gen} TO ${r[0]?.VAL || 1}`);
|
|
} catch (_) {}
|
|
}
|
|
},
|
|
},
|
|
|
|
// ----------------------------------------------------------
|
|
// MIGRAÇÃO 21: Índices para busca de telefone (webhook)
|
|
// ----------------------------------------------------------
|
|
{
|
|
id: 21,
|
|
descricao: 'Índices para busca de cliente por telefone (acelera webhook)',
|
|
sql: [
|
|
// Índice na tabela CLIENTES para busca por CELULAR/FONE1/FONE2
|
|
'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)',
|
|
// Índice composto para a query mais comum do webhook
|
|
'CREATE INDEX IDX_CONV_NUMERO_EMPRESA ON CHATC2_CONVERSAS (CON_NUMERO, CON_EMPRESA_ID)',
|
|
'CREATE INDEX IDX_CONV_STATUS ON CHATC2_CONVERSAS (CON_STATUS)',
|
|
],
|
|
},
|
|
|
|
// ----------------------------------------------------------
|
|
// MIGRAÇÃO 22: Mensagem e etiqueta por equipe (triagem)
|
|
// ----------------------------------------------------------
|
|
{
|
|
id: 22,
|
|
descricao: 'Adicionar EQU_MENSAGEM e EQU_ETIQUETA_ID em CHATC2_EQUIPES',
|
|
sql: [
|
|
'ALTER TABLE CHATC2_EQUIPES ADD EQU_MENSAGEM VARCHAR(500)',
|
|
'ALTER TABLE CHATC2_EQUIPES ADD EQU_ETIQUETA_ID INTEGER',
|
|
],
|
|
},
|
|
|
|
// ----------------------------------------------------------
|
|
// MIGRAÇÃO 23: Agendamento de conversas
|
|
// ----------------------------------------------------------
|
|
{
|
|
id: 23,
|
|
descricao: 'Criar tabela CHATC2_CONVERSAS_AGENDAMENTOS (retorno agendado)',
|
|
sql: [
|
|
`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
|
|
)`,
|
|
],
|
|
},
|
|
|
|
// ----------------------------------------------------------
|
|
// MIGRAÇÃO 24: DDL que estava no resolucaoSetup.js (runtime)
|
|
// ----------------------------------------------------------
|
|
{
|
|
id: 24,
|
|
descricao: 'Consolidar DDL do fluxo de resolução (antes criado em runtime)',
|
|
sql: [
|
|
`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)',
|
|
],
|
|
},
|
|
|
|
// ----------------------------------------------------------
|
|
// MIGRAÇÃO 25: Generator para MENSAGEM_CLIENTES (tabela do ERP)
|
|
// ----------------------------------------------------------
|
|
{
|
|
id: 25,
|
|
descricao: 'Criar generator para MENSAGEM_CLIENTES (tabela ja existe no ERP)',
|
|
sql: [
|
|
'CREATE GENERATOR GEN_MENSAGEM_CLIENTES',
|
|
],
|
|
async after(alias) {
|
|
try {
|
|
const r = await db.query(alias, 'SELECT COALESCE(MAX(MSC_CODIGO_ID),0)+1 AS V FROM MENSAGEM_CLIENTES');
|
|
await db.execute(alias, 'SET GENERATOR GEN_MENSAGEM_CLIENTES TO ' + (r[0]?.V || 1));
|
|
} catch (_) {}
|
|
},
|
|
},
|
|
];
|
|
|
|
// ============================================================
|
|
// EXECUTOR
|
|
// ============================================================
|
|
|
|
async function garantirControle(alias) {
|
|
try {
|
|
await db.execute(alias, `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')`);
|
|
} catch (_) {}
|
|
}
|
|
|
|
async function migracaoJaExecutada(alias, id) {
|
|
try {
|
|
const r = await db.query(alias, 'SELECT COUNT(*) AS CT FROM CHATC2_CONTROLE_MIGRACOES WHERE MIG_ID = ?', [id]);
|
|
return r[0]?.CT > 0;
|
|
} catch (_) { return false; }
|
|
}
|
|
|
|
async function registrarMigracao(alias, id, descricao) {
|
|
try {
|
|
await db.execute(alias, 'INSERT INTO CHATC2_CONTROLE_MIGRACOES (MIG_ID, MIG_DESCRICAO) VALUES (?, ?)', [id, descricao]);
|
|
} catch (_) {}
|
|
}
|
|
|
|
/**
|
|
* Executa todas as migrações pendentes para um alias.
|
|
* Idempotente: cada migração roda uma única vez.
|
|
*
|
|
* @param {string} alias
|
|
* @param {boolean} [silencioso=false] - Se true, não loga no console
|
|
* @returns {Promise<number>} Quantidade de migrações executadas
|
|
*/
|
|
async function executarMigracoes(alias, silencioso) {
|
|
if (!silencioso) console.log(`📦 Verificando migrações para "${alias}"...`);
|
|
|
|
const driver = db.driverOf(alias);
|
|
if (driver !== 'firebird') {
|
|
if (!silencioso) console.log(` ⏭️ Driver "${driver}" — sem migrações DDL`);
|
|
return 0;
|
|
}
|
|
|
|
await garantirControle(alias);
|
|
|
|
let executadas = 0;
|
|
for (const mig of MIGRACOES) {
|
|
if (await migracaoJaExecutada(alias, mig.id)) continue;
|
|
|
|
if (!silencioso) console.log(` ▶️ [${mig.id}] ${mig.descricao}`);
|
|
|
|
let erros = 0;
|
|
for (const cmd of mig.sql) {
|
|
try {
|
|
await db.execute(alias, cmd);
|
|
} catch (e) {
|
|
if (e.message.includes('already exists') || e.message.includes('Violation') || e.message.includes('duplicate')) {
|
|
continue; // idempotente
|
|
}
|
|
if (!silencioso) console.log(` ❌ ${e.message.substring(0, 100)}`);
|
|
erros++;
|
|
}
|
|
}
|
|
|
|
if (erros === 0) {
|
|
if (typeof mig.after === 'function') {
|
|
try { await mig.after(alias); } catch (_) {}
|
|
}
|
|
await registrarMigracao(alias, mig.id, mig.descricao);
|
|
executadas++;
|
|
}
|
|
}
|
|
|
|
if (executadas > 0 && !silencioso) console.log(` ✅ ${executadas} migração(ões) aplicada(s)`);
|
|
else if (!silencioso) console.log(` ✅ Nenhuma migração pendente`);
|
|
|
|
return executadas;
|
|
}
|
|
|
|
module.exports = { executarMigracoes, MIGRACOES };
|