2025-10-23 17:30:03 -03:00
const TicketSnModel = require ( '../models/ticketSnModel' ) ;
const TicketSyncModel = require ( '../models/ticketSyncModel' ) ;
const SyncControlModel = require ( '../models/syncControlModel' ) ;
2025-10-28 14:06:00 -03:00
const { fetchTicketsFromServiceNow : fetchTicketsApi , fetchRequestsFromServiceNow : fetchRequestsApi , fetchScItemOptionValue } = require ( '../services/servicenowService' ) ;
PERF: Reduz verbosidade de logs em nivel info no ciclo do cron
Rebaixa para debug uma serie de logs "heartbeat" que disparavam todo ciclo
(a cada 1 minuto) independente de qualquer mudanca real, competindo com os
logs de acao/erro que realmente importam: buscas por watermark, contagem
de tickets pendentes/monitorados, checagens de comentario ja sincronizado,
abertura de conexao GLPI/PostgreSQL, e o resumo de sincronizacao do SN
(que a margem de seguranca de 6h do watermark sempre retorna nao-vazio,
mesmo sem mudanca real de campo).
Corrige tambem um bug real encontrado em teste: no fluxo GLPI -> SN, todo
comentario historico de um ticket era sanitizado e checado contra a API do
SN a cada ciclo antes mesmo de verificar se ja tinha sido sincronizado
(ticket_updates.destiny_id). Em tickets com historico longo isso
significava dezenas de chamadas desnecessarias por minuto. Agora a
checagem local (barata) roda primeiro, e so sanitiza/consulta o SN quando
o comentario realmente ainda nao foi sincronizado.
Corrige tambem o log de configuracao do pool PostgreSQL, que imprimia
"[object Object]" por passar o objeto de config como mensagem em vez de
metadado.
LOG_LEVEL default muda de debug para info em .env.example e
.env.production, unica forma do rebaixamento acima ter efeito pratico.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 16:00:29 -03:00
const { logInfo , logError , logSync , logDebug } = require ( '../utils/logger' ) ;
2025-10-23 17:30:03 -03:00
/ * *
2026-02-19 11:21:40 -03:00
* Processa e salva um lote de tickets ( incidentes ou requisicoes ) .
2025-10-23 17:30:03 -03:00
* @ param { Array } tickets - A lista de tickets da API do ServiceNow .
* @ param { ( 'incidente' | 'requisicao' ) } type - O tipo de ticket .
* /
const processAndSaveTickets = async ( tickets , type ) => {
2025-10-28 14:06:00 -03:00
const parseServiceNowDate = ( dateString ) => {
if ( ! dateString ) return null ;
if ( ! dateString . includes ( '/' ) ) {
return new Date ( dateString ) ;
}
const parts = dateString . match ( /(\d{2})\/(\d{2})\/(\d{4}) (\d{2}):(\d{2}):(\d{2})/ ) ;
if ( ! parts ) return null ; // Retorna nulo se o formato for inesperado
2026-02-19 11:21:40 -03:00
// Formato: new Date(ano, mes-1, dia, hora, minuto, segundo)
2025-10-28 14:06:00 -03:00
return new Date ( parts [ 3 ] , parts [ 2 ] - 1 , parts [ 1 ] , parts [ 4 ] , parts [ 5 ] , parts [ 6 ] ) ;
} ;
2025-10-23 17:30:03 -03:00
if ( ! tickets || tickets . length === 0 ) {
PERF: Reduz verbosidade de logs em nivel info no ciclo do cron
Rebaixa para debug uma serie de logs "heartbeat" que disparavam todo ciclo
(a cada 1 minuto) independente de qualquer mudanca real, competindo com os
logs de acao/erro que realmente importam: buscas por watermark, contagem
de tickets pendentes/monitorados, checagens de comentario ja sincronizado,
abertura de conexao GLPI/PostgreSQL, e o resumo de sincronizacao do SN
(que a margem de seguranca de 6h do watermark sempre retorna nao-vazio,
mesmo sem mudanca real de campo).
Corrige tambem um bug real encontrado em teste: no fluxo GLPI -> SN, todo
comentario historico de um ticket era sanitizado e checado contra a API do
SN a cada ciclo antes mesmo de verificar se ja tinha sido sincronizado
(ticket_updates.destiny_id). Em tickets com historico longo isso
significava dezenas de chamadas desnecessarias por minuto. Agora a
checagem local (barata) roda primeiro, e so sanitiza/consulta o SN quando
o comentario realmente ainda nao foi sincronizado.
Corrige tambem o log de configuracao do pool PostgreSQL, que imprimia
"[object Object]" por passar o objeto de config como mensagem em vez de
metadado.
LOG_LEVEL default muda de debug para info em .env.example e
.env.production, unica forma do rebaixamento acima ter efeito pratico.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 16:00:29 -03:00
logDebug ( ` Nenhum ticket do tipo ' ${ type } ' para processar. ` ) ;
2025-10-29 14:20:21 -03:00
return '' ;
2025-10-23 17:30:03 -03:00
}
2026-02-19 11:21:40 -03:00
logInfo ( ` INFO: Iniciando salvamento de ${ tickets . length } ${ type } s no banco... ` ) ;
2025-10-29 14:20:21 -03:00
let latestUpdateTimestamp = new Date ( 0 ) ;
2025-10-23 17:30:03 -03:00
for ( const ticket of tickets ) {
try {
2026-02-19 18:22:38 -03:00
if ( type === 'requisicao' ) {
const ticketNumber = typeof ticket . number === 'object' ? ticket . number ? . value : ticket . number ;
logInfo ( ` Buscando variaveis de catalogo para requisicao ${ ticketNumber } . ` ) ;
2025-10-28 14:06:00 -03:00
const extraData = await fetchScItemOptionValue ( ticket . sys _id , 'justificativa' ) ;
const vars = { } ;
2026-02-19 18:22:38 -03:00
( extraData || [ ] ) . forEach ( ( item ) => {
const question = item . question _text ;
const name = item . variable _name ;
const value = item . value ;
2025-10-28 14:06:00 -03:00
2026-02-19 18:22:38 -03:00
if ( name === 'select_what_you_want' || question === 'Select what you want' ) vars . whatYouWant = value ;
if ( name === 'please_justify_the_need' || question === 'Please justify the need' ) vars . justification = value ;
if ( name === 'telephone_favorecido_ti' || question === 'Telephone' ) vars . telephone = value ;
} ) ;
2025-10-28 14:06:00 -03:00
2026-02-19 18:22:38 -03:00
ticket . justificativa = vars . justification || null ;
if ( vars . telephone ) {
2025-10-28 14:06:00 -03:00
ticket . telefone = vars . telephone ;
}
2026-02-19 18:22:38 -03:00
logInfo ( ` Variaveis da requisicao ${ ticketNumber } : justificativa= ${ ticket . justificativa ? 'ok' : 'vazia' } , telefone= ${ ticket . telefone ? 'ok' : 'vazio' } . ` ) ;
2025-10-28 14:06:00 -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 ticketNumber = typeof ticket . number === 'object' ? ticket . number ? . value : ticket . number ;
const existingTicketSn = await TicketSnModel . findByTicketNumber ( ticketNumber ) ;
const oldStatus = existingTicketSn ? . status || null ;
2025-10-23 17:30:03 -03:00
const { id : idTableTicketSn , wasInserted } = await TicketSnModel . saveTicket ( ticket , type ) ;
2025-10-28 14:06:00 -03:00
const ticketStatus = ticket . state ;
2025-10-23 17:30:03 -03:00
const dataSync = {
sn _ticket _id : idTableTicketSn ,
last _sn _sync : new Date ( ) ,
glpi _ticket _id : null ,
created _at : new Date ( ) ,
updated _at : new Date ( )
} ;
if ( ticketStatus === 'Encerrado' || ticketStatus === 'Encerrado - Omitido' ) {
dataSync . sn _sync _status = 'closed' ;
2025-10-29 14:20:21 -03:00
dataSync . glpi _sync _status = 'ignored' ;
2025-10-23 17:30:03 -03:00
} else {
2025-10-29 14:20:21 -03:00
dataSync . sn _sync _status = 'collected' ;
dataSync . glpi _sync _status = 'pending_check' ;
2025-10-23 17:30:03 -03:00
}
2025-10-29 14:20:21 -03:00
dataSync . source _last = 'SNOW' ;
2025-10-23 17:30:03 -03:00
const exists = await TicketSyncModel . getIdSyncBySnId ( idTableTicketSn ) ;
if ( ! exists ) {
await TicketSyncModel . createSyncRecord ( dataSync ) ;
} else if ( wasInserted === false ) {
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
if ( oldStatus !== ticketStatus ) {
await TicketSyncModel . updateSourceLast ( idTableTicketSn , 'SNOW' ) ;
logInfo ( ` Ticket ${ ticket . number } atualizado pelo ServiceNow (status ' ${ oldStatus } ' -> ' ${ ticketStatus } '). 'source_last' definido como 'SNOW'. ` ) ;
} else {
logInfo ( ` Ticket ${ ticket . number } recebido de novo pela margem de seguranca do watermark, sem mudanca de status (' ${ ticketStatus } '). Ignorando reset de 'source_last'. ` ) ;
}
2025-10-23 17:30:03 -03:00
} else {
PERF: Reduz verbosidade de logs em nivel info no ciclo do cron
Rebaixa para debug uma serie de logs "heartbeat" que disparavam todo ciclo
(a cada 1 minuto) independente de qualquer mudanca real, competindo com os
logs de acao/erro que realmente importam: buscas por watermark, contagem
de tickets pendentes/monitorados, checagens de comentario ja sincronizado,
abertura de conexao GLPI/PostgreSQL, e o resumo de sincronizacao do SN
(que a margem de seguranca de 6h do watermark sempre retorna nao-vazio,
mesmo sem mudanca real de campo).
Corrige tambem um bug real encontrado em teste: no fluxo GLPI -> SN, todo
comentario historico de um ticket era sanitizado e checado contra a API do
SN a cada ciclo antes mesmo de verificar se ja tinha sido sincronizado
(ticket_updates.destiny_id). Em tickets com historico longo isso
significava dezenas de chamadas desnecessarias por minuto. Agora a
checagem local (barata) roda primeiro, e so sanitiza/consulta o SN quando
o comentario realmente ainda nao foi sincronizado.
Corrige tambem o log de configuracao do pool PostgreSQL, que imprimia
"[object Object]" por passar o objeto de config como mensagem em vez de
metadado.
LOG_LEVEL default muda de debug para info em .env.example e
.env.production, unica forma do rebaixamento acima ter efeito pratico.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 16:00:29 -03:00
logDebug ( ` Registro de sincronizacao para o ticket ${ ticket . number ? . value } ja existe. Nenhuma acao necessaria. ` ) ;
2025-10-23 17:30:03 -03:00
}
2025-10-28 14:06:00 -03:00
const currentUpdateDate = parseServiceNowDate ( ticket . sys _updated _on ) ;
if ( currentUpdateDate && currentUpdateDate . getTime ( ) > latestUpdateTimestamp . getTime ( ) ) {
latestUpdateTimestamp = currentUpdateDate ;
2025-10-23 17:30:03 -03:00
}
} catch ( error ) {
logError ( error , ` Erro ao processar o ticket ${ ticket . number ? . value } do tipo ${ type } . ` ) ;
}
}
logSync ( 'ServiceNow' , tickets . length , ` ${ type } s ` ) ;
2025-10-28 14:06:00 -03:00
return latestUpdateTimestamp . getTime ( ) > 0
? latestUpdateTimestamp . toISOString ( ) . slice ( 0 , 19 ) . replace ( 'T' , ' ' )
: '' ;
2025-10-23 17:30:03 -03:00
} ;
/ * *
2026-02-19 11:21:40 -03:00
* Controller para buscar tickets e requisicoes do ServiceNow e salva - los localmente .
2025-10-23 17:30:03 -03:00
* /
const processSyncController = async ( ) => {
try {
const watermark = await SyncControlModel . getWatermark ( 'servicenow' ) ;
PERF: Reduz verbosidade de logs em nivel info no ciclo do cron
Rebaixa para debug uma serie de logs "heartbeat" que disparavam todo ciclo
(a cada 1 minuto) independente de qualquer mudanca real, competindo com os
logs de acao/erro que realmente importam: buscas por watermark, contagem
de tickets pendentes/monitorados, checagens de comentario ja sincronizado,
abertura de conexao GLPI/PostgreSQL, e o resumo de sincronizacao do SN
(que a margem de seguranca de 6h do watermark sempre retorna nao-vazio,
mesmo sem mudanca real de campo).
Corrige tambem um bug real encontrado em teste: no fluxo GLPI -> SN, todo
comentario historico de um ticket era sanitizado e checado contra a API do
SN a cada ciclo antes mesmo de verificar se ja tinha sido sincronizado
(ticket_updates.destiny_id). Em tickets com historico longo isso
significava dezenas de chamadas desnecessarias por minuto. Agora a
checagem local (barata) roda primeiro, e so sanitiza/consulta o SN quando
o comentario realmente ainda nao foi sincronizado.
Corrige tambem o log de configuracao do pool PostgreSQL, que imprimia
"[object Object]" por passar o objeto de config como mensagem em vez de
metadado.
LOG_LEVEL default muda de debug para info em .env.example e
.env.production, unica forma do rebaixamento acima ter efeito pratico.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 16:00:29 -03:00
logDebug ( ` Buscando tickets do ServiceNow atualizados desde: ${ watermark } ` ) ;
2025-10-23 17:30:03 -03:00
const incidents = await fetchTicketsApi ( watermark ) ;
const latestIncidentUpdate = await processAndSaveTickets ( incidents , 'incidente' ) ;
PERF: Reduz verbosidade de logs em nivel info no ciclo do cron
Rebaixa para debug uma serie de logs "heartbeat" que disparavam todo ciclo
(a cada 1 minuto) independente de qualquer mudanca real, competindo com os
logs de acao/erro que realmente importam: buscas por watermark, contagem
de tickets pendentes/monitorados, checagens de comentario ja sincronizado,
abertura de conexao GLPI/PostgreSQL, e o resumo de sincronizacao do SN
(que a margem de seguranca de 6h do watermark sempre retorna nao-vazio,
mesmo sem mudanca real de campo).
Corrige tambem um bug real encontrado em teste: no fluxo GLPI -> SN, todo
comentario historico de um ticket era sanitizado e checado contra a API do
SN a cada ciclo antes mesmo de verificar se ja tinha sido sincronizado
(ticket_updates.destiny_id). Em tickets com historico longo isso
significava dezenas de chamadas desnecessarias por minuto. Agora a
checagem local (barata) roda primeiro, e so sanitiza/consulta o SN quando
o comentario realmente ainda nao foi sincronizado.
Corrige tambem o log de configuracao do pool PostgreSQL, que imprimia
"[object Object]" por passar o objeto de config como mensagem em vez de
metadado.
LOG_LEVEL default muda de debug para info em .env.example e
.env.production, unica forma do rebaixamento acima ter efeito pratico.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 16:00:29 -03:00
logDebug ( 'Buscando requisicoes do ServiceNow...' ) ;
2025-10-23 17:30:03 -03:00
const requests = await fetchRequestsApi ( watermark ) ;
2025-10-24 13:13:08 -03:00
const latestRequestUpdate = await processAndSaveTickets ( requests , 'requisicao' ) ;
2025-10-23 17:30:03 -03:00
const newWatermark = latestIncidentUpdate > latestRequestUpdate ? latestIncidentUpdate : latestRequestUpdate ;
2025-10-28 14:06:00 -03:00
2025-10-23 17:30:03 -03:00
if ( newWatermark ) {
2026-02-19 11:21:40 -03:00
// Mantem margem de seguranca de 6 horas para evitar perda de eventos por atraso de replicacao, timezone ou clock skew entre sistemas.
2025-10-28 14:06:00 -03:00
const sixHoursInMillis = 6 * 60 * 60 * 1000 ;
const nextWatermark = new Date ( new Date ( newWatermark ) . getTime ( ) - sixHoursInMillis ) ;
2025-10-23 17:30:03 -03:00
await SyncControlModel . setWatermark ( 'servicenow' , nextWatermark ) ;
} else {
PERF: Reduz verbosidade de logs em nivel info no ciclo do cron
Rebaixa para debug uma serie de logs "heartbeat" que disparavam todo ciclo
(a cada 1 minuto) independente de qualquer mudanca real, competindo com os
logs de acao/erro que realmente importam: buscas por watermark, contagem
de tickets pendentes/monitorados, checagens de comentario ja sincronizado,
abertura de conexao GLPI/PostgreSQL, e o resumo de sincronizacao do SN
(que a margem de seguranca de 6h do watermark sempre retorna nao-vazio,
mesmo sem mudanca real de campo).
Corrige tambem um bug real encontrado em teste: no fluxo GLPI -> SN, todo
comentario historico de um ticket era sanitizado e checado contra a API do
SN a cada ciclo antes mesmo de verificar se ja tinha sido sincronizado
(ticket_updates.destiny_id). Em tickets com historico longo isso
significava dezenas de chamadas desnecessarias por minuto. Agora a
checagem local (barata) roda primeiro, e so sanitiza/consulta o SN quando
o comentario realmente ainda nao foi sincronizado.
Corrige tambem o log de configuracao do pool PostgreSQL, que imprimia
"[object Object]" por passar o objeto de config como mensagem em vez de
metadado.
LOG_LEVEL default muda de debug para info em .env.example e
.env.production, unica forma do rebaixamento acima ter efeito pratico.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-10 16:00:29 -03:00
logDebug ( "Nenhum ticket novo ou atualizado encontrado. A marca d'agua nao foi alterada." ) ;
2025-10-23 17:30:03 -03:00
}
} catch ( error ) {
2026-02-19 11:21:40 -03:00
logError ( error , 'Erro no processo de sincronizacao de tickets do ServiceNow.' ) ;
2025-10-23 17:30:03 -03:00
}
} ;
module . exports = {
processSyncController
2025-10-29 14:20:21 -03:00
} ;
/ * *
* @ file processSyncController . js
* @ description
2026-02-19 11:21:40 -03:00
* Este controlador e responsavel por buscar novos tickets e atualizacoes do ServiceNow .
* Ele utiliza uma "marca d'agua" ( watermark ) para buscar apenas os registros modificados desde a ultima execucao ,
2025-10-29 14:20:21 -03:00
* otimizando o processo .
*
* Funcionalidades :
2026-02-19 11:21:40 -03:00
* - Busca incidentes e requisicoes do ServiceNow com base na data da ultima atualizacao .
* - Para cada ticket , salva ou atualiza o registro no banco de dados intermediario ( ` tickets_sn ` ) .
* - Cria ou atualiza o registro de controle na tabela ` ticket_sync ` , definindo o status inicial e a origem da atualizacao ( ` source_last ` ) .
* - Implementa uma logica de enriquecimento de dados para requisicoes com descricao vazia , buscando informacoes de variaveis de catalogo ( ` sc_item_option ` ) .
* - Ao final , atualiza a marca d ' agua para a proxima execucao , apenas se houverem tickets atualizados na execucao vigente .
2026-02-19 18:22:38 -03:00
* /