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');
|
2025-10-23 17:30:03 -03:00
|
|
|
const { logInfo, logError, logSync } = require('../utils/logger');
|
|
|
|
|
|
|
|
|
|
/**
|
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) {
|
|
|
|
|
logInfo(`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 {
|
2025-10-28 14:06:00 -03:00
|
|
|
if (type === 'requisicao' && (!ticket.description || ticket.description.trim() === '')) {
|
2026-02-19 11:21:40 -03:00
|
|
|
logInfo(`Descricao vazia para requisicao ${ticket.number}. Buscando em sc_item_option...`);
|
2025-10-28 14:06:00 -03:00
|
|
|
const extraData = await fetchScItemOptionValue(ticket.sys_id, 'justificativa');
|
|
|
|
|
const vars = {};
|
|
|
|
|
(extraData || []).forEach(item => {
|
|
|
|
|
const question = item['sc_item_option.item_option_new.question_text'];
|
|
|
|
|
const value = item['sc_item_option.value'];
|
|
|
|
|
|
|
|
|
|
if (question === 'Select what you want') vars.whatYouWant = value;
|
|
|
|
|
if (question === 'Please justify the need') vars.justification = value;
|
|
|
|
|
if (question === 'Telephone') vars.telephone = value;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (vars.justification) {
|
2025-10-29 14:20:21 -03:00
|
|
|
ticket.description = vars.justification;
|
2026-02-19 11:21:40 -03:00
|
|
|
logInfo(`Descricao da requisicao ${ticket.number} atualizada com valor de sc_item_option.`);
|
2025-10-28 14:06:00 -03:00
|
|
|
}
|
|
|
|
|
if (vars.telephone){
|
|
|
|
|
ticket.telefone = vars.telephone;
|
2026-02-19 11:21:40 -03:00
|
|
|
logInfo(`Telefone da requisicao ${ticket.number} atualizado com valor de sc_item_option.`);
|
2025-10-28 14:06:00 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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) {
|
|
|
|
|
await TicketSyncModel.updateSourceLast(idTableTicketSn, 'SNOW');
|
2025-10-28 14:06:00 -03:00
|
|
|
logInfo(`Ticket ${ticket.number} atualizado pelo ServiceNow. 'source_last' definido como 'SNOW'.`);
|
2025-10-23 17:30:03 -03:00
|
|
|
} else {
|
2026-02-19 11:21:40 -03:00
|
|
|
logInfo(`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');
|
2026-02-19 11:21:40 -03:00
|
|
|
logInfo(`INFO: 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');
|
|
|
|
|
|
2026-02-19 11:21:40 -03:00
|
|
|
logInfo('INFO: 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 {
|
2026-02-19 11:21:40 -03:00
|
|
|
logInfo("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.
|
2025-10-29 14:20:21 -03:00
|
|
|
*/
|