2025-10-23 17:30:03 -03:00
|
|
|
const TicketSnModel = require('../models/ticketSnModel');
|
|
|
|
|
const TicketSyncModel = require('../models/ticketSyncModel');
|
|
|
|
|
const SyncControlModel = require('../models/syncControlModel');
|
|
|
|
|
const { fetchTicketsFromServiceNow: fetchTicketsApi, fetchRequestsFromServiceNow: fetchRequestsApi } = require('../services/servicenowService');
|
|
|
|
|
const { logInfo, logError, logSync } = require('../utils/logger');
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Processa e salva um lote de tickets (incidentes ou requisições).
|
|
|
|
|
* @param {Array} tickets - A lista de tickets da API do ServiceNow.
|
|
|
|
|
* @param {('incidente'|'requisicao')} type - O tipo de ticket.
|
|
|
|
|
*/
|
|
|
|
|
const processAndSaveTickets = async (tickets, type) => {
|
|
|
|
|
if (!tickets || tickets.length === 0) {
|
|
|
|
|
logInfo(`Nenhum ticket do tipo '${type}' para processar.`);
|
|
|
|
|
return ''; // Retorna string vazia se não houver tickets
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logInfo(`💾 Iniciando salvamento de ${tickets.length} ${type}s no banco...`);
|
|
|
|
|
let latestUpdateTimestamp = '';
|
|
|
|
|
for (const ticket of tickets) {
|
|
|
|
|
try {
|
|
|
|
|
// 1. Salva o ticket na tabela 'tickets_sn'
|
|
|
|
|
const { id: idTableTicketSn, wasInserted } = await TicketSnModel.saveTicket(ticket, type);
|
|
|
|
|
|
|
|
|
|
// 2. Prepara os dados para a tabela de sincronização 'ticket_sync'
|
2025-10-24 13:13:08 -03:00
|
|
|
// Para requisições, o status está diretamente em ticket.state
|
|
|
|
|
const ticketStatus = type === 'incidente' ? ticket.state?.display_value : 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()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// 3. Define o status inicial de sincronização
|
|
|
|
|
if (ticketStatus === 'Encerrado' || ticketStatus === 'Encerrado - Omitido') {
|
|
|
|
|
dataSync.sn_sync_status = 'closed';
|
2025-10-24 13:13:08 -03:00
|
|
|
dataSync.glpi_sync_status = 'ignored'; // Ignora no GLPI se já veio fechado do SN
|
2025-10-23 17:30:03 -03:00
|
|
|
} else {
|
|
|
|
|
dataSync.sn_sync_status = 'collected'; // Status que indica que foi coletado do SN
|
2025-10-24 13:13:08 -03:00
|
|
|
dataSync.glpi_sync_status = 'pending_check'; // Status que indica que precisa ser checkado no GLPI
|
2025-10-23 17:30:03 -03:00
|
|
|
}
|
2025-10-24 13:13:08 -03:00
|
|
|
dataSync.source_last = 'SNOW'; // A última atualização veio do ServiceNow
|
2025-10-23 17:30:03 -03:00
|
|
|
|
|
|
|
|
// 4. Cria o registro de sincronização se ele não existir
|
|
|
|
|
const exists = await TicketSyncModel.getIdSyncBySnId(idTableTicketSn);
|
|
|
|
|
if (!exists) {
|
|
|
|
|
await TicketSyncModel.createSyncRecord(dataSync);
|
|
|
|
|
} else if (wasInserted === false) {
|
|
|
|
|
// Se o ticket foi atualizado (não inserido), significa que uma mudança veio do SN.
|
|
|
|
|
// Então, definimos o SN como a última fonte da verdade.
|
|
|
|
|
await TicketSyncModel.updateSourceLast(idTableTicketSn, 'SNOW');
|
|
|
|
|
logInfo(`Ticket ${ticket.number?.value} atualizado pelo ServiceNow. 'source_last' definido como 'SNOW'.`);
|
|
|
|
|
} else {
|
|
|
|
|
logInfo(`Registro de sincronização para o ticket ${ticket.number?.value} já existe. Nenhuma ação necessária.`);
|
|
|
|
|
}
|
|
|
|
|
// Se já existe, não faz nada, pois a lógica de atualização de status será em outro processo.
|
|
|
|
|
|
|
|
|
|
// Guarda o timestamp mais recente do lote para usar como a próxima marca d'água
|
2025-10-24 21:59:08 -03:00
|
|
|
let currentUpdate = type === 'incidente' ? ticket.sys_updated_on?.value : ticket.sys_updated_on;
|
|
|
|
|
currentUpdate = defaultDate(currentUpdate);
|
2025-10-23 17:30:03 -03:00
|
|
|
if (currentUpdate && currentUpdate > latestUpdateTimestamp) {
|
|
|
|
|
latestUpdateTimestamp = currentUpdate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logError(error, `Erro ao processar o ticket ${ticket.number?.value} do tipo ${type}.`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
logSync('ServiceNow', tickets.length, `${type}s`);
|
|
|
|
|
return latestUpdateTimestamp;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Controller para buscar tickets e requisições do ServiceNow e salvá-los localmente.
|
|
|
|
|
*/
|
|
|
|
|
const processSyncController = async () => {
|
|
|
|
|
try {
|
|
|
|
|
// 1. Pega a marca d'água da última execução bem-sucedida
|
|
|
|
|
const watermark = await SyncControlModel.getWatermark('servicenow');
|
|
|
|
|
logInfo(`📡 Buscando tickets do ServiceNow atualizados desde: ${watermark}`);
|
|
|
|
|
|
|
|
|
|
// Busca incidentes
|
2025-10-24 13:13:08 -03:00
|
|
|
|
2025-10-23 17:30:03 -03:00
|
|
|
const incidents = await fetchTicketsApi(watermark);
|
|
|
|
|
const latestIncidentUpdate = await processAndSaveTickets(incidents, 'incidente');
|
|
|
|
|
|
|
|
|
|
// Busca requisições
|
2025-10-24 13:13:08 -03:00
|
|
|
logInfo('📡 Buscando requisições 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
|
|
|
|
|
|
|
|
// 2. Determina a nova marca d'água (o timestamp mais recente entre todos os tickets processados)
|
|
|
|
|
const newWatermark = latestIncidentUpdate > latestRequestUpdate ? latestIncidentUpdate : latestRequestUpdate;
|
2025-10-24 21:59:08 -03:00
|
|
|
|
2025-10-23 17:30:03 -03:00
|
|
|
// 3. Se processamos algum ticket, atualiza a marca d'água para a próxima execução
|
|
|
|
|
if (newWatermark) {
|
|
|
|
|
// Adiciona 1 segundo para evitar reprocessar o último ticket em caso de timestamps iguais
|
|
|
|
|
const nextWatermark = new Date(new Date(newWatermark).getTime() + 1000);
|
|
|
|
|
await SyncControlModel.setWatermark('servicenow', nextWatermark);
|
|
|
|
|
} else {
|
|
|
|
|
logInfo("Nenhum ticket novo ou atualizado encontrado. A marca d'água não foi alterada.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logError(error, 'Erro no processo de sincronização de tickets do ServiceNow.');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-10-24 21:59:08 -03:00
|
|
|
|
|
|
|
|
//Função auxiliar para padronizar data
|
|
|
|
|
|
|
|
|
|
function defaultDate(dataStr) {
|
|
|
|
|
if (!dataStr) return null;
|
|
|
|
|
const [data, hora] = dataStr.split(' ');
|
|
|
|
|
|
|
|
|
|
let ano, mes, dia;
|
|
|
|
|
if (data.includes('/')) {
|
|
|
|
|
// Ex: 31/12/2024 → 2024-12-31
|
|
|
|
|
[dia, mes, ano] = data.split('/');
|
|
|
|
|
} else {
|
|
|
|
|
// Ex: 2025-10-24 → já está no formato
|
|
|
|
|
[ano, mes, dia] = data.split('-');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Garante formato consistente (YYYY-MM-DD HH:mm:ss)
|
|
|
|
|
return `${ano.padStart(4, '0')}-${mes.padStart(2, '0')}-${dia.padStart(2, '0')} ${hora}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-10-23 17:30:03 -03:00
|
|
|
module.exports = {
|
|
|
|
|
processSyncController
|
|
|
|
|
};
|