snglpi/src/services/glpiTicketService.js

204 lines
10 KiB
JavaScript
Raw Normal View History

const TicketSnModel = require('../models/ticketSnModel');
const TicketGlpiModel = require('../models/ticketGlpiModel');
const TicketUpdateModel = require('../models/ticketUpdateModel');
const TicketSyncModel = require('../models/ticketSyncModel');
const { logInfo, logError } = require('../utils/logger');
const { addWorkNoteToServiceNow, updateExternalTicketInServiceNow, queueExternalTicketLinkRetry, reassignTicketInServiceNow } = require('./servicenowService');
const { stripHTML } = require('../utils/commentSanitizer');
const outOfScopeSolutionTypeId = Number(process.env.GLPI_OOS_SOLUTION_TYPE_ID || 27);
const caoaTiGroupId = process.env.SERVICENOW_CAOA_TI_GROUP_ID;
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;
}
logInfo(`📋 Encontrados ${pendingTickets.length} tickets para sincronizar`);
2025-10-13 06:06:52 -03:00
for (const ticket of pendingTickets) {
await processSingleTicket(ticket);
}
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
}
};
const processSingleTicket = async (ticket) => {
try {
logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`);
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');
const linkedOk = await updateExternalTicketInServiceNow(ticket.id, ticket.glpi_ticket_id);
await queueExternalTicketLinkRetry(ticket.ticket_sync_id, ticket.id, ticket.glpi_ticket_id, linkedOk);
return;
}
2025-10-13 06:06:52 -03:00
const existingTicket = await TicketGlpiModel.ticketExists(ticket.ticket_number);
await TicketSyncModel.updateLastSync(ticket.id, 'glpi');
2025-10-13 06:06:52 -03:00
if (existingTicket) {
logInfo(`✅ Ticket ${ticket.ticket_number} encontrado no GLPI. Atualizando registro local com o ID: ${existingTicket}`);
const glpiLiveStatus = await TicketGlpiModel.getTicketStatus(existingTicket);
if (glpiLiveStatus === 6) {
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);
return;
}
await TicketSyncModel.updateStatusAndGlpiTicket(ticket.id, 'synced', 'synced', existingTicket);
const existingLinkOk = await updateExternalTicketInServiceNow(ticket.id, existingTicket);
await queueExternalTicketLinkRetry(ticket.ticket_sync_id, ticket.id, existingTicket, existingLinkOk);
logInfo(`✅ Ticket ${ticket.ticket_number} já existe no GLPI! GLPI ID: ${existingTicket}`);
return;
}
const glpiData = {
ticket_number: ticket.ticket_number,
short_description: ticket.short_description,
description: ticket.description,
justificativa: ticket.justificativa,
caller_id: ticket.caller_id,
caller_email: ticket.caller_email,
caller_phone: ticket.telefone,
caller_ramal: ticket.ramal,
type: ticket.tipo,
state: ticket.status,
priority: '3',
location_id: ticket.location_id,
location_name: ticket.location_name,
opened_at: ticket.opened_at,
updated_at: ticket.updated_at
};
const glpiTicketId = await TicketGlpiModel.createTicket(glpiData);
await TicketSyncModel.updateLastSync(ticket.id, 'glpi');
await TicketSyncModel.updateStatusAndGlpiTicket(ticket.id, 'synced', 'synced', glpiTicketId);
const externalLinkOk = await updateExternalTicketInServiceNow(ticket.id, glpiTicketId);
await queueExternalTicketLinkRetry(ticket.ticket_sync_id, ticket.id, glpiTicketId, externalLinkOk);
logInfo(`✅ Ticket ${ticket.ticket_number} sincronizado! GLPI ID: ${glpiTicketId}`);
} catch (error) {
logError(error, `❌ Erro no ticket ${ticket.ticket_number}`);
try {
await TicketSyncModel.updateStatus(ticket.id, 'error', 'collected');
} catch (innerError) {
logError(innerError, `❌ Erro ao registrar falha de sincronização para ticket ${ticket.id}`);
}
}
2025-08-30 19:04:35 -03:00
};
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
const isOutOfScopeSolution = (solution) => solution?.solutiontypes_id === outOfScopeSolutionTypeId;
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
/**
* Regra "fora de escopo": nota no SN + transferencia para fila CAOA + remove grupo do GLPI + fecha
* o ticket no GLPI. Antes existia uma copia quase identica em processGlpiClosureController.js e
* outra em processStatusController.js (uma delas nao removia o grupo do GLPI) - consolidado aqui.
*/
const handleOutOfScopeResolution = async (ticket, solution) => {
const justification = solution?.content ? stripHTML(solution.content) : 'Ticket marcado como "Fora do escopo" no GLPI.';
await addWorkNoteToServiceNow(ticket.sn_ticket_id, `Ticket fora do escopo Sothis. Chamado transferido para a fila CAOA Redes com justificativa: ${justification}`);
await reassignTicketInServiceNow(ticket.sn_ticket_id, caoaTiGroupId);
await TicketGlpiModel.unassignGroupFromTicket(ticket.glpi_ticket_id);
await TicketGlpiModel.forceCloseTicket(ticket.glpi_ticket_id);
await TicketSyncModel.updateStatus(ticket.sn_ticket_id, 'closed', 'ignored');
logInfo(`Ticket GLPI ${ticket.glpi_ticket_id} fora do escopo: grupo removido, transferido para CAOA e fechado.`);
};
const addServiceNowClosureNoteToGlpi = async (ticket, collaboratorName, finalStatus = 'encerrado') => {
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
const sourceId = `sn_closure_note:${ticket.glpi_ticket_id}:${finalStatus}`;
try {
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
const alreadySent = await TicketUpdateModel.getBySourceId(sourceId);
if (alreadySent) {
logInfo(`Nota de encerramento do ServiceNow ja registrada para o GLPI ${ticket.glpi_ticket_id} (status '${finalStatus}'). Ignorando reenvio.`);
return { ok: true, isNew: false };
}
const resolvedBy = collaboratorName && collaboratorName.trim()
? collaboratorName.trim()
: 'colaborador CAOA';
const statusText = finalStatus === 'solucionado'
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
? 'Chamado solucionado no Service Now e removido do monitoramento.'
: 'Chamado encerrado no Service Now e removido do monitoramento.';
const styledMessage = `
<div style="border:1px solid #fecaca; border-left:4px solid #c53030; background:#fff5f5; padding:12px; border-radius:4px;">
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
<div style="font-weight:700; color:#9b2c2c; margin-bottom:6px;">Encerramento realizado pelo Service Now</div>
<div style="color:#742a2a;">
${statusText}
</div>
<div style="margin-top:8px; color:#742a2a; font-size:12px;">
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
Responsavel: ${resolvedBy}
</div>
</div>`;
await TicketGlpiModel.insertComment({ content: styledMessage }, ticket.glpi_ticket_id);
await TicketSyncModel.updateLastSync(ticket.sn_ticket_id, 'glpi');
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
await TicketUpdateModel.insert({
ticket_sync_id: ticket.id,
update_type: 'close_notice',
source_system: 'sn',
content: styledMessage,
author: 'integration',
created_at: new Date(),
updated_at: new Date(),
source_id: sourceId,
destiny_id: 'done'
});
logInfo(`Nota de encerramento do ServiceNow adicionada ao GLPI ${ticket.glpi_ticket_id}.`);
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
return { ok: true, isNew: true };
} catch (error) {
logError(error, `Falha ao adicionar nota de encerramento do SN no GLPI ${ticket.glpi_ticket_id}`);
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
return { ok: false, isNew: false };
}
};
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);
await TicketSyncModel.updateLastSync(ticket.sn_ticket_id, 'glpi');
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}`);
throw error;
}
};
2025-08-30 19:04:35 -03:00
module.exports = {
syncTicketsToGlpi,
closeTicketInGlpi,
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
addServiceNowClosureNoteToGlpi,
isOutOfScopeSolution,
handleOutOfScopeResolution
};
/**
* @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 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 existir, apenas atualiza o registro de sincronização.
FEAT: Motor de regras único de status/fechamento + mandante configurável Consolida os dois controladores legados que decidiam status/fechamento de forma concorrente (processGlpiClosureController + processStatusController) em um único processTicketLifecycleController, eliminando a dependência do campo source_last (mantido só como rastro de auditoria) e a flag ENABLE_STATUS_SYNC (removida). Isso corrige o bug de reabertura: chamados reabertos no SN voltavam a ser fechados no GLPI porque a regra de precedência de finalização do fluxo legado forçava o status do GLPI de volta pro SN. Correções de bugs encontrados em teste (Frente 1): - Constraint UNIQUE em ticket_sync.sn_ticket_id + ON CONFLICT DO NOTHING na criação do registro, prevenindo duplicação de tickets. - Fechamento no SN agora é confirmado por leitura ao vivo do status antes de marcar o ticket como solved/closed, em vez de confiar só no HTTP 200 (a API do SN pode retornar sucesso e silenciosamente ignorar um valor de estado inválido — descoberto e corrigido também no mandante). - Nota de encerramento do SN pro GLPI agora é idempotente e criada uma única vez por ticket. - Erros de sincronização passam a ter um limite de tentativas (MAX_ERROR_RETRIES) antes de marcar o ticket como error_permanent, exigindo intervenção manual em vez de tentar para sempre. - Chamados criados no GLPI passam a gravar o número do ticket do SN no campo externalid. Nova funcionalidade (Frente 2): quando o SN resolve/encerra um chamado por conta própria antes do GLPI, além da nota informativa no GLPI (agora com o nome de quem encerrou, buscado ao vivo via resolved_by/closed_by/ sys_updated_by), o próprio SN recebe um aviso de que a Sothis não vai mais atender aquele chamado e ele é transferido pra fila de TI da CAOA. Nova funcionalidade (Mandante, regras 19-22): espelhamento opcional e configurável de status intermediário (Aguardando Atendimento/Em Atendimento/Em Espera) entre os dois sistemas, controlado pela env MANDANTE=GLPI|SNOW. Sem essa env configurada, mantém o comportamento atual (nenhum espelhamento de status intermediário). Nunca afeta os status finais (resolvido/encerrado/fora de escopo), que continuam regidos pelas regras fixas de fechamento. Corrigido também o workflow de deploy (.gitea/workflows/deploy-production.yml), que ainda validava os controladores antigos (já removidos) e exigia ENABLE_STATUS_SYNC=true, o que quebraria o próximo deploy em produção. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 09:45:34 -03:00
* - `isOutOfScopeSolution(solution)` / `handleOutOfScopeResolution(ticket, solution)`: Regra "fora do escopo" compartilhada por `processGlpiClosureController` e `processStatusController`.
* - `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".
*/