FEATURE/FIX: Ajusta encerramento SN->GLPI e direção do bastão da verdade

- Adiciona comentário estilizado no GLPI quando SN marca Resolvido, Encerrado ou Encerrado - Omitido
- Marca Sync como "closed" para remover o chamado da fila de sincronização
- Corrige a direção do bastão da verdade para preservar SNOW -> GLPI quando SN foi a última origem
This commit is contained in:
Rafael Alves Lopes 2026-02-23 11:39:09 -03:00
parent a4f6797c8f
commit eda8f558fd
2 changed files with 54 additions and 15 deletions

View File

@ -4,7 +4,7 @@ const TicketGlpiModel = require('../models/ticketGlpiModel');
const { logInfo, logError, logWarning } = require('../utils/logger'); const { logInfo, logError, logWarning } = require('../utils/logger');
const { stripHTML } = require('../utils/commentSanitizer'); const { stripHTML } = require('../utils/commentSanitizer');
const { closeTicketInServiceNow, updateStatusInServiceNow, addWorkNoteToServiceNow } = require('../services/servicenowService'); const { closeTicketInServiceNow, updateStatusInServiceNow, addWorkNoteToServiceNow } = require('../services/servicenowService');
const { closeTicketInGlpi } = require('../services/glpiTicketService'); const { addServiceNowClosureNoteToGlpi } = require('../services/glpiTicketService');
const processStatusAndClosureController = async () => { const processStatusAndClosureController = async () => {
logInfo('Iniciando processamento de status e fechamento de tickets...'); logInfo('Iniciando processamento de status e fechamento de tickets...');
@ -40,9 +40,12 @@ const processStatusAndClosureController = async () => {
// Compara o status real do GLPI com o status que temos do ServiceNow // Compara o status real do GLPI com o status que temos do ServiceNow
if (glpiLiveStatus !== null && glpiLiveStatusAsString !== snCurrentStatus) { if (glpiLiveStatus !== null && glpiLiveStatusAsString !== snCurrentStatus) {
logInfo(`Mudança de status detectada no GLPI para o ticket ${ticket.glpi_ticket_id}. GLPI está como '${glpiLiveStatusAsString}', SN está como '${snCurrentStatus}'.`); logInfo(`Mudanca de status detectada no GLPI para o ticket ${ticket.glpi_ticket_id}. GLPI esta como '${glpiLiveStatusAsString}', SN esta como '${snCurrentStatus}'.`);
// Se uma mudança foi detectada no GLPI, forçamos o "bastão" para ele. if (ticket.source_last !== 'SNOW') {
ticket.source_last = 'GLPI'; ticket.source_last = 'GLPI';
} else {
logInfo(`Direcao mantida como SNOW -> GLPI para o ticket ${ticketSnDetails.ticket_number}.`);
}
} }
// ================= LÓGICA SNOW -> GLPI ================= // ================= LÓGICA SNOW -> GLPI =================
@ -56,17 +59,22 @@ const processStatusAndClosureController = async () => {
// Se o ticket no GLPI está permanentemente Fechado (6), não fazemos nada. // Se o ticket no GLPI está permanentemente Fechado (6), não fazemos nada.
if (glpiCurrentStatus === 6) { if (glpiCurrentStatus === 6) {
logWarning(`Ticket GLPI ${ticket.glpi_ticket_id} está permanentemente Fechado (6). Ação de reabertura pelo SN ('${snCurrentStatus}') foi bloqueada.`); logWarning(`Ticket GLPI ${ticket.glpi_ticket_id} está permanentemente Fechado (6). Ação de reabertura pelo SN ('${snCurrentStatus}') foi bloqueada.`);
} else if (snCurrentStatus === 'Resolvido' && glpiCurrentStatus !== 5) { // SN foi para 'Resolved' e GLPI ainda não está 'Solucionado' } else if (snCurrentStatus === 'Resolvido') {
logInfo(`SN ${ticketSnDetails.ticket_number} foi Resolvido. Sincronizando para GLPI...`); logInfo(`SN ${ticketSnDetails.ticket_number} foi Resolvido. Enviando nota para GLPI sem fechar o chamado...`);
await closeTicketInGlpi(ticket);
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'solved', 'solved'); // Regra de negocio: resolucao vinda do SN nao fecha no GLPI.
} else if (snCurrentStatus === 'Resolvido' && glpiCurrentStatus === 5) { // Apenas registra uma nota e remove o ticket do monitoramento da integracao.
// Ambos já estão resolvidos, nenhuma ação necessária. const collaboratorName = ticketSnDetails.updated_by || ticketSnDetails.closed_by || null;
logInfo(`SN ${ticketSnDetails.ticket_number} e GLPI ${ticket.glpi_ticket_id} já estão no estado Resolvido/Solucionado. Nenhuma ação necessária.`); await addServiceNowClosureNoteToGlpi(ticket, collaboratorName, 'solucionado');
await TicketSyncModel.updateStatusAndSourceLast(ticket.sn_ticket_id, 'closed', 'closed', 'SNOW');
} else if (snCurrentStatus === 'Encerrado - Omitido' || snCurrentStatus === 'Encerrado') { // SN foi para 'Closed' } else if (snCurrentStatus === 'Encerrado - Omitido' || snCurrentStatus === 'Encerrado') { // SN foi para 'Closed'
logInfo(`SN ${ticketSnDetails.ticket_number} foi Fechado. Sincronizando para GLPI...`); logInfo(`SN ${ticketSnDetails.ticket_number} foi Fechado. Enviando nota para GLPI sem fechar o chamado...`);
await TicketGlpiModel.forceCloseTicket(ticket.glpi_ticket_id);
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'closed', 'closed'); // Regra de negocio: encerramento vindo do SN nao fecha no GLPI.
// Apenas registra uma nota e remove o ticket do monitoramento da integracao.
const collaboratorName = ticketSnDetails.updated_by || ticketSnDetails.closed_by || null;
await addServiceNowClosureNoteToGlpi(ticket, collaboratorName, 'encerrado');
await TicketSyncModel.updateStatusAndSourceLast(ticket.sn_ticket_id, 'closed', 'closed', 'SNOW');
} else { } else {
// Se estiver Solucionado (5) ou em qualquer outro estado, permite a atualização (reabertura). // Se estiver Solucionado (5) ou em qualquer outro estado, permite a atualização (reabertura).
await TicketGlpiModel.updateStatus(ticket.glpi_ticket_id, snCurrentStatus); await TicketGlpiModel.updateStatus(ticket.glpi_ticket_id, snCurrentStatus);

View File

@ -147,6 +147,36 @@ const checkGlpiStatusChanges = async () => {
} }
}; };
const addServiceNowClosureNoteToGlpi = async (ticket, collaboratorName, finalStatus = 'encerrado') => {
try {
const resolvedBy = collaboratorName && collaboratorName.trim()
? collaboratorName.trim()
: 'colaborador CAOA';
const statusText = finalStatus === 'solucionado'
? 'Chamado solucionado no Service Now pela CAOA.'
: 'Chamado encerrado no Service Now pela CAOA.';
const styledMessage = `
<div style="border:1px solid #d6e9ff; border-left:4px solid #2b6cb0; background:#f7fbff; padding:12px; border-radius:4px;">
<div style="font-weight:700; color:#1a365d; margin-bottom:6px;">Atualizacao automatica de encerramento</div>
<div style="color:#2d3748;">
${statusText}
</div>
<div style="margin-top:8px; color:#4a5568; font-size:12px;">
Responsavel informado: ${resolvedBy}
</div>
</div>`;
await TicketGlpiModel.insertComment({ content: styledMessage }, ticket.glpi_ticket_id);
await TicketSyncModel.updateLastSync(ticket.sn_ticket_id, 'glpi');
logInfo(`Nota de encerramento do ServiceNow adicionada ao GLPI ${ticket.glpi_ticket_id}.`);
return true;
} catch (error) {
logError(error, `Falha ao adicionar nota de encerramento do SN no GLPI ${ticket.glpi_ticket_id}`);
return false;
}
};
const closeTicketInGlpi = async (ticket) => { const closeTicketInGlpi = async (ticket) => {
try { try {
logInfo(`🔒 Iniciando fechamento do chamado GLPI ${ticket.glpi_ticket_id}...`); logInfo(`🔒 Iniciando fechamento do chamado GLPI ${ticket.glpi_ticket_id}...`);
@ -167,7 +197,8 @@ const closeTicketInGlpi = async (ticket) => {
module.exports = { module.exports = {
syncTicketsToGlpi, syncTicketsToGlpi,
checkGlpiStatusChanges, checkGlpiStatusChanges,
closeTicketInGlpi closeTicketInGlpi,
addServiceNowClosureNoteToGlpi
}; };
/** /**