snglpi/src/models/ticketSnModel.js

247 lines
9.6 KiB
JavaScript
Raw Normal View History

const pool = require('../data/database');
const { logInfo, logError } = require('../utils/logger');
2025-08-30 19:04:35 -03:00
class TicketSnModel {
static async saveTicket(ticketData, typeTicket) {
const query = `
INSERT INTO tickets_sn
(ticket_number, sys_id, short_description, status, description, caller_id, caller_email,
location_id, location_name, ramal, telefone, justificativa, opened_at, tipo)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
ON CONFLICT (ticket_number)
DO UPDATE SET
sys_id = EXCLUDED.sys_id,
short_description = EXCLUDED.short_description,
status = EXCLUDED.status,
description = EXCLUDED.description,
caller_id = EXCLUDED.caller_id,
caller_email = EXCLUDED.caller_email,
location_id = EXCLUDED.location_id,
location_name = EXCLUDED.location_name,
ramal = EXCLUDED.ramal,
telefone = EXCLUDED.telefone,
justificativa = EXCLUDED.justificativa,
opened_at = EXCLUDED.opened_at,
2025-10-13 06:06:52 -03:00
tipo = EXCLUDED.tipo
RETURNING id, (xmax = 0) AS "wasInserted"`;
2025-08-30 19:04:35 -03:00
const ticketNumber = typeof ticketData.number === 'object' ? ticketData.number?.value : ticketData.number;
let callerName, callerEmail, ramal, telefone, justificativa, sysId, shortDescription, state, description, locationId, locationName, openedAt;
if (typeTicket === 'incidente') {
callerName = ticketData.caller_id?.display_value;
const emailObject = ticketData['caller_id.email'] || {};
callerEmail = (typeof emailObject === 'object' ? emailObject.value : emailObject) || null;
ramal = null; // Incidentes não têm essa variável por padrão
telefone = ticketData['caller_id.phone'] || ticketData['caller_id.mobile_phone'] || null; // Incidentes não têm essa variável por padrão
sysId = ticketData.sys_id || null;
shortDescription = ticketData.short_description || null;
state = ticketData.state || null;
description = ticketData.description || null;
justificativa = null;
const locationLink = ticketData.location?.link;
locationId = locationLink ? locationLink.substring(locationLink.lastIndexOf('/') + 1) : null;
locationName = ticketData.location?.display_value || null;
openedAt = ticketData.opened_at || null;
} else { // 'requisicao'
callerName = ticketData.opened_by?.display_value || null;
callerEmail = ticketData['opened_by.email'] || null;
ramal = ticketData['variables.numero_ramal'] || null;
telefone = ticketData['variables.telephone_favorecido_rh'] || ticketData['variables.telephone_favorecido_ti'] || ticketData.telefone || null;
sysId = ticketData.sys_id || null;
shortDescription = ticketData.short_description || null;
state = ticketData.state || null;
description = ticketData.description || null;
justificativa = ticketData.justificativa || null;
const locationLink = ticketData.location?.link;
locationId = locationLink ? locationLink.substring(locationLink.lastIndexOf('/') + 1) : null;
locationName = ticketData.location?.display_value || null;
openedAt = ticketData.opened_at || null;
}
const values = [
ticketNumber,
sysId,
shortDescription,
state,
description,
callerName,
callerEmail,
locationId,
locationName,
ramal,
telefone,
justificativa,
openedAt
? (typeof openedAt === 'string' && openedAt.includes('/')
? new Date(openedAt.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$2-$1'))
: new Date(openedAt))
: null,
typeTicket
];
try {
const result = await pool.query(query, values);
const wasInserted = result.rows[0].wasInserted;
if (wasInserted) {
logInfo(`✅ NOVO ticket salvo! ID: ${result.rows[0].id}`, {
ticket_id: result.rows[0].id,
ticket_number: values[0],
action: 'insert'
});
} else {
logInfo(`🔄 Ticket atualizado! ID: ${result.rows[0].id}`, {
ticket_id: result.rows[0].id,
ticket_number: values[0],
action: 'update'
});
}
return { id: result.rows[0].id, wasInserted };
} catch (error) {
logError(error, `❌ Erro ao salvar ticket ${values[0]}`);
throw error;
}
}
2025-08-30 19:04:35 -03:00
static async getPendingTickets() {
try {
const query = `
SELECT
tsync.id AS ticket_sync_id,
ts.id,
ts.ticket_number,
ts.short_description,
ts.description,
ts.status,
ts.caller_id,
ts.caller_email,
ts.location_id,
ts.location_name,
ts.ramal,
ts.telefone,
ts.justificativa,
ts.opened_at,
ts.tipo,
ts.created_at,
ts.updated_at,
tsync.glpi_ticket_id
FROM tickets_sn ts
LEFT JOIN ticket_sync tsync ON ts.id = tsync.sn_ticket_id
2025-10-13 06:06:52 -03:00
WHERE tsync.glpi_sync_status = 'pending_check'
ORDER BY ts.opened_at
`;
const result = await pool.query(query);
logInfo(`📋 Tickets pendentes encontrados: ${result.rows.length}`);
return result.rows;
} catch (error) {
logError(error, '❌ Erro ao buscar tickets pendentes');
return [];
}
}
static async findByTicketNumber(ticketNumber) {
try {
const query = `
SELECT * FROM tickets_sn
WHERE ticket_number = $1
`;
const result = await pool.query(query, [ticketNumber]);
return result.rows[0] || null;
} catch (error) {
logError(error, `❌ Erro ao buscar ticket por número: ${ticketNumber}`);
return null;
}
}
static async getStatus(ticketId) {
try {
const query = `
SELECT status,tipo FROM tickets_sn
WHERE id = $1`;
const result = await pool.query(query, [ticketId]);
return result.rows || null;
} catch (error) {
logError(error, `❌ Erro ao buscar status do ticket: ${ticketId}`);
return null;
}
}
static async findById(ticketId) {
try {
const query = `
SELECT * FROM tickets_sn
WHERE id = $1
`;
const result = await pool.query(query, [ticketId]);
return result.rows[0] || null;
} catch (error) {
logError(error, `❌ Erro ao buscar ticket por ID: ${ticketId}`);
return null;
}
}
static async updateTicket(ticketId, updateData) {
try {
const fields = [];
const values = [];
let paramCount = 1;
for (const [key, value] of Object.entries(updateData)) {
fields.push(`${key} = $${paramCount}`);
values.push(value);
paramCount++;
}
fields.push(`updated_at = NOW()`);
values.push(ticketId);
const query = `
UPDATE tickets_sn
SET ${fields.join(', ')}
WHERE id = $${paramCount}
RETURNING *
`;
const result = await pool.query(query, values);
if (result.rows.length > 0) {
logInfo(`✅ Ticket atualizado: ${ticketId}`);
return result.rows[0];
}
return null;
} catch (error) {
logError(error, `❌ Erro ao atualizar ticket ${ticketId}`);
throw error;
}
}
}
/**
* @module TicketSnModel
* @description Este módulo serve como a camada de acesso a dados para a tabela `tickets_sn`, que armazena uma cópia local dos dados dos tickets do ServiceNow.
* Manter uma cópia local otimiza as consultas e reduz a dependência de chamadas de API repetitivas ao ServiceNow.
*
* Principais Funcionalidades:
* - `saveTicket(ticketData, typeTicket)`: Insere um novo ticket ou atualiza um existente (UPSERT) na tabela `tickets_sn`. Ele mapeia de forma inteligente os campos de incidentes e requisições, que possuem estruturas diferentes na API do ServiceNow. Retorna se o registro foi inserido ou atualizado.
* - `getPendingTickets()`: Busca todos os tickets que estão no estado 'pending_check', indicando que foram coletados do ServiceNow mas ainda não foram processados para criação no GLPI.
* - Funções de Busca (`findByTicketNumber`, `findById`, `getStatus`): Métodos para recuperar registros de tickets ou seus status com base em diferentes identificadores.
* - `updateTicket(ticketId, updateData)`: Atualiza dinamicamente os campos de um registro de ticket existente.
*/
module.exports = TicketSnModel;