HOTFIX: Correção do Bug Em PROD

- Travas foram adicionadas para que a reabertura nao seja feita
- Regra de desatribuição de usuario caso seja "Fora de Escopo"
This commit is contained in:
Rafael Alves Lopes 2026-03-11 17:02:27 -03:00
parent 686c2f0a54
commit 00b573d60d
4 changed files with 33 additions and 20 deletions

View File

@ -37,7 +37,7 @@ async function main() {
logInfo('Monitor de encerramento definitivo GLPI desabilitado (ENABLE_GLPI_CLOSE_CRON=false).');
}
await processCommentsController();
if (statusSyncEnabled) {
await processStatusAndClosureController();
@ -45,6 +45,8 @@ async function main() {
logInfo('Sincronizacao legada de status desabilitada (ENABLE_STATUS_SYNC=false).');
}
await processCommentsController();
logInfo('Ciclo de sincronizacao concluido com sucesso.');
} catch (error) {
logInfo('Erro na aplicacao:', error);

View File

@ -12,6 +12,12 @@ const ensureReopenFromSNWhenGlpiSolved = async (ticket) => {
return { reopened: false, glpiSolved: false };
}
// REGRA DE CONTENÇÃO: Só reabre se o monitoramento de status estiver ativo.
// Se o usuário desligou o sync de status, ele quer controle manual.
if (process.env.ENABLE_STATUS_SYNC === 'false') {
return { reopened: false, glpiSolved: true };
}
// CORREÇÃO: Verificar se o ticket foi fechado recentemente.
// Se o status já é 'solved', significa que o processGlpiClosureController
// tentou fechar recentemente. O SN pode ainda estar processando o close,

View File

@ -120,10 +120,10 @@ const processStatusAndClosureController = async () => {
await addServiceNowClosureNoteToGlpi(ticket, collaboratorName, 'encerrado');
await TicketSyncModel.updateStatusAndSourceLast(ticket.sn_ticket_id, 'closed', 'closed', 'SNOW');
} else {
// Se estiver Solucionado (5) ou em qualquer outro estado, permite a atualização (reabertura).
await TicketGlpiModel.updateStatus(ticket.glpi_ticket_id, snCurrentStatus);
logInfo(`Status do ticket GLPI ${ticket.glpi_ticket_id} atualizado para '${snCurrentStatus}'.`);
await TicketSyncModel.updateStatusAndSourceLast(ticket.sn_ticket_id, 'synced', 'synced', 'GLPI'); // Passa o bastão
// Sincronização passiva: não propagamos status intermediários (Em Espera, Atribuído, etc.)
// para o GLPI para evitar conflitos com a operação manual sugerida pelo usuário.
logInfo(`SN ${ticketSnDetails.ticket_number} em status '${snCurrentStatus}'. Ignorando atualização automática de status no GLPI (Regra de Contenção).`);
await TicketSyncModel.updateStatusAndSourceLast(ticket.sn_ticket_id, 'synced', 'synced', 'GLPI');
}
}
// ================= LÓGICA GLPI -> SNOW =================
@ -144,6 +144,7 @@ const processStatusAndClosureController = async () => {
const justification = solution?.content ? stripHTML(solution.content) : 'Ticket marcado como "Fora do escopo" no GLPI.';
await addWorkNoteToServiceNow(ticket.sn_ticket_id, `Ticket não será mais exibido para equipe Sothis, com a justificativa: ${justification}`);
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'closed', 'ignored');
await TicketGlpiModel.unassignGroupFromTicket(ticket.glpi_ticket_id);
await TicketGlpiModel.forceCloseTicket(ticket.glpi_ticket_id);
} else {
// REGRA DE NEGÓCIO: Não pode fechar um ticket que está "Em Espera" ou "Aguardando Atendimento" no SN.
@ -171,23 +172,12 @@ const processStatusAndClosureController = async () => {
if (ticket.glpi_sync_status !== 'closed') {
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'closed', ticket.sn_sync_status);
}
} else {
// Se o status for de um ticket aberto (reabertura, etc.), propaga a mudança.
const glpiStatusAsString = TicketGlpiModel.mapGlpiStatusToString(glpiCurrentStatus);
if (glpiStatusAsString !== snCurrentStatus) {
if (snCurrentStatus === 'Encerrado' || snCurrentStatus === 'Encerrado - Omitido'){
logInfo(`SN ${ticketSnDetails.ticket_number} foi Fechado. Sincronizando para GLPI...`);
await TicketGlpiModel.forceCloseTicket(ticket.glpi_ticket_id);
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'closed', 'closed');
}
else{
logInfo(`Diferença de status detectada (GLPI: ${glpiStatusAsString}, SN: ${snCurrentStatus}). Sincronizando...`);
await updateStatusInServiceNow(ticket.sn_ticket_id, glpiStatusAsString);
}
}
// Sincronização passiva: não propagamos status intermediários (reaberturas parciais, etc.)
// do GLPI para o SN, exceto para fechamentos definitivos já tratados acima.
logInfo(`Ticket GLPI ${ticket.glpi_ticket_id} em status '${glpiCurrentStatus}'. Ignorando atualização automática de status no SN (Regra de Contenção).`);
}
// Passa o bastão de volta para o SN se a ação não for de finalização
if (glpiCurrentStatus <= 4) {
if (glpiCurrentStatus && glpiCurrentStatus <= 4) {
await TicketSyncModel.updateStatusAndSourceLast(ticket.sn_ticket_id, 'synced', 'synced', 'SNOW');
}
} else {

View File

@ -476,6 +476,21 @@ class TicketGlpiModel {
}
}
static async unassignGroupFromTicket(ticketId) {
const connection = await glpiPool.getConnection();
const groupId = parseInt(process.env.GLPI_DEFAULT_GROUP_ID, 10) || 30;
try {
const query = `DELETE FROM glpi_groups_tickets WHERE tickets_id = ? AND groups_id = ?`;
await connection.execute(query, [ticketId, groupId]);
logInfo(`✅ Grupo ${groupId} removido do ticket GLPI ${ticketId}.`);
} catch (error) {
logError(error, `❌ Erro ao remover grupo ${groupId} do ticket ${ticketId} no GLPI`);
throw error;
} finally {
if (connection) connection.release();
}
}
}
/**