WIP: Lógica de sincronização bidirecional criada.
- Tabelas de updates criada e adicionado ao script - Tabela de marca d'água criada - Fluxo de coleta de mensagens do HubSoft criado - Fluxo de coleta de comentários do GLPI criado.
This commit is contained in:
parent
4d931bdddf
commit
fe4462c323
@ -39,9 +39,9 @@ HUBGLPI_DB_PASSWORD=Ut@2S@$M9Xs@@W
|
|||||||
# BANCO DE DADOS FINAL - GLPI (MySQL - Desenvolvimento)
|
# BANCO DE DADOS FINAL - GLPI (MySQL - Desenvolvimento)
|
||||||
# ==============================================================================
|
# ==============================================================================
|
||||||
GLPI_DB_TYPE=mysql
|
GLPI_DB_TYPE=mysql
|
||||||
GLPI_DB_HOST=177.73.177.32
|
GLPI_DB_HOST=177.73.177.44
|
||||||
GLPI_DB_PORT=3306
|
GLPI_DB_PORT=3306
|
||||||
GLPI_DB_USER=snglpi
|
GLPI_DB_USER=desenvolvimento
|
||||||
GLPI_DB_PASSWORD=j2633669
|
GLPI_DB_PASSWORD=Ut@2S@$M9Xs@@W
|
||||||
GLPI_DB_NAME=glpi_data
|
GLPI_DB_NAME=glpi_data
|
||||||
GLPI_DB_CHARSET=utf8mb4
|
GLPI_DB_CHARSET=utf8mb4
|
||||||
|
|||||||
31
src/controller/commentController.js
Normal file
31
src/controller/commentController.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// src/controller/commentController.js
|
||||||
|
const commentService = require('../services/commentService.js');
|
||||||
|
const { logInfo, logError, logWarning } = require('../utils/logger.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller para lidar com o webhook de novo comentário do GLPI.
|
||||||
|
* @param {import('express').Request} req - O objeto de requisição do Express.
|
||||||
|
* @param {import('express').Response} res - O objeto de resposta do Express.
|
||||||
|
*/
|
||||||
|
async function handleNewComment(req, res) {
|
||||||
|
const body = req.body;
|
||||||
|
const glpiTicketId = body?.item?.items_id;
|
||||||
|
const commentContent = body?.item?.content;
|
||||||
|
|
||||||
|
if (!glpiTicketId || !commentContent) {
|
||||||
|
logWarning('Webhook de novo comentário recebido com dados incompletos.', body);
|
||||||
|
return res.status(400).json({ error: 'Dados do comentário ou ID do ticket ausentes.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo(`Webhook de novo comentário recebido para o ticket GLPI ID ${glpiTicketId}.`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await commentService.syncGlpiCommentToHubsoft(glpiTicketId, commentContent);
|
||||||
|
res.status(200).json({ status: 'success', message: 'Comentário recebido e processado.' });
|
||||||
|
} catch (error) {
|
||||||
|
logError(`Erro ao processar webhook de comentário para o ticket GLPI ID ${glpiTicketId}:`, error);
|
||||||
|
res.status(500).json({ status: 'error', message: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { handleNewComment };
|
||||||
19
src/cron.js
19
src/cron.js
@ -3,23 +3,34 @@ loadEnv();
|
|||||||
|
|
||||||
const cron = require('node-cron');
|
const cron = require('node-cron');
|
||||||
const { processaAtendimentos } = require('./controller/processController.js');
|
const { processaAtendimentos } = require('./controller/processController.js');
|
||||||
|
const commentService = require('./services/commentService.js'); // 1. Importar o novo serviço
|
||||||
const { logInfo, logError } = require('./utils/logger.js');
|
const { logInfo, logError } = require('./utils/logger.js');
|
||||||
|
|
||||||
let isCronRunning = false;
|
let isCronRunning = false;
|
||||||
|
|
||||||
logInfo('⏰ Agendando cron job para processar atendimentos a cada 5 minutos.');
|
logInfo('⏰ Agendando cron job para processar atendimentos a cada 1 minuto.');
|
||||||
|
|
||||||
cron.schedule('*/5 * * * *', async () => {
|
cron.schedule('* * * * *', async () => {
|
||||||
if (isCronRunning) {
|
if (isCronRunning) {
|
||||||
logInfo('CRON: Tentativa de início, mas o processo anterior ainda está em execução. Pulando esta rodada.');
|
logInfo('CRON: Tentativa de início, mas o processo anterior ainda está em execução. Pulando esta rodada.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isCronRunning = true;
|
isCronRunning = true;
|
||||||
logInfo('CRON: Iniciando processamento de atendimentos...');
|
logInfo('CRON: Iniciando ciclo de sincronização...');
|
||||||
try {
|
try {
|
||||||
|
// --- Tarefa 1: Sincronizar criação de tickets ---
|
||||||
|
logInfo('CRON (Etapa 1/2): Processando criação de tickets...');
|
||||||
await processaAtendimentos();
|
await processaAtendimentos();
|
||||||
logInfo('CRON: Processamento concluído com sucesso.');
|
logInfo('CRON (Etapa 1/2): Criação de tickets concluída.');
|
||||||
|
|
||||||
|
// --- Tarefa 2: Sincronizar comentários ---
|
||||||
|
logInfo('CRON (Etapa 2/2): Processando sincronização de comentários...');
|
||||||
|
await commentService.syncHubsoftCommentsToLocalDB();
|
||||||
|
await commentService.sendPendingCommentsToGlpi(); // E outras direções se necessário
|
||||||
|
logInfo('CRON (Etapa 2/2): Sincronização de comentários concluída.');
|
||||||
|
|
||||||
|
logInfo('CRON: Ciclo de sincronização concluído com sucesso.');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError('CRON: Erro durante o processamento de atendimentos.', error);
|
logError('CRON: Erro durante o processamento de atendimentos.', error);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
130
src/model/commentModel.js
Normal file
130
src/model/commentModel.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
const { logError, logInfo } = require('../utils/logger');
|
||||||
|
const pool = require('../data/hubglpiDataBase');
|
||||||
|
|
||||||
|
class CommentModel {
|
||||||
|
/**
|
||||||
|
* Busca o timestamp da última execução de um job de sincronização.
|
||||||
|
* @param {string} jobName - O nome do job (ex: 'hubsoft_comments_sync').
|
||||||
|
* @returns {Promise<Date>} O timestamp da última execução.
|
||||||
|
*/
|
||||||
|
static async getLastRunTimestamp(jobName) {
|
||||||
|
const query = 'SELECT last_run_timestamp FROM sync_control WHERE job_name = $1';
|
||||||
|
try {
|
||||||
|
const { rows } = await pool.query(query, [jobName]);
|
||||||
|
if (rows.length === 0) {
|
||||||
|
throw new Error(`Job de controle '${jobName}' não encontrado na tabela sync_control.`);
|
||||||
|
}
|
||||||
|
return rows[0].last_run_timestamp;
|
||||||
|
} catch (error) {
|
||||||
|
logError(`Erro ao buscar último timestamp para o job '${jobName}':`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atualiza o timestamp da última execução de um job de sincronização.
|
||||||
|
* @param {string} jobName - O nome do job.
|
||||||
|
* @param {Date} timestamp - O novo timestamp.
|
||||||
|
*/
|
||||||
|
static async updateLastRunTimestamp(jobName, timestamp) {
|
||||||
|
const query = 'UPDATE sync_control SET last_run_timestamp = $1 WHERE job_name = $2';
|
||||||
|
try {
|
||||||
|
await pool.query(query, [timestamp, jobName]);
|
||||||
|
} catch (error) {
|
||||||
|
logError(`Erro ao atualizar o timestamp para o job '${jobName}':`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insere um novo comentário na fila de sincronização.
|
||||||
|
* Usa ON CONFLICT para evitar duplicatas com base no sistema de origem e ID do comentário.
|
||||||
|
* @param {object} commentData - Dados do comentário.
|
||||||
|
* @param {number} commentData.hubsoftAtendimentoId - ID do atendimento no HubSoft.
|
||||||
|
* @param {string} commentData.sourceSystem - 'hubsoft' ou 'glpi'.
|
||||||
|
* @param {string} commentData.sourceCommentId - ID do comentário na origem.
|
||||||
|
* @param {string} commentData.content - Conteúdo do comentário.
|
||||||
|
* @param {string} [commentData.author] - Autor do comentário.
|
||||||
|
*/
|
||||||
|
static async insertComment(commentData) {
|
||||||
|
// Primeiro, precisamos do ID do sync_data correspondente ao atendimento do HubSoft.
|
||||||
|
const syncDataQuery = 'SELECT id FROM sync_data WHERE hubsoft_ticket_id = $1';
|
||||||
|
const syncDataRes = await pool.query(syncDataQuery, [commentData.hubsoftAtendimentoId]);
|
||||||
|
|
||||||
|
if (syncDataRes.rows.length === 0) {
|
||||||
|
logInfo(`Nenhum registro de sincronização encontrado para o atendimento HubSoft ID ${commentData.hubsoftAtendimentoId}. Comentário não será inserido.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const syncDataId = syncDataRes.rows[0].id;
|
||||||
|
|
||||||
|
const insertQuery = `
|
||||||
|
INSERT INTO sync_comments (
|
||||||
|
sync_data_id, source_system, source_comment_id, content, author
|
||||||
|
)
|
||||||
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
|
ON CONFLICT (source_system, source_comment_id) DO NOTHING;
|
||||||
|
`;
|
||||||
|
const values = [
|
||||||
|
syncDataId,
|
||||||
|
commentData.sourceSystem,
|
||||||
|
commentData.sourceCommentId,
|
||||||
|
commentData.content,
|
||||||
|
commentData.author
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
await pool.query(insertQuery, values);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
logError('Erro ao inserir comentário em sync_comments:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Busca comentários pendentes para um sistema de destino específico.
|
||||||
|
* @param {string} destinationSystem - O sistema de destino ('glpi' ou 'hubsoft').
|
||||||
|
* @returns {Promise<Array<object>>} Uma lista de comentários pendentes.
|
||||||
|
*/
|
||||||
|
static async getPendingCommentsForDestination(destinationSystem) {
|
||||||
|
const sourceSystem = destinationSystem === 'glpi' ? 'hubsoft' : 'glpi';
|
||||||
|
const query = `
|
||||||
|
SELECT sc.id, sc.content, sc.author, sd.glpi_ticket_id, sd.hubsoft_ticket_id
|
||||||
|
FROM sync_comments sc
|
||||||
|
JOIN sync_data sd ON sc.sync_data_id = sd.id
|
||||||
|
WHERE sc.source_system = $1
|
||||||
|
AND sc.sync_status = 'pending_sync'
|
||||||
|
AND sc.sync_attempts < 5; -- Evita retentativas infinitas
|
||||||
|
`;
|
||||||
|
const { rows } = await pool.query(query, [sourceSystem]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atualiza o status de sincronização de um comentário.
|
||||||
|
* @param {number} commentId - O ID do comentário em sync_comments.
|
||||||
|
* @param {string} status - O novo status ('synced', 'sync_error').
|
||||||
|
* @param {string|null} destinationId - O ID do comentário no sistema de destino.
|
||||||
|
* @param {string|null} errorMessage - A mensagem de erro, se houver.
|
||||||
|
*/
|
||||||
|
static async updateCommentSyncStatus(commentId, status, destinationId = null, errorMessage = null) {
|
||||||
|
const query = `
|
||||||
|
UPDATE sync_comments
|
||||||
|
SET
|
||||||
|
sync_status = $1,
|
||||||
|
destination_comment_id = $2,
|
||||||
|
error_message = $3,
|
||||||
|
sync_attempts = sync_attempts + 1,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE id = $4;
|
||||||
|
`;
|
||||||
|
try {
|
||||||
|
await pool.query(query, [status, destinationId, errorMessage, commentId]);
|
||||||
|
} catch (error) {
|
||||||
|
logError(`Erro ao atualizar status do comentário ID ${commentId}:`, error);
|
||||||
|
// Não relançamos o erro para não parar o loop de sincronização.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = CommentModel;
|
||||||
0
src/model/commentSyncModel.js
Normal file
0
src/model/commentSyncModel.js
Normal file
@ -123,7 +123,20 @@ class GlpiModel {
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
static async insertComment(commentData) {
|
||||||
|
const query = `
|
||||||
|
INSERT INTO glpi_tickets_comments (tickets_id, content, date_creation)
|
||||||
|
VALUES (?, ?, ?)
|
||||||
|
`;
|
||||||
|
const values = [commentData.tickets_id, commentData.content, new Date()];
|
||||||
|
try {
|
||||||
|
const [rows] = await pool.query(query, values);
|
||||||
|
return rows && rows.insertId ? { id: rows.insertId } : null;
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
logError(`Erro ao inserir comentário no GLPI: ${err}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
module.exports = GlpiModel;
|
||||||
module.exports = GlpiModel;
|
|
||||||
|
|||||||
@ -49,6 +49,27 @@ const validateMensagensByAtendimento = async (id_atendimento) => {
|
|||||||
return rows;
|
return rows;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getNewMessagesFromDB = async (lastFetchTimestamp) => {
|
||||||
|
// Busca novas mensagens e o id_atendimento correspondente
|
||||||
|
const query = `
|
||||||
|
SELECT
|
||||||
|
m.id_atendimento_mensagem,
|
||||||
|
m.id_atendimento,
|
||||||
|
m.mensagem,
|
||||||
|
m.data_cadastro
|
||||||
|
FROM
|
||||||
|
atendimento_mensagem AS m
|
||||||
|
INNER JOIN
|
||||||
|
atendimento AS a ON m.id_atendimento = a.id_atendimento
|
||||||
|
WHERE
|
||||||
|
m.data_cadastro > $1
|
||||||
|
AND a.id_tipo_atendimento = 4
|
||||||
|
AND a.id_usuario_abertura = 248;
|
||||||
|
`;
|
||||||
|
const { rows } = await pool.query(query, [lastFetchTimestamp]);
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
const updateFechaAtendimento = async (id_atendimento, closingMessage) => {
|
const updateFechaAtendimento = async (id_atendimento, closingMessage) => {
|
||||||
const query = `
|
const query = `
|
||||||
UPDATE atendimento
|
UPDATE atendimento
|
||||||
@ -76,5 +97,6 @@ module.exports = {
|
|||||||
getAtendimentosFromDB,
|
getAtendimentosFromDB,
|
||||||
validateAtendimentoStatus,
|
validateAtendimentoStatus,
|
||||||
validateMensagensByAtendimento,
|
validateMensagensByAtendimento,
|
||||||
updateFechaAtendimento // Exportando a função
|
updateFechaAtendimento, // Exportando a função
|
||||||
|
getNewMessagesFromDB
|
||||||
};
|
};
|
||||||
@ -1,8 +1,13 @@
|
|||||||
const { Router } = require('express');
|
const { Router } = require('express');
|
||||||
const ticketController = require('./controller/closureController.js');
|
const closureController = require('./controller/closureController.js');
|
||||||
|
const commentController = require('./controller/commentController'); // Novo
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.post('/close-ticket', ticketController.closeTicket);
|
router.post('/webhook/close-ticket', closureController.closeTicket);
|
||||||
|
router.post('/webhook/new-comment', commentController.handleNewComment); // Nova rota
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
@ -31,14 +31,6 @@ CREATE TABLE hubsoft_tickets (
|
|||||||
-- =============================================
|
-- =============================================
|
||||||
|
|
||||||
CREATE TYPE source_last_enum AS ENUM ('hubsoft', 'glpi');
|
CREATE TYPE source_last_enum AS ENUM ('hubsoft', 'glpi');
|
||||||
CREATE TYPE status_sync_enum AS ENUM (
|
|
||||||
'pending_create',
|
|
||||||
'created_glpi',
|
|
||||||
'pending_close',
|
|
||||||
'closed_glpi',
|
|
||||||
'sync_error',
|
|
||||||
'need_update'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE sync_data (
|
CREATE TABLE sync_data (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
@ -54,8 +46,43 @@ CREATE TABLE sync_data (
|
|||||||
UNIQUE (hubsoft_ticket_id, glpi_ticket_id)
|
UNIQUE (hubsoft_ticket_id, glpi_ticket_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- TABELA: sync_comments
|
||||||
|
-- DESCRIÇÃO: Armazena o estado de comentarios entre HubSoft e GLPI
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
|
||||||
|
CREATE TABLE sync_comments (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
sync_data_id INTEGER NOT NULL REFERENCES sync_data(id),
|
||||||
|
source_system VARCHAR(20) NOT NULL, -- 'hubsoft' ou 'glpi'
|
||||||
|
source_comment_id VARCHAR(255) NOT NULL, -- ID do comentário no sistema de origem
|
||||||
|
destination_comment_id VARCHAR(255), -- ID do comentário no sistema de destino
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
author VARCHAR(255), -- Nome do autor do comentário (opcional, mas útil)
|
||||||
|
sync_status VARCHAR(50) NOT NULL DEFAULT 'pending_sync', -- ex: 'pending_sync', 'synced', 'sync_error'
|
||||||
|
sync_attempts INTEGER DEFAULT 0,
|
||||||
|
error_message TEXT,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Índice para evitar duplicatas e otimizar buscas
|
||||||
|
CREATE UNIQUE INDEX idx_unique_source_comment ON sync_comments(source_system, source_comment_id);
|
||||||
|
CREATE INDEX idx_sync_status ON sync_comments(sync_status, sync_attempts);
|
||||||
|
|
||||||
|
|
||||||
|
-- =============================================
|
||||||
|
-- TABELA: sync_data
|
||||||
|
-- DESCRIÇÃO: Armazena o estado de comentarios entre HubSoft e GLPI
|
||||||
|
-- =============================================
|
||||||
|
|
||||||
|
CREATE TABLE sync_control (
|
||||||
|
job_name VARCHAR(100) PRIMARY KEY,
|
||||||
|
last_run_timestamp TIMESTAMPTZ NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Inserir o registro inicial para o nosso novo cron de comentários
|
||||||
|
INSERT INTO sync_control (job_name, last_run_timestamp) VALUES ('hubsoft_comments_sync', '2024-01-01 00:00:00');
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
115
src/services/commentService.js
Normal file
115
src/services/commentService.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
// src/services/commentService.js
|
||||||
|
const hubsoftModel = require('../model/hubsoftModel.js');
|
||||||
|
const commentModel = require('../model/commentModel.js'); // a ser criado
|
||||||
|
const glpiModel = require('../model/glpiModel.js');
|
||||||
|
const hubglpiModel = require('../model/hubglpiModel.js');
|
||||||
|
const hubsoftService = require('./hubsoftService.js');
|
||||||
|
const { sanitizeGLPIComment } = require('../utils/commentSanitizer.js');
|
||||||
|
const { logInfo, logError, logWarning } = require('../utils/logger');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Busca novos comentários no HubSoft e os salva no banco intermediário.
|
||||||
|
*/
|
||||||
|
async function syncHubsoftCommentsToLocalDB() {
|
||||||
|
logInfo('Iniciando sincronização de comentários do HubSoft...');
|
||||||
|
const lastRun = await commentModel.getLastRunTimestamp('hubsoft_comments_sync');
|
||||||
|
|
||||||
|
const newMessages = await hubsoftModel.getNewMessagesFromDB(lastRun);
|
||||||
|
if (newMessages.length === 0) {
|
||||||
|
logInfo('Nenhum comentário novo encontrado no HubSoft.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo(`Encontrados ${newMessages.length} novos comentários no HubSoft.`);
|
||||||
|
|
||||||
|
for (const message of newMessages) {
|
||||||
|
try {
|
||||||
|
// A função insertComment deve ter uma cláusula ON CONFLICT para não duplicar
|
||||||
|
const commentInserted = await commentModel.insertComment({
|
||||||
|
hubsoftAtendimentoId: message.id_atendimento,
|
||||||
|
sourceSystem: 'hubsoft',
|
||||||
|
sourceCommentId: message.id_atendimento_mensagem,
|
||||||
|
content: message.mensagem,
|
||||||
|
// author: ... se disponível
|
||||||
|
});
|
||||||
|
if (commentInserted){
|
||||||
|
logInfo(`Comentário HubSoft ID ${message.id_atendimento_mensagem} salvo para sincronização.`);
|
||||||
|
}else{
|
||||||
|
logInfo(`Chamado HubSoft ID ${message.id_atendimento} não possui registro de sincronização. Comentário ignorado.`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Se o erro for de violação de chave única, apenas ignoramos.
|
||||||
|
if (error.code !== '23505') { // Código de erro do PostgreSQL para unique_violation
|
||||||
|
logError(`Erro ao salvar comentário HubSoft ID ${message.id_atendimento_mensagem}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atualiza o timestamp da última execução
|
||||||
|
await commentModel.updateLastRunTimestamp('hubsoft_comments_sync', new Date());
|
||||||
|
logInfo('Sincronização de comentários do HubSoft para o banco local concluída.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envia comentários pendentes do banco local para o GLPI.
|
||||||
|
*/
|
||||||
|
async function sendPendingCommentsToGlpi() {
|
||||||
|
// Busca comentários com origem 'hubsoft' que estão pendentes de envio para o GLPI
|
||||||
|
const pendingComments = await commentModel.getPendingCommentsForDestination('glpi');
|
||||||
|
|
||||||
|
if (pendingComments.length === 0) {
|
||||||
|
return; // Nenhum comentário para processar
|
||||||
|
}
|
||||||
|
|
||||||
|
logInfo(`Encontrados ${pendingComments.length} comentários pendentes para envio ao GLPI.`);
|
||||||
|
|
||||||
|
for (const comment of pendingComments) {
|
||||||
|
try {
|
||||||
|
// 1. Inserir o comentário diretamente no banco de dados do GLPI
|
||||||
|
const newGlpiComment = await glpiModel.insertComment({
|
||||||
|
tickets_id: comment.glpi_ticket_id, // ID do ticket no GLPI
|
||||||
|
content: comment.content,
|
||||||
|
// Outros campos necessários, como autor, data, etc.
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sucesso: atualiza o status no nosso banco
|
||||||
|
await commentModel.updateCommentSyncStatus(comment.id, 'synced', newGlpiComment.id);
|
||||||
|
logInfo(`Comentário ID ${comment.id} sincronizado com sucesso para o GLPI. Novo ID de comentário no GLPI: ${newGlpiComment.id}`);
|
||||||
|
} catch (error) {
|
||||||
|
// Falha: atualiza o status para erro e incrementa a tentativa no nosso banco
|
||||||
|
await commentModel.updateCommentSyncStatus(comment.id, 'sync_error', null, error.message);
|
||||||
|
logError(`Falha ao sincronizar comentário ID ${comment.id} para o GLPI:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recebe um novo comentário do GLPI (via webhook) e o envia para o HubSoft.
|
||||||
|
* @param {number} glpiTicketId - O ID do ticket no GLPI.
|
||||||
|
* @param {string} rawContent - O conteúdo bruto do comentário.
|
||||||
|
*/
|
||||||
|
async function syncGlpiCommentToHubsoft(glpiTicketId, rawContent) {
|
||||||
|
// 1. Encontrar o registro de sincronização para obter o ID do HubSoft
|
||||||
|
const syncRecord = await hubglpiModel.getIdByGlpiID(glpiTicketId);
|
||||||
|
|
||||||
|
if (!syncRecord || !syncRecord.hubsoftId) {
|
||||||
|
logWarning(`Recebido comentário para o ticket GLPI ID ${glpiTicketId}, mas não há registro de sincronização correspondente. Ignorando.`);
|
||||||
|
// Não lançamos um erro, pois o comentário pode ser de um ticket não sincronizado.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hubsoftTicketId = syncRecord.hubsoftId;
|
||||||
|
|
||||||
|
// 2. Sanitizar o comentário (remover HTML, etc.)
|
||||||
|
const sanitizedContent = sanitizeGLPIComment({ content: rawContent });
|
||||||
|
|
||||||
|
// 3. Enviar o comentário para a API do HubSoft
|
||||||
|
logInfo(`Enviando comentário do GLPI Ticket ${glpiTicketId} para o HubSoft Atendimento ${hubsoftTicketId}.`);
|
||||||
|
await hubsoftService.addMensagem(hubsoftTicketId, sanitizedContent);
|
||||||
|
|
||||||
|
// Opcional: Salvar o comentário no banco intermediário (sync_comments) para ter um log.
|
||||||
|
// Isso pode ser útil para auditoria, mas não é estritamente necessário para o envio.
|
||||||
|
logInfo(`Comentário do GLPI Ticket ${glpiTicketId} enviado com sucesso para o HubSoft.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { syncHubsoftCommentsToLocalDB, sendPendingCommentsToGlpi, syncGlpiCommentToHubsoft };
|
||||||
@ -77,7 +77,39 @@ const closeAtendimento = async (id_atendimento, closingMessage) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adiciona uma nova mensagem a um atendimento no HubSoft.
|
||||||
|
* @param {number} id_atendimento - O ID do atendimento no HubSoft.
|
||||||
|
* @param {string} mensagem - O conteúdo da mensagem a ser adicionada.
|
||||||
|
* @returns {Promise<object>} A resposta da API do HubSoft.
|
||||||
|
*/
|
||||||
|
async function addMensagem(id_atendimento, mensagem) {
|
||||||
|
// 1. Obter o token de autenticação
|
||||||
|
const token = await getAuthToken();
|
||||||
|
|
||||||
|
// 2. Construir a URL completa do endpoint
|
||||||
|
const url = `${apiConfig.hubsoft.baseUrl}/integracao/atendimento/adicionar_mensagem/${id_atendimento}`;
|
||||||
|
const payload = { mensagem };
|
||||||
|
|
||||||
|
try {
|
||||||
|
logInfo(`Enviando nova mensagem para o atendimento HubSoft ID ${id_atendimento}...`);
|
||||||
|
// 3. Usar a instância 'axios' e passar o token no header
|
||||||
|
const response = await axios.post(url, payload, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
logInfo(`Resposta da API HubSoft para adição de mensagem: ${JSON.stringify(response.data)}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
logError(`Erro ao adicionar mensagem no atendimento HubSoft ID ${id_atendimento}:`, error.response?.data || error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
updateAtendimentoStatus,
|
updateAtendimentoStatus,
|
||||||
closeAtendimento
|
closeAtendimento,
|
||||||
|
addMensagem
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user