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' ) ;
2026-06-26 11:39:57 -03:00
const { addWorkNoteToServiceNow , updateExternalTicketInServiceNow , queueExternalTicketLinkRetry , reassignTicketInServiceNow } = require ( './servicenowService' ) ;
2026-02-24 17:40:39 -03:00
const { stripHTML } = require ( '../utils/commentSanitizer' ) ;
const outOfScopeSolutionTypeId = Number ( process . env . GLPI _OOS _SOLUTION _TYPE _ID || 27 ) ;
2026-06-26 11:39:57 -03:00
const caoaTiGroupId = process . env . SERVICENOW _CAOA _TI _GROUP _ID ;
2025-10-23 17:30:03 -03:00
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' ) ;
2026-03-04 16:37:04 -03:00
const linkedOk = await updateExternalTicketInServiceNow ( ticket . id , ticket . glpi _ticket _id ) ;
await queueExternalTicketLinkRetry ( ticket . ticket _sync _id , ticket . id , ticket . glpi _ticket _id , linkedOk ) ;
2025-10-17 16:58:14 -03:00
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 ) ;
2026-03-04 16:37:04 -03:00
const existingLinkOk = await updateExternalTicketInServiceNow ( ticket . id , existingTicket ) ;
await queueExternalTicketLinkRetry ( ticket . ticket _sync _id , ticket . id , existingTicket , existingLinkOk ) ;
2025-10-17 16:58:14 -03:00
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 ) ;
2026-03-04 16:37:04 -03:00
const externalLinkOk = await updateExternalTicketInServiceNow ( ticket . id , glpiTicketId ) ;
await queueExternalTicketLinkRetry ( ticket . ticket _sync _id , ticket . id , glpiTicketId , externalLinkOk ) ;
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
} ;
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 ;
2025-10-23 17:30:03 -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
/ * *
* 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. ` ) ;
2025-10-23 17:30:03 -03:00
} ;
2026-02-23 11:39:09 -03:00
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 } ` ;
2026-02-23 11:39:09 -03:00
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 } ;
}
2026-02-23 11:39:09 -03:00
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.' ;
2026-02-23 11:39:09 -03:00
const styledMessage = `
2026-02-24 17:40:39 -03:00
< 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 < / d i v >
2026-02-24 17:40:39 -03:00
< div style = "color:#742a2a;" >
2026-02-23 11:39:09 -03:00
$ { statusText }
< / d i v >
2026-02-24 17:40:39 -03:00
< 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 }
2026-02-24 17:40:39 -03:00
< / d i v >
2026-02-23 11:39:09 -03:00
< / d i v > ` ;
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'
} ) ;
2026-02-23 11:39:09 -03:00
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 } ;
2026-02-23 11:39:09 -03:00
} 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 } ;
2026-02-23 11:39:09 -03:00
}
} ;
2025-10-23 17:30:03 -03:00
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 ,
2026-02-23 11:39:09 -03:00
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
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 .
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 ` .
2025-10-29 14:20:21 -03:00
* - ` 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
* /