2025-09-15 20:55:19 -03:00
|
|
|
const TicketSnModel = require('../models/ticketSnModel');
|
|
|
|
|
const TicketGlpiModel = require('../models/ticketGlpiModel');
|
2025-10-23 17:30:03 -03:00
|
|
|
const TicketUpdateModel = require('../models/ticketUpdateModel');
|
2025-09-17 20:56:32 -03:00
|
|
|
const TicketSyncModel = require('../models/ticketSyncModel');
|
2026-02-19 18:22:38 -03:00
|
|
|
const { logInfo, logError } = require('../utils/logger');
|
2025-10-23 17:30:03 -03:00
|
|
|
const { addWorkNoteToServiceNow } = require('./servicenowService');
|
|
|
|
|
|
2025-09-15 20:55:19 -03:00
|
|
|
const syncTicketsToGlpi = async () => {
|
|
|
|
|
try {
|
|
|
|
|
logInfo('🔄 Iniciando sincronização para GLPI...');
|
|
|
|
|
const pendingTickets = await TicketSnModel.getPendingTickets();
|
|
|
|
|
|
|
|
|
|
if (pendingTickets.length === 0) {
|
|
|
|
|
logInfo('✅ Nenhum ticket pendente para sincronizar');
|
|
|
|
|
return;
|
2025-09-11 20:57:43 -03:00
|
|
|
}
|
2025-09-15 20:55:19 -03:00
|
|
|
|
|
|
|
|
logInfo(`📋 Encontrados ${pendingTickets.length} tickets para sincronizar`);
|
2025-10-13 06:06:52 -03:00
|
|
|
|
2025-09-15 20:55:19 -03:00
|
|
|
for (const ticket of pendingTickets) {
|
|
|
|
|
await processSingleTicket(ticket);
|
2025-09-11 20:57:43 -03:00
|
|
|
}
|
2025-09-15 20:55:19 -03:00
|
|
|
|
|
|
|
|
logInfo('🎉 Sincronização com GLPI concluída!');
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logError(error, '❌ Erro na sincronização GLPI');
|
|
|
|
|
throw error;
|
2025-08-30 19:04:35 -03:00
|
|
|
}
|
2025-09-15 20:55:19 -03:00
|
|
|
};
|
2025-09-11 20:57:43 -03:00
|
|
|
|
2025-09-15 20:55:19 -03:00
|
|
|
const processSingleTicket = async (ticket) => {
|
2025-09-17 20:56:32 -03:00
|
|
|
|
2025-09-15 20:55:19 -03:00
|
|
|
try {
|
|
|
|
|
logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`);
|
|
|
|
|
|
2025-10-17 16:58:14 -03:00
|
|
|
if (ticket.glpi_ticket_id) {
|
|
|
|
|
logInfo(`✅ Ticket ${ticket.ticket_number} já possui GLPI ID (${ticket.glpi_ticket_id}) registrado. Apenas atualizando status.`);
|
|
|
|
|
await TicketSyncModel.updateStatus(ticket.id, 'synced', 'synced');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-10-13 06:06:52 -03:00
|
|
|
|
2025-10-17 16:58:14 -03:00
|
|
|
const existingTicket = await TicketGlpiModel.ticketExists(ticket.ticket_number);
|
2025-10-29 14:20:21 -03:00
|
|
|
await TicketSyncModel.updateLastSync(ticket.id, 'glpi');
|
2025-10-13 06:06:52 -03:00
|
|
|
|
2025-10-17 16:58:14 -03:00
|
|
|
if (existingTicket) {
|
|
|
|
|
logInfo(`✅ Ticket ${ticket.ticket_number} encontrado no GLPI. Atualizando registro local com o ID: ${existingTicket}`);
|
2025-10-28 14:06:00 -03:00
|
|
|
|
|
|
|
|
const glpiLiveStatus = await TicketGlpiModel.getTicketStatus(existingTicket);
|
2025-10-29 14:20:21 -03:00
|
|
|
if (glpiLiveStatus === 6) {
|
2025-10-28 14:06:00 -03:00
|
|
|
logInfo(`Ticket GLPI ${existingTicket} já está Fechado. Marcando ticket SN ${ticket.ticket_number} como 'closed'/'ignored' e não monitorado.`);
|
|
|
|
|
await TicketSyncModel.updateStatusAndGlpiTicket(ticket.id, 'ignored', 'closed', existingTicket);
|
2025-10-29 14:20:21 -03:00
|
|
|
return;
|
2025-10-28 14:06:00 -03:00
|
|
|
}
|
2025-10-17 16:58:14 -03:00
|
|
|
await TicketSyncModel.updateStatusAndGlpiTicket(ticket.id, 'synced', 'synced', existingTicket);
|
|
|
|
|
logInfo(`✅ Ticket ${ticket.ticket_number} já existe no GLPI! GLPI ID: ${existingTicket}`);
|
2025-10-29 14:20:21 -03:00
|
|
|
return;
|
2025-09-15 20:55:19 -03:00
|
|
|
}
|
2025-09-17 20:56:32 -03:00
|
|
|
|
2025-09-15 20:55:19 -03:00
|
|
|
const glpiData = {
|
|
|
|
|
ticket_number: ticket.ticket_number,
|
|
|
|
|
short_description: ticket.short_description,
|
|
|
|
|
description: ticket.description,
|
2026-02-19 18:22:38 -03:00
|
|
|
justificativa: ticket.justificativa,
|
2025-09-17 20:56:32 -03:00
|
|
|
caller_id: ticket.caller_id,
|
2025-09-15 20:55:19 -03:00
|
|
|
caller_email: ticket.caller_email,
|
|
|
|
|
caller_phone: ticket.telefone,
|
|
|
|
|
caller_ramal: ticket.ramal,
|
2025-09-17 20:56:32 -03:00
|
|
|
type: ticket.tipo,
|
|
|
|
|
state: ticket.status,
|
|
|
|
|
priority: '3',
|
|
|
|
|
location_id: ticket.location_id,
|
2025-09-15 20:55:19 -03:00
|
|
|
location_name: ticket.location_name,
|
|
|
|
|
opened_at: ticket.opened_at,
|
|
|
|
|
updated_at: ticket.updated_at
|
|
|
|
|
};
|
|
|
|
|
const glpiTicketId = await TicketGlpiModel.createTicket(glpiData);
|
2025-10-29 14:20:21 -03:00
|
|
|
await TicketSyncModel.updateLastSync(ticket.id, 'glpi');
|
2025-10-23 17:30:03 -03:00
|
|
|
await TicketSyncModel.updateStatusAndGlpiTicket(ticket.id, 'synced', 'synced', glpiTicketId);
|
2025-09-15 20:55:19 -03:00
|
|
|
|
|
|
|
|
logInfo(`✅ Ticket ${ticket.ticket_number} sincronizado! GLPI ID: ${glpiTicketId}`);
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logError(error, `❌ Erro no ticket ${ticket.ticket_number}`);
|
2025-10-17 16:58:14 -03:00
|
|
|
try {
|
2025-10-23 17:30:03 -03:00
|
|
|
await TicketSyncModel.updateStatus(ticket.id, 'error', 'collected');
|
2025-10-17 16:58:14 -03:00
|
|
|
} catch (innerError) {
|
2025-09-17 20:56:32 -03:00
|
|
|
logError(innerError, `❌ Erro ao registrar falha de sincronização para ticket ${ticket.id}`);
|
|
|
|
|
}
|
2025-09-15 20:55:19 -03:00
|
|
|
}
|
2025-08-30 19:04:35 -03:00
|
|
|
};
|
|
|
|
|
|
2025-10-23 17:30:03 -03:00
|
|
|
const checkGlpiStatusChanges = async () => {
|
|
|
|
|
try {
|
|
|
|
|
logInfo('📡 Verificando mudanças de status nos tickets do GLPI...');
|
|
|
|
|
|
|
|
|
|
const ticketsToMonitor = await TicketSyncModel.getTicketsToMonitor();
|
|
|
|
|
|
|
|
|
|
if (ticketsToMonitor.length === 0) {
|
|
|
|
|
logInfo('Nenhum ticket ativo para verificar status no GLPI.');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (const ticket of ticketsToMonitor) {
|
|
|
|
|
const glpiStatus = await TicketGlpiModel.getTicketStatus(ticket.glpi_ticket_id);
|
|
|
|
|
|
|
|
|
|
const currentDbStatus = ticket.glpi_sync_status;
|
2025-10-29 14:20:21 -03:00
|
|
|
const glpiStatusAsString = TicketGlpiModel.mapGlpiStatusToString(glpiStatus).toLowerCase().replace(/ /g, '_');
|
2025-10-23 17:30:03 -03:00
|
|
|
|
|
|
|
|
if (glpiStatus === 5 && currentDbStatus !== 'solved' && currentDbStatus !== 'closed') {
|
|
|
|
|
logInfo(`Ticket GLPI ${ticket.glpi_ticket_id} foi Solucionado. Preparando para sincronização...`);
|
|
|
|
|
|
|
|
|
|
const solution = await TicketGlpiModel.getTicketSolution(ticket.glpi_ticket_id);
|
|
|
|
|
|
|
|
|
|
if (solution?.solutiontypes_id === 27) {
|
|
|
|
|
logInfo(`REGRA DETECTADA: Ticket ${ticket.glpi_ticket_id} está fora do escopo (SolutionType ID: 27).`);
|
|
|
|
|
const justification = solution?.content || 'Ticket marcado como "Fora do escopo" no GLPI.';
|
|
|
|
|
await addWorkNoteToServiceNow(ticket.sn_ticket_id, `Ticket fechado no GLPI com a justificativa: ${justification}`);
|
|
|
|
|
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'closed', 'ignored');
|
|
|
|
|
await TicketGlpiModel.forceCloseTicket(ticket.glpi_ticket_id);
|
2025-10-29 14:20:21 -03:00
|
|
|
continue;
|
2025-10-23 17:30:03 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sanitizedContent = solution?.content ? stripHTML(solution.content) : 'Chamado solucionado sem notas.';
|
|
|
|
|
await TicketUpdateModel.insert({
|
|
|
|
|
ticket_sync_id: ticket.id,
|
|
|
|
|
source_system: 'glpi',
|
|
|
|
|
update_type: 'close_message',
|
|
|
|
|
content: sanitizedContent,
|
|
|
|
|
author: null,
|
|
|
|
|
created_at: solution?.date_mod || new Date(),
|
|
|
|
|
source_id: `solution-${ticket.glpi_ticket_id}`,
|
|
|
|
|
});
|
|
|
|
|
await TicketSyncModel.updateStatusAndSourceLast(ticket.sn_ticket_id, 'solved', 'pending_solve', 'GLPI');
|
|
|
|
|
|
|
|
|
|
} else if (glpiStatus === 6 && currentDbStatus !== 'closed') {
|
|
|
|
|
logInfo(`Ticket GLPI ${ticket.glpi_ticket_id} foi Fechado permanentemente. Marcando como 'closed'.`);
|
|
|
|
|
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'closed', ticket.sn_sync_status);
|
|
|
|
|
|
|
|
|
|
} else if (glpiStatus < 5 && glpiStatusAsString !== currentDbStatus && ticket.source_last !== 'GLPI') {
|
|
|
|
|
logInfo(`Mudança de status (aberto) detectada no GLPI para o ticket ${ticket.glpi_ticket_id}. De '${currentDbStatus}' para '${glpiStatusAsString}'.`);
|
|
|
|
|
await TicketSyncModel.updateSourceLast(ticket.sn_ticket_id, 'GLPI');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logError(error, '🚨Falha ao verificar mudanças de status no GLPI');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const closeTicketInGlpi = async (ticket) => {
|
|
|
|
|
try {
|
|
|
|
|
logInfo(`🔒 Iniciando fechamento do chamado GLPI ${ticket.glpi_ticket_id}...`);
|
|
|
|
|
|
|
|
|
|
const closeMessageRecord = await TicketUpdateModel.getLatestUpdate(ticket.id, 'servicenow');
|
|
|
|
|
const solutionContent = closeMessageRecord?.content || 'Chamado encerrado via integração com o ServiceNow.';
|
|
|
|
|
|
|
|
|
|
await TicketGlpiModel.closeTicket(ticket.glpi_ticket_id, solutionContent);
|
2025-10-29 14:20:21 -03:00
|
|
|
await TicketSyncModel.updateLastSync(ticket.sn_ticket_id, 'glpi');
|
2025-10-23 17:30:03 -03:00
|
|
|
|
|
|
|
|
logInfo(`✅ Chamado ${ticket.glpi_ticket_id} solucionado com sucesso no GLPI.`);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logError(error, `🚨Falha ao fechar chamado no GLPI: ${ticket.glpi_ticket_id}`);
|
2025-10-29 14:20:21 -03:00
|
|
|
throw error;
|
2025-10-23 17:30:03 -03:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-08-30 19:04:35 -03:00
|
|
|
module.exports = {
|
2025-10-23 17:30:03 -03:00
|
|
|
syncTicketsToGlpi,
|
|
|
|
|
checkGlpiStatusChanges,
|
|
|
|
|
closeTicketInGlpi
|
2025-10-29 14:20:21 -03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @module glpiTicketService
|
|
|
|
|
* @description Este serviço contém a lógica de negócio para a criação e atualização de tickets no GLPI.
|
|
|
|
|
* Ele atua como um intermediário entre os controladores e os modelos de dados.
|
|
|
|
|
*
|
|
|
|
|
* Principais Funcionalidades:
|
|
|
|
|
* - `syncTicketsToGlpi()`: Função principal que busca tickets pendentes no banco de dados local (previamente coletados do ServiceNow) e orquestra sua criação no GLPI.
|
|
|
|
|
* - `processSingleTicket(ticket)`: Processa um único ticket. Verifica se ele já existe no GLPI para evitar duplicatas. Se não existir, formata os dados (incluindo o enriquecimento da descrição para requisições) e chama o `TicketGlpiModel` para criar o ticket. Se já existir, apenas atualiza o registro de sincronização.
|
|
|
|
|
* - `checkGlpiStatusChanges()`: (Atualmente não utilizado no fluxo principal, sua lógica foi integrada ao `processStatusController`) Função projetada para detectar proativamente mudanças de status no GLPI.
|
|
|
|
|
* - `closeTicketInGlpi(ticket)`: Orquestra o processo de fechamento de um ticket no GLPI. Ele busca a nota de resolução vinda do ServiceNow e a utiliza para adicionar uma solução no GLPI antes de mudar o status para "Solucionado".
|
2026-02-19 18:22:38 -03:00
|
|
|
*/
|