const glpiPool = require('../data/glpiDataBase');
const { logInfo, logError } = require('../utils/logger');
class TicketGlpiModel {
// Mapeamento de status do ServiceNow para o GLPI
static mapStatus(snState) {
const statusMap = {
'Aguardando Atendimento': 1,
'Em Atendimento': 2,
'Em Espera': 4,
'Encerrado': 5,
'Encerrado - Omitido': 6
};
return statusMap[snState] || 1;
}
// Mapeamento de prioridade
static mapPriority(snPriority) {
const priorityMap = {
'1': 5,
'2': 4,
'3': 3,
'4': 2,
'5': 1
};
return priorityMap[snPriority] || 3;
}
// Mapeamento de categoria
static mapCategory(shortDescription) {
const categoryMap = {
'Problemas com Telefonia Fixa': 5717,
'Problemas com Internet': 5707,
'Problemas com Wi-Fi/Rede Corporativa': 5707,
'Wi-fi Clientes': 5707
};
return categoryMap[shortDescription] || 5717;
}
// Mapeamento de SLA baseado no tipo e na descrição
static mapSlasIdTtr(ticketData) {
const isRequisicao = ticketData.type === 'requisicao';
const isTelefonia = ticketData.short_description === 'Problemas com Telefonia Fixa' || 'Solicitações relacionadas a telefonia fixa';
const isLinkDedicado = ticketData.short_description === 'Problemas com Internet';
const isWifi = ticketData.short_description === 'Problemas com Wi-Fi/Rede Corporativa' || ticketData.short_description === 'Wi-fi Clientes';
if (isRequisicao && isTelefonia) {
return 37;
}
if (isRequisicao && (isLinkDedicado || isWifi)) {
return 37;
}
if (!isRequisicao && (isWifi || isLinkDedicado)) {
return 34;
}
if (!isRequisicao && isTelefonia) {
return 34;
}
return 0; // Default se nenhum critério for atendido
}
static formatDescription(ticketData) {
// Função auxiliar para extrair informações usando regex
const extractInfo = (regex, description) => {
if (!description) return null;
const match = description.match(regex);
return match ? match[1] : null;
};
const extractEmailFromDescription = (description) => {
return extractInfo(/([\w-\.]+@([\w-]+\.)+[\w-]{2,4})/i, description);
};
const extractPhoneFromDescription = (description) => {
return extractInfo(/(\+?(\d{2})?\s*\(?\d{2}\)?[\s-]*\d{4,5}[\s-]?\d{4})/i, description);
};
const extractRamalFromDescription = (description) => {
return extractInfo(/(\d{3,4})/i, description);
};
// Obtém email, telefone e ramal, usando os valores do ticketData ou extraindo da descrição, se necessário.
const email = ticketData.caller_email || extractEmailFromDescription(ticketData.description) || 'N/A';
const phone = ticketData.caller_phone || extractPhoneFromDescription(ticketData.description) || 'N/A';
const ramal = ticketData.caller_ramal || extractRamalFromDescription(ticketData.description) || 'N/A';
// Formata a descrição em HTML
let htmlDescription = `
| Campo |
Valor |
| Email: |
${email} |
| Telefone: |
${phone} |
| Ramal: |
${ramal} |
| Ticket Number (ServiceNow): |
${ticketData.ticket_number || 'N/A'} |
Descrição:
${ticketData.description || 'N/A'}
`;
return htmlDescription;
}
// Cria um ticket no GLPI
static async createTicket(ticketData) {
const connection = await glpiPool.getConnection();
try {
await connection.beginTransaction();
// Extrai o email da descrição, se existir e não houver email no ticketData
let email = ticketData.caller_email;
if (!email && ticketData.description) {
const emailRegex = /[\w-\.]+@([\w-]+\.)+[\w-]{2,4}/g;
const matches = ticketData.description.match(emailRegex);
if (matches && matches.length > 0) {
email = matches[0];
}
}
// Formata a descrição para HTML
const formattedDescription = TicketGlpiModel.formatDescription(ticketData);
// Mapeia o tipo do ticket
const ticketType = ticketData.type === 'requisicao' ? 2 : 1;
// Determina o valor de slas_id_ttr
const slasIdTtr = TicketGlpiModel.mapSlasIdTtr(ticketData);
// Prepara os dados para o GLPI
const glpiTicketData = {
entities_id: 1371,
name: ticketData.short_description || 'Ticket sem descrição',
date: ticketData.opened_at || new Date(),
date_mod: ticketData.updated_at || new Date(),
status: TicketGlpiModel.mapStatus(ticketData.state || '1'),
users_id_recipient: 917,
requesttypes_id: 1,
content: formattedDescription,
urgency: 3,
impact: 3,
priority: TicketGlpiModel.mapPriority(ticketData.priority || '3'),
type: ticketType,
itilcategories_id: TicketGlpiModel.mapCategory(ticketData.short_description),
global_validation: 1,
date_creation: ticketData.opened_at || new Date(),
slas_id_ttr: slasIdTtr
};
// Constrói a query dinamicamente e insere o ticket principal
const fields = Object.keys(glpiTicketData).join(', ');
const placeholders = Object.keys(glpiTicketData).map(() => '?').join(', ');
const values = Object.values(glpiTicketData);
const ticketQuery = `INSERT INTO glpi_tickets (${fields}) VALUES (${placeholders})`;
const [ticketResult] = await connection.execute(ticketQuery, values);
const ticketId = ticketResult.insertId;
// Adiciona o requerente
await connection.execute(
`INSERT INTO glpi_tickets_users
(tickets_id, users_id, type, use_notification, alternative_email)
VALUES (?, ?, 1, 1, '')`,
[ticketId, 917]
);
// Adiciona o grupo
await connection.execute(
`INSERT INTO glpi_groups_tickets
(tickets_id, groups_id, type)
VALUES (?, ?, 2)`,
[ticketId, 30]
);
await connection.commit();
logInfo(`✅ Ticket GLPI criado: ${ticketId}`);
return ticketId;
} catch (error) {
await connection.rollback();
logError(error, `❌ Erro ao criar ticket GLPI`);
throw error;
} finally {
connection.release();
}
}
// Verifica se um ticket já existe no GLPI
static async ticketExists(snTicketNumber) {
try {
const query = `
SELECT id FROM glpi_tickets
WHERE name LIKE ? OR content LIKE ?
LIMIT 1
`;
const [rows] = await glpiPool.execute(query, [
`%${snTicketNumber}%`,
`%${snTicketNumber}%`
]);
return rows.length > 0 ? rows[0].id : null;
} catch (error) {
logError(error, 'Erro ao verificar ticket existente');
return null;
}
}
}
/**
* @module TicketGlpiModel
* @description Este módulo fornece métodos para gerenciar tickets no GLPI.
* Inclui funcionalidades para mapear status, prioridade e categoria,
* formatar a descrição do ticket, determinar o SLA, criar tickets e
* verificar se um ticket já existe no GLPI.
*
* O módulo usa expressões regulares para extrair informações da descrição do ticket,
* como email, número de telefone e ramal, quando essas informações não são
* fornecidas diretamente. Também formata a descrição em HTML para melhor
* apresentação no GLPI.
*
* O módulo interage com o banco de dados GLPI através da conexão glpiPool.
*/
module.exports = TicketGlpiModel;