snglpi/src/controllers/processSyncController.js

160 lines
8.1 KiB
JavaScript
Raw Normal View History

const TicketSnModel = require('../models/ticketSnModel');
const TicketSyncModel = require('../models/ticketSyncModel');
const SyncControlModel = require('../models/syncControlModel');
const { fetchTicketsFromServiceNow: fetchTicketsApi, fetchRequestsFromServiceNow: fetchRequestsApi, fetchScItemOptionValue } = require('../services/servicenowService');
const { logInfo, logError, logSync } = require('../utils/logger');
const e = require('express');
/**
* 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) => {
// Helper para converter a data do formato 'DD/MM/YYYY HH:mm:ss' para um objeto Date.
const parseServiceNowDate = (dateString) => {
if (!dateString) return null;
// Verifica se já está em um formato reconhecível (como ISO 8601)
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
// Formato: new Date(ano, mês-1, dia, hora, minuto, segundo)
return new Date(parts[3], parts[2] - 1, parts[1], parts[4], parts[5], parts[6]);
};
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 = new Date(0); // Inicia com a data mínima (epoch)
for (const ticket of tickets) {
try {
// --- LÓGICA DE ENRIQUECIMENTO DA DESCRIÇÃO ---
// Se for uma requisição e a descrição estiver vazia, busca na sc_item_option.
if (type === 'requisicao' && (!ticket.description || ticket.description.trim() === '')) {
logInfo(`Descrição vazia para requisição ${ticket.number}. Buscando em sc_item_option...`);
// O nome 'justificativa' é um exemplo, use o nome real da sua variável.
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) {
ticket.description = vars.justification; // Atualiza o objeto do ticket em memória
logInfo(`Descrição da requisição ${ticket.number} atualizada com valor de sc_item_option.`);
}
if (vars.telephone){
ticket.telefone = vars.telephone;
logInfo(`Telefone da requisição ${ticket.number} atualizado com valor de sc_item_option.`);
}
}
// --- FIM DA LÓGICA DE ENRIQUECIMENTO ---
// 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'
// Para requisições, o status está diretamente em ticket.state
const ticketStatus = ticket.state;
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';
dataSync.glpi_sync_status = 'ignored'; // Ignora no GLPI se já veio fechado do SN
} else {
dataSync.sn_sync_status = 'collected'; // Status que indica que foi coletado do SN
dataSync.glpi_sync_status = 'pending_check'; // Status que indica que precisa ser checkado no GLPI
}
dataSync.source_last = 'SNOW'; // A última atualização veio do ServiceNow
// 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} 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
const currentUpdateDate = parseServiceNowDate(ticket.sys_updated_on);
if (currentUpdateDate && currentUpdateDate.getTime() > latestUpdateTimestamp.getTime()) {
latestUpdateTimestamp = currentUpdateDate;
}
} catch (error) {
logError(error, `Erro ao processar o ticket ${ticket.number?.value} do tipo ${type}.`);
}
}
logSync('ServiceNow', tickets.length, `${type}s`);
// Se encontramos uma data válida, a formata para 'YYYY-MM-DD HH:MM:SS'. Senão, retorna string vazia.
return latestUpdateTimestamp.getTime() > 0
? latestUpdateTimestamp.toISOString().slice(0, 19).replace('T', ' ')
: '';
};
/**
* 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
const incidents = await fetchTicketsApi(watermark);
const latestIncidentUpdate = await processAndSaveTickets(incidents, 'incidente');
// Busca requisições
logInfo('📡 Buscando requisições do ServiceNow...');
const requests = await fetchRequestsApi(watermark);
const latestRequestUpdate = await processAndSaveTickets(requests, 'requisicao');
// 2. Determina a nova marca d'água (o timestamp mais recente entre todos os tickets processados)
const newWatermark = latestIncidentUpdate > latestRequestUpdate ? latestIncidentUpdate : latestRequestUpdate;
// 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 sixHoursInMillis = 6 * 60 * 60 * 1000;
const nextWatermark = new Date(new Date(newWatermark).getTime() - sixHoursInMillis);
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.');
}
};
module.exports = {
processSyncController
};