This commit is contained in:
Rafael Lopes 2025-10-13 06:06:52 -03:00
parent 3b6d78152d
commit 4f7a699f92
10 changed files with 186 additions and 68 deletions

View File

@ -1,6 +1,6 @@
// src/app.js
const { snServices } = require('./controllers/servicenowController');
const { glpiServices } = require('./controllers/glpiController');
const { processTicketsController } = require('./controllers/processTicketsController');
const { processCommentsController } = require('./controllers/processCommentsController');
const { logInfo } = require('./utils/logger');
logInfo('Aplicação iniciada', {
@ -10,8 +10,8 @@ logInfo('Aplicação iniciada', {
async function main() {
try {
await snServices();
await glpiServices();
await processTicketsController();
await processCommentsController();
} catch (error) {
logInfo('Erro na aplicação:', error);
}

View File

@ -1,12 +0,0 @@
// src/controllers/glpiController.js
const { syncTicketsToGlpi } = require('../services/glpiTicketService');
const { syncCommentsGlpitoSN } = require('../services/glpiCommentService');
const glpiServices = async () => {
//await syncTicketsToGlpi();
await syncCommentsGlpitoSN();
}
module.exports = {
glpiServices
}

View File

@ -0,0 +1,12 @@
const {fetchCommentsFromServiceNow } = require('../services/servicenowService');
const { syncCommentsGlpitoSN } = require('../services/glpiCommentService');
const processCommentsController = async () => {
await fetchCommentsFromServiceNow();
await syncCommentsGlpitoSN();
}
module.exports = {
processCommentsController
}

View File

@ -0,0 +1,19 @@
// src/controllers/glpiController.js
const { syncTicketsToGlpi } = require('../services/glpiTicketService');
const { fetchTicketsFromServiceNow, fetchRequestsFromServiceNow } = require('../services/servicenowService');
const processTicketsController = async () => {
//Busca Incidentes e Requisições do Service Now e salva no banco intermediario
await fetchTicketsFromServiceNow();
await fetchRequestsFromServiceNow();
//Sincroniza os tickets com o GLPI
await syncTicketsToGlpi();
}
module.exports = {
processTicketsController
}

View File

@ -1,11 +0,0 @@
const { fetchTicketsFromServiceNow, fetchRequestsFromServiceNow, fetchCommentsFromServiceNow } = require('../services/servicenowService');
const snServices = async () => {
//await fetchTicketsFromServiceNow();
//await fetchRequestsFromServiceNow();
await fetchCommentsFromServiceNow(626)
}
module.exports = {
snServices
}

View File

@ -252,6 +252,7 @@ class TicketGlpiModel {
SELECT id FROM glpi_tickets
WHERE name LIKE ? OR content LIKE ?
LIMIT 1
`;
const [rows] = await glpiPool.execute(query, [

View File

@ -24,8 +24,7 @@ class TicketSnModel {
ramal = EXCLUDED.ramal,
telefone = EXCLUDED.telefone,
opened_at = EXCLUDED.opened_at,
tipo = EXCLUDED.tipo,
updated_at = NOW()
tipo = EXCLUDED.tipo
RETURNING id, xmax = 0 as inserted`;
const values = [
@ -92,8 +91,7 @@ class TicketSnModel {
ts.updated_at
FROM tickets_sn ts
LEFT JOIN ticket_sync tsync ON ts.id = tsync.sn_ticket_id
WHERE tsync.id IS NULL
AND ts.status NOT IN ('Encerrado', 'Encerrado - Omitido')
WHERE tsync.glpi_sync_status = 'pending_check'
ORDER BY ts.opened_at
`;

View File

@ -1,9 +1,33 @@
// src/models/ticketSyncModel.js
const { query } = require('winston');
const pool = require('../data/database');
const { logError } = require('../utils/logger');
class TicketSyncModel {
// Cria um registro de sincronização para um ticket
static async createSyncRecord(data) {
try {
const query = `INSERT INTO ticket_sync (
sn_ticket_id,
glpi_ticket_id,
last_sn_sync,
last_glpi_sync,
created_at,
updated_at,
sn_sync_status,
glpi_sync_status
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id`;
await pool.query(query, data);
} catch (error) {
logError(error, '❌ Erro ao criar registro de sincronização');
throw error;
}
}
// Buscar tickets que foram criados com sucesso para sincronizar comentários
static async getTicketsToSync() {
try {
@ -51,7 +75,15 @@ class TicketSyncModel {
throw error;
}
}
static async getByTicketNumber(ticketNumber) {
try {
const query = 'SELECT * FROM ticket_sync WHERE ticket_number = $1';
} catch (error) {
}
}
}

View File

@ -8,7 +8,6 @@ const { logInfo, logError } = require('../utils/logger');
const syncTicketsToGlpi = async () => {
try {
logInfo('🔄 Iniciando sincronização para GLPI...');
// 1. Buscar tickets pendentes
const pendingTickets = await TicketSnModel.getPendingTickets();
@ -18,7 +17,7 @@ const syncTicketsToGlpi = async () => {
}
logInfo(`📋 Encontrados ${pendingTickets.length} tickets para sincronizar`);
// 2. Processar cada ticket
for (const ticket of pendingTickets) {
await processSingleTicket(ticket);
@ -34,21 +33,32 @@ const syncTicketsToGlpi = async () => {
// Processar um ticket individual
const processSingleTicket = async (ticket) => {
let syncRecord = null;
try {
logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`);
// 1. Verificar se já existe no GLPI (evitar duplicatas)
// 1. Verificar se já existe no GLPI
const existingTicket = await TicketGlpiModel.ticketExists(ticket.ticket_number);
if (existingTicket) {
logInfo(`⏭️ Ticket já existe no GLPI: ${existingTicket}`);
// Criar registro de sincronização indicando que já existe
syncRecord = await TicketSyncModel.createSyncRecord(ticket.id, existingTicket);
await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'duplicate', existingTicket);
const idSyncRecord = await TicketSyncModel.getByTicketNumber(ticket.ticket_number);
if (existingTicket && idSyncRecord) {
await TicketSyncModel.updateGlpiSyncStatus(idSyncRecord.id, 'synced', existingTicket);
await TicketSyncModel.updateSnSyncStatus(idSyncRecord.id, 'synced');
return;
}else if (existingTicket && !idSyncRecord) {
await TicketSyncModel.createSyncRecord({
sn_ticket_id: ticket.id,
glpi_ticket_id: existingTicket,
sn_sync_status: 'synced',
glpi_sync_status: 'synced',
last_glpi_sync: new Date()
});
return;
}
// 2. Converter dados para formato GLPI
const glpiData = {
ticket_number: ticket.ticket_number,
@ -71,8 +81,7 @@ const processSingleTicket = async (ticket) => {
const glpiTicketId = await TicketGlpiModel.createTicket(glpiData);
// 4. Criar registro de sincronização
syncRecord = await TicketSyncModel.createSyncRecord(ticket.id, glpiTicketId);
await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'completed', glpiTicketId);
await TicketSyncModel.updateGlpiSyncStatus(idSyncRecord.id, 'synced', glpiTicketId);
logInfo(`✅ Ticket ${ticket.ticket_number} sincronizado! GLPI ID: ${glpiTicketId}`);
@ -81,12 +90,12 @@ const processSingleTicket = async (ticket) => {
// Se temos um registro de sync, atualizar o status para error
if (syncRecord) {
await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'error');
await TicketSyncModel.updateGlpiSyncStatus(idSyncRecord.id, 'error');
} else {
// Se não temos um registro, criar um com status error
try {
syncRecord = await TicketSyncModel.createSyncRecord(ticket.id);
await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'error');
await TicketSyncModel.updateGlpiSyncStatus(idSyncRecord.id, 'error');
} catch (innerError) {
logError(innerError, `❌ Erro ao registrar falha de sincronização para ticket ${ticket.id}`);
}

View File

@ -14,15 +14,40 @@ const fetchTicketsFromServiceNow = async () => {
const ticketsResponse = await axios.get(apiConfig.snIncidentConfig.baseUrl, {
auth: apiConfig.servicenowAuthentication.auth
});
// comentado para evitar muitos arquivos
//fs.writeFileSync('servicenow_tickets.json', JSON.stringify(ticketsResponse.data, null, 2));
//logInfo('💾 Backup JSON de incidentes salvo', { count: ticketsResponse.data.result.length });
// Salvar os tickets no banco de dados
logInfo('💾 Iniciando salvamento de incidentes no banco...');
for (const ticket of ticketsResponse.data.result) {
await TicketSnModel.saveTicket(ticket, 'incidente');
//Insere no banco de dados como Incidente
const idTableTicketSn =await TicketSnModel.saveTicket(ticket, 'incidente');
//Salvando atualização no banco intermediario e defininfo sync status
const ticketStatus = ticket.state?.display_value;
const dataSync = {
glpi_ticket_id: null,
sn_ticket_id: idTableTicketSn,
last_sn_sync: new Date(),
last_glpi_sync: null,
created_at: null,
updated_at: null
}
if (ticketStatus === 'Encerrado' || ticketStatus === 'Encerrado - Omitido') {
dataSync.sn_sync_status ='closed'
dataSync.glpi_sync_status = 'ignored'
} else {
dataSync.sn_sync_status ='synced'
dataSync.glpi_sync_status = 'pending_check'
}
await TicketSyncModel.createSyncRecord(dataSync);
}
logSync('ServiceNow', ticketsResponse.data.result.length, 'incidentes');
@ -40,18 +65,38 @@ const fetchRequestsFromServiceNow = async () => {
auth: apiConfig.servicenowAuthentication.auth
});
// Comentado para evitar muitos arquivos
//fs.writeFileSync('servicenow_requests.json', JSON.stringify(requestsResponse.data, null, 2));
//logInfo('💾 Backup JSON de requisições salvo', { count: requestsResponse.data.result.length });
// Salvar as requisições no banco de dados
logInfo('💾 Iniciando salvamento de requisições no banco...');
for (const request of requestsResponse.data.result) {
await TicketSnModel.saveTicket(request, 'requisicao');
//Salvando a requisicao no banco intermediario
const idTableTicketSn = await TicketSnModel.saveTicket(request, 'requisicao');
const dataSync = {
glpi_ticket_id: null,
sn_ticket_id: idTableTicketSn,
last_sn_sync: new Date(),
last_glpi_sync: null,
created_at: null,
updated_at: null
}
if (ticketStatus === 'Encerrado' || ticketStatus === 'Encerrado - Omitido') {
dataSync.sn_sync_status ='closed'
dataSync.glpi_sync_status = 'ignored'
} else {
dataSync.sn_sync_status ='collected'
dataSync.glpi_sync_status = 'pending_check'
}
//Cria apenas se não existir
const exists = await TicketSyncModel.getIdSyncBySnId(idTableTicketSn);
if (!exists) await TicketSyncModel.createSyncRecord(dataSync);
}
logSync('ServiceNow', requestsResponse.data.result.length, 'requisições');
} catch (error) {
logError(error, 'SERVICE NOW Requests');
}
@ -204,11 +249,21 @@ const syncCommentToServiceNow = async (ticketId, comment) => {
// Deve-se atentar para a paginação da API do ServiceNow
// para fins de teste, gerar um arquivo JSON com os comentários de um ticket específico
const fetchCommentsFromServiceNow = async (ticketId) => {
const fetchCommentsFromServiceNow = async () => {
try {
const ticketsSyncList = await TicketSyncModel.getTicketsToSync();
console.log(`Iniciando busca de comentários do ServiceNow para o ticket SN Ticket: ${ticketId}`);
//Coleta o sys id no bando intermediario para busca no service now
const sys_id = await getTicketSysId(ticketId);
//Se não encontrar é impossível atualizar
if (!sys_id) {
logError('sys_id não encontrado para ticket', { ticketId });
return [];
@ -237,6 +292,7 @@ const fetchCommentsFromServiceNow = async (ticketId) => {
const comments = response?.data?.result || [];
if (!Array.isArray(comments) || comments.length === 0) {
break; // não há mais comentários
}
@ -264,30 +320,31 @@ const fetchCommentsFromServiceNow = async (ticketId) => {
// Salva no arquivo
fs.writeFileSync(filename, JSON.stringify(allComments, null, 2));
//Trata os dados para inserir no banco intermediario apenas os tickets que não são do sys_created_by: "SOTHIS.CAOA"
//Trata os dados para inserir no banco intermediario a
// as os tickets que não são do sys_created_by: "SOTHIS.CAOA"
const filteredComments = allComments.filter(comment => comment.sys_created_by !== 'SOTHIS.CAOA');
// Insere cada comment no banco intermediario
for (const comment of filteredComments) {
const existingComment = await TicketUpdateModel.getCommentSync(ticketId, comment.sys_id);
const existingComment = await TicketUpdateModel.getCommentSync(ticketId, comment.sys_id);
if (existingComment) {
logInfo(`Comentário: ${comment.sys_id} já existe no banco de dados`, { step: 3 });
continue;
} else {
console.log(comment);
const ticketSnId = await TicketSyncModel.getIdSyncBySnId(ticketId);
const ticketSyncId = await TicketSyncModel.getIdSyncBySnId(ticketId);
const commentData = {
ticket_sync_id: parseInt(ticketSnId),
ticket_sync_id: ticketSyncId,
source_system: 'servicenow',
content: comment.value,
created_at: new Date(comment.sys_created_on),
@ -296,12 +353,25 @@ const fetchCommentsFromServiceNow = async (ticketId) => {
source_id: comment.sys_id,
destiny_id: null
};
commentInserted = await TicketUpdateModel.insert(commentData);
if (!ticketSyncId) {
logError(`❌ TicketSyncId não encontrado para o ticket SN ID: ${ticketId}`, { step: 4 });
continue;
}
const commentInserted = await TicketUpdateModel.insert(commentData);
if (commentInserted.success) {
// Insere Glpi status como pending e SN status como synced
await TicketSyncModel.updateStatus(ticketGlpiId.id, 'pending', 'synced');
// Insere Glpi status como
// ding e SN status como synced
await TicketSyncModel.updateStatus(ticketGlpiId.id, '
ding', 'synced');
// Atualiza o status para indicar que o SN está sincronizado e o GLPI está
// dente de atualização
await TicketSyncModel.updateStatus(ticketSyncId, '
ding', 'synced');
logInfo(`✅ Comentário ID ${comment.sys_id} inserido no banco! Novo ID: ${commentInserted.newId}`, { step: 4 });
} else {
logError(`❌ Erro ao inserir comentário ID ${comment.sys_id} no banco: ${commentInserted.error}`, { step: 4 });