diff --git a/src/app.js b/src/app.js index 628bc75..78bf1aa 100644 --- a/src/app.js +++ b/src/app.js @@ -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); diff --git a/src/controllers/processCommentsController.js b/src/controllers/processCommentsController.js index 1548acd..21927b9 100644 --- a/src/controllers/processCommentsController.js +++ b/src/controllers/processCommentsController.js @@ -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, diff --git a/src/controllers/processStatusController.js b/src/controllers/processStatusController.js index 6d737a4..dc682cb 100644 --- a/src/controllers/processStatusController.js +++ b/src/controllers/processStatusController.js @@ -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 { diff --git a/src/models/ticketGlpiModel.js b/src/models/ticketGlpiModel.js index 652b8e4..3a563fe 100644 --- a/src/models/ticketGlpiModel.js +++ b/src/models/ticketGlpiModel.js @@ -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(); + } + } + } /**