FEAT: Tickets agora estão sendo enviados para o GLPI
- Busca de tickets pendentes no banco de dados. - Mapeamento de dados para o formato do GLPI. - Criação/atualização de tickets no GLPI. - Filtro de tickets com status 'Encerrado' ou 'Encerrado - Omitido'. - Extração de informações (email, telefone, ramal) da descrição do ticket. - Verificação se ticket já não existe no GLPI com base no RITM ou INC
This commit is contained in:
parent
9926aee962
commit
ba7148154a
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,3 +3,5 @@ logs/*.log
|
|||||||
config/*.json
|
config/*.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.tmp
|
*.tmp
|
||||||
|
servicenow_requests.json
|
||||||
|
servicenow_tickets.json
|
||||||
28248
servicenow_requests.json
28248
servicenow_requests.json
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,17 +1,16 @@
|
|||||||
// src/models/ticketGlpiModel.js
|
|
||||||
const glpiPool = require('../data/glpiDataBase');
|
const glpiPool = require('../data/glpiDataBase');
|
||||||
const { logInfo, logError } = require('../utils/logger');
|
const { logInfo, logError } = require('../utils/logger');
|
||||||
|
|
||||||
class TicketGlpiModel {
|
class TicketGlpiModel {
|
||||||
|
|
||||||
// Mapeamento de status ServiceNow → GLPI
|
// Mapeamento de status do ServiceNow para o GLPI
|
||||||
static mapStatus(snState) {
|
static mapStatus(snState) {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'1': 1, // Aguardando Atendimento → Novo
|
'Aguardando Atendimento': 1,
|
||||||
'2': 2, // Em Andamento → Em andamento
|
'Em Atendimento': 2,
|
||||||
'3': 3, // Pendente → Pendente
|
'Em Espera': 4,
|
||||||
'6': 5, // Resolvido → Resolvido
|
'Encerrado': 5,
|
||||||
'7': 6 // Fechado → Fechado
|
'Encerrado - Omitido': 6
|
||||||
};
|
};
|
||||||
return statusMap[snState] || 1;
|
return statusMap[snState] || 1;
|
||||||
}
|
}
|
||||||
@ -19,11 +18,11 @@ class TicketGlpiModel {
|
|||||||
// Mapeamento de prioridade
|
// Mapeamento de prioridade
|
||||||
static mapPriority(snPriority) {
|
static mapPriority(snPriority) {
|
||||||
const priorityMap = {
|
const priorityMap = {
|
||||||
'1': 5, // Crítico → Muito alta
|
'1': 5,
|
||||||
'2': 4, // Alto → Alta
|
'2': 4,
|
||||||
'3': 3, // Moderado → Média
|
'3': 3,
|
||||||
'4': 2, // Baixo → Baixa
|
'4': 2,
|
||||||
'5': 1 // Planejamento → Muito baixa
|
'5': 1
|
||||||
};
|
};
|
||||||
return priorityMap[snPriority] || 3;
|
return priorityMap[snPriority] || 3;
|
||||||
}
|
}
|
||||||
@ -31,55 +30,146 @@ class TicketGlpiModel {
|
|||||||
// Mapeamento de categoria
|
// Mapeamento de categoria
|
||||||
static mapCategory(shortDescription) {
|
static mapCategory(shortDescription) {
|
||||||
const categoryMap = {
|
const categoryMap = {
|
||||||
'Problemas com Telefonia Fixa': 5717, // PABX IP Nuvem
|
'Problemas com Telefonia Fixa': 5717,
|
||||||
'Problemas com Internet': 5720, // Link Dedicado
|
'Problemas com Internet': 5707,
|
||||||
'Problemas com Wi-Fi/Rede Corporativa': 5722, // Wifi
|
'Problemas com Wi-Fi/Rede Corporativa': 5707,
|
||||||
'Wi-fi Clientes': 5722 // Wifi
|
'Wi-fi Clientes': 5707
|
||||||
};
|
};
|
||||||
return categoryMap[shortDescription] || 5717;
|
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';
|
||||||
|
|
||||||
// Criar ticket no GLPI
|
if (isRequisicao && isTelefonia) {
|
||||||
// src/models/ticketGlpiModel.js
|
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 async createTicket(ticketData) {
|
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<table style="width:100%; border-collapse: collapse;">
|
||||||
|
<tr style="background-color:#f2f2f2;">
|
||||||
|
<th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Campo</th>
|
||||||
|
<th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Valor</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Email:</strong></td>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;">${email}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Telefone:</strong></td>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;">${phone}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Ramal:</strong></td>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;">${ramal}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Ticket Number (ServiceNow):</strong></td>
|
||||||
|
<td style="padding: 8px; border: 1px solid #ddd;">${ticketData.ticket_number || 'N/A'}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<strong>Descrição:</strong>
|
||||||
|
<hr>
|
||||||
|
${ticketData.description || 'N/A'}
|
||||||
|
`;
|
||||||
|
return htmlDescription;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cria um ticket no GLPI
|
||||||
|
static async createTicket(ticketData) {
|
||||||
const connection = await glpiPool.getConnection();
|
const connection = await glpiPool.getConnection();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await connection.beginTransaction();
|
await connection.beginTransaction();
|
||||||
|
|
||||||
// 1. Preparar dados para o GLPI (usando valores fixos para teste)
|
// 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 = {
|
const glpiTicketData = {
|
||||||
entities_id: 1371, // Valor fixo para teste
|
entities_id: 1371,
|
||||||
name: ticketData.short_description || 'Sem título',
|
name: ticketData.short_description || 'Ticket sem descrição',
|
||||||
date: ticketData.opened_at || new Date(),
|
date: ticketData.opened_at || new Date(),
|
||||||
date_mod: ticketData.updated_at || new Date(),
|
date_mod: ticketData.updated_at || new Date(),
|
||||||
status: TicketGlpiModel.mapStatus(ticketData.state || '1'),
|
status: TicketGlpiModel.mapStatus(ticketData.state || '1'),
|
||||||
users_id_recipient: 917, // Valor fixo para teste
|
users_id_recipient: 917,
|
||||||
requesttypes_id: 1,
|
requesttypes_id: 1,
|
||||||
content: ticketData.description || '',
|
content: formattedDescription,
|
||||||
urgency: 3, // Valor fixo
|
urgency: 3,
|
||||||
impact: 3, // Valor fixo
|
impact: 3,
|
||||||
priority: TicketGlpiModel.mapPriority(ticketData.priority || '3'),
|
priority: TicketGlpiModel.mapPriority(ticketData.priority || '3'),
|
||||||
type: 1, // Incidente
|
type: ticketType,
|
||||||
itilcategories_id: TicketGlpiModel.mapCategory(ticketData.short_description),
|
itilcategories_id: TicketGlpiModel.mapCategory(ticketData.short_description),
|
||||||
global_validation: 1,
|
global_validation: 1,
|
||||||
date_creation: ticketData.opened_at || new Date()
|
date_creation: ticketData.opened_at || new Date(),
|
||||||
|
slas_id_ttr: slasIdTtr
|
||||||
};
|
};
|
||||||
|
|
||||||
// Construir a query dinamicamente e inserir o ticket principal
|
// Constrói a query dinamicamente e insere o ticket principal
|
||||||
const fields = Object.keys(glpiTicketData).join(', ');
|
const fields = Object.keys(glpiTicketData).join(', ');
|
||||||
const placeholders = Object.keys(glpiTicketData).map(() => '?').join(', ');
|
const placeholders = Object.keys(glpiTicketData).map(() => '?').join(', ');
|
||||||
const values = Object.values(glpiTicketData);
|
const values = Object.values(glpiTicketData);
|
||||||
|
|
||||||
const ticketQuery = `INSERT INTO glpi_tickets (${fields}) VALUES (${placeholders})`;
|
const ticketQuery = `INSERT INTO glpi_tickets (${fields}) VALUES (${placeholders})`;
|
||||||
|
|
||||||
// Inserir ticket principal e obter insertId
|
|
||||||
const [ticketResult] = await connection.execute(ticketQuery, values);
|
const [ticketResult] = await connection.execute(ticketQuery, values);
|
||||||
const ticketId = ticketResult.insertId;
|
const ticketId = ticketResult.insertId;
|
||||||
|
|
||||||
// Adicionar requerente (usa ticketId agora que foi criado)
|
// Adiciona o requerente
|
||||||
await connection.execute(
|
await connection.execute(
|
||||||
`INSERT INTO glpi_tickets_users
|
`INSERT INTO glpi_tickets_users
|
||||||
(tickets_id, users_id, type, use_notification, alternative_email)
|
(tickets_id, users_id, type, use_notification, alternative_email)
|
||||||
@ -87,7 +177,7 @@ static async createTicket(ticketData) {
|
|||||||
[ticketId, 917]
|
[ticketId, 917]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Adicionar grupo
|
// Adiciona o grupo
|
||||||
await connection.execute(
|
await connection.execute(
|
||||||
`INSERT INTO glpi_groups_tickets
|
`INSERT INTO glpi_groups_tickets
|
||||||
(tickets_id, groups_id, type)
|
(tickets_id, groups_id, type)
|
||||||
@ -107,9 +197,9 @@ static async createTicket(ticketData) {
|
|||||||
} finally {
|
} finally {
|
||||||
connection.release();
|
connection.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar se ticket já existe no GLPI
|
// Verifica se um ticket já existe no GLPI
|
||||||
static async ticketExists(snTicketNumber) {
|
static async ticketExists(snTicketNumber) {
|
||||||
try {
|
try {
|
||||||
const query = `
|
const query = `
|
||||||
@ -131,4 +221,18 @@ static async createTicket(ticketData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
module.exports = TicketGlpiModel;
|
||||||
@ -4,14 +4,16 @@ const { logInfo, logError, logWarning } = require('../utils/logger');
|
|||||||
|
|
||||||
class TicketSnModel {
|
class TicketSnModel {
|
||||||
|
|
||||||
|
// Salva ou atualiza um ticket no banco de dados
|
||||||
static async saveTicket(ticketData, typeTicket) {
|
static async saveTicket(ticketData, typeTicket) {
|
||||||
const query = `
|
const query = `
|
||||||
INSERT INTO tickets_sn
|
INSERT INTO tickets_sn
|
||||||
(ticket_number, short_description, status, description, caller_id, caller_email,
|
(ticket_number, sys_id, short_description, status, description, caller_id, caller_email,
|
||||||
location_id, location_name, ramal, telefone, opened_at, tipo)
|
location_id, location_name, ramal, telefone, opened_at, tipo)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
||||||
ON CONFLICT (ticket_number)
|
ON CONFLICT (ticket_number)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
|
sys_id = EXCLUDED.sys_id,
|
||||||
short_description = EXCLUDED.short_description,
|
short_description = EXCLUDED.short_description,
|
||||||
status = EXCLUDED.status,
|
status = EXCLUDED.status,
|
||||||
description = EXCLUDED.description,
|
description = EXCLUDED.description,
|
||||||
@ -27,23 +29,25 @@ class TicketSnModel {
|
|||||||
RETURNING id, xmax = 0 as inserted`;
|
RETURNING id, xmax = 0 as inserted`;
|
||||||
|
|
||||||
const values = [
|
const values = [
|
||||||
ticketData.number?.value || null, // $1 - ticket_number
|
ticketData.number?.value || null,
|
||||||
ticketData.short_description?.value || null, // $2 - short_description
|
ticketData.sys_id?.value || null,
|
||||||
ticketData.state?.display_value || null, // $3 - status
|
ticketData.short_description?.value || null,
|
||||||
ticketData.description?.value || null, // $4 - description
|
ticketData.state?.display_value || null,
|
||||||
ticketData.opened_by?.display_value || null, // $5 - caller_id
|
ticketData.description?.value || null,
|
||||||
ticketData['opened_by.email']?.value || null, // $6 - caller_email
|
ticketData.opened_by?.display_value || null,
|
||||||
ticketData.location?.value || null, // $7 - location_id
|
ticketData.sys_created_by?.display_value || null,
|
||||||
ticketData.location?.display_value || null, // $8 - location_name
|
ticketData.location?.value || null,
|
||||||
ticketData['variables.numero_ramal']?.value || null, // $9 - ramal
|
ticketData.location?.display_value || null,
|
||||||
ticketData['variables.telephone_favorecido_rh']?.value || null, // $10 - telefone
|
ticketData['variables.numero_ramal']?.value || null,
|
||||||
ticketData.opened_at?.value ? new Date(ticketData.opened_at.value) : null, // $11 - opened_at
|
ticketData['variables.telephone_favorecido_rh']?.value || null,
|
||||||
typeTicket // $12 - tipo
|
ticketData.opened_at?.value ? new Date(ticketData.opened_at.value) : null,
|
||||||
|
typeTicket
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(query, values);
|
const result = await pool.query(query, values);
|
||||||
const wasInserted = result.rows[0].inserted; // true = INSERT, false = UPDATE
|
const wasInserted = result.rows[0].inserted;
|
||||||
|
|
||||||
if (wasInserted) {
|
if (wasInserted) {
|
||||||
logInfo(`✅ NOVO ticket salvo! ID: ${result.rows[0].id}`, {
|
logInfo(`✅ NOVO ticket salvo! ID: ${result.rows[0].id}`, {
|
||||||
@ -66,8 +70,8 @@ class TicketSnModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOVO: Buscar tickets pendentes para sincronização com GLPI
|
// Busca tickets que ainda não foram sincronizados com o GLPI
|
||||||
static async getPendingTickets(limit = 3) {
|
static async getPendingTickets() {
|
||||||
try {
|
try {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
@ -88,11 +92,11 @@ class TicketSnModel {
|
|||||||
updated_at
|
updated_at
|
||||||
FROM tickets_sn
|
FROM tickets_sn
|
||||||
WHERE glpi_sync_status IS NULL
|
WHERE glpi_sync_status IS NULL
|
||||||
ORDER BY opened_at ASC
|
AND status NOT IN ('Encerrado', 'Encerrado - Omitido')
|
||||||
LIMIT $1
|
ORDER BY opened_at
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await pool.query(query, [limit]);
|
const result = await pool.query(query);
|
||||||
logInfo(`📋 Tickets pendentes encontrados: ${result.rows.length}`);
|
logInfo(`📋 Tickets pendentes encontrados: ${result.rows.length}`);
|
||||||
return result.rows;
|
return result.rows;
|
||||||
|
|
||||||
@ -102,7 +106,7 @@ class TicketSnModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOVO: Atualizar status de sincronização com GLPI
|
// Atualiza o status de sincronização de um ticket específico // Inativo
|
||||||
static async updateSyncStatus(ticketId, status, glpiTicketId = null) {
|
static async updateSyncStatus(ticketId, status, glpiTicketId = null) {
|
||||||
try {
|
try {
|
||||||
const query = `
|
const query = `
|
||||||
@ -130,33 +134,8 @@ class TicketSnModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOVO: Resetar status de sync para testes (opcional)
|
|
||||||
static async resetSyncStatus() {
|
|
||||||
try {
|
|
||||||
const query = `
|
|
||||||
UPDATE tickets_sn
|
|
||||||
SET glpi_sync_status = NULL,
|
|
||||||
glpi_ticket_id = NULL,
|
|
||||||
glpi_sync_date = NULL
|
|
||||||
WHERE id IN (
|
|
||||||
SELECT id FROM tickets_sn
|
|
||||||
ORDER BY opened_at DESC
|
|
||||||
LIMIT $1
|
|
||||||
)
|
|
||||||
RETURNING id, ticket_number
|
|
||||||
`;
|
|
||||||
|
|
||||||
const result = await pool.query(query, [limit]);
|
// Coleta o status de sincronização para todos os tickets (uso em testes)
|
||||||
logInfo(`🔄 Status resetado para ${result.rows.length} tickets`);
|
|
||||||
return result.rows;
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
logError(error, '❌ Erro ao resetar status de sync');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOVO: Verificar status de sincronização
|
|
||||||
static async getSyncStatus() {
|
static async getSyncStatus() {
|
||||||
try {
|
try {
|
||||||
const query = `
|
const query = `
|
||||||
@ -178,4 +157,15 @@ class TicketSnModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module TicketSnModel
|
||||||
|
* @description Este módulo fornece métodos para interagir com a tabela de tickets do ServiceNow (tickets_sn) no banco de dados.
|
||||||
|
* Inclui funcionalidades para salvar um ticket, buscar tickets pendentes para sincronização com o GLPI,
|
||||||
|
* atualizar o status de sincronização de um ticket, resetar o status de sincronização para testes e verificar o status geral de sincronização.
|
||||||
|
*
|
||||||
|
* A função saveTicket insere ou atualiza um ticket na tabela tickets_sn.
|
||||||
|
* A função getPendingTickets busca tickets que ainda não foram sincronizados com o GLPI e ignora os tickets com status 'Encerrado' ou 'Encerrado - Omitido'.
|
||||||
|
* A função updateSyncStatus atualiza o status de sincronização de um ticket específico.
|
||||||
|
* A função getSyncStatus retorna um resumo do status de sincronização dos tickets.
|
||||||
|
*/
|
||||||
module.exports = TicketSnModel;
|
module.exports = TicketSnModel;
|
||||||
@ -1,6 +1,7 @@
|
|||||||
CREATE TABLE tickets_sn (
|
CREATE TABLE tickets_sn (
|
||||||
id SERIAL PRIMARY KEY, -- ID automático (1, 2, 3...)
|
id SERIAL PRIMARY KEY, -- ID automático (1, 2, 3...)
|
||||||
ticket_number VARCHAR(32) NOT NULL UNIQUE, -- Número do ticket (INC0140378) - ÚNICO
|
ticket_number VARCHAR(32) NOT NULL UNIQUE, -- Número do ticket (INC0140378) - ÚNICO
|
||||||
|
sys_id VARCHAR(64) NOT NULL UNIQUE, -- Sys ID do ticket no ServiceNow - ÚNICO
|
||||||
short_description TEXT, -- Resumo do problema
|
short_description TEXT, -- Resumo do problema
|
||||||
tipo VARCHAR(10), -- Requisição ou incidente
|
tipo VARCHAR(10), -- Requisição ou incidente
|
||||||
status VARCHAR(55), -- Resumo do problema
|
status VARCHAR(55), -- Resumo do problema
|
||||||
@ -14,13 +15,14 @@ CREATE TABLE tickets_sn (
|
|||||||
opened_at TIMESTAMP, -- Quando o ticket foi aberto
|
opened_at TIMESTAMP, -- Quando o ticket foi aberto
|
||||||
created_at TIMESTAMP DEFAULT NOW(), -- Quando registramos no BD
|
created_at TIMESTAMP DEFAULT NOW(), -- Quando registramos no BD
|
||||||
updated_at TIMESTAMP DEFAULT NOW(), -- Quando atualizamos pela última vez
|
updated_at TIMESTAMP DEFAULT NOW(), -- Quando atualizamos pela última vez
|
||||||
glpi_sync_status VARCHAR(20),
|
glpi_sync_status VARCHAR(20), -- Status da sincronização com GLPI
|
||||||
glpi_ticket_id INT
|
glpi_ticket_id INT, -- ID do ticket no GLPI
|
||||||
|
glpi_sync_date TIMESTAMP -- Data da sincronização com GLPI
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SELECT * FROM tickets_sn;
|
SELECT * FROM tickets_sn;
|
||||||
|
|
||||||
SELECT * FROM tickets_sn LIMIT 3;
|
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,17 @@
|
|||||||
// src/services/glpiService.js
|
// src/services/glpiService.js
|
||||||
const TicketSnModel = require('../models/ticketSnModel'); // SEU MODEL do PostgreSQL
|
const TicketSnModel = require('../models/ticketSnModel');
|
||||||
const TicketGlpiModel = require('../models/ticketGlpiModel'); // Model do GLPI
|
const TicketGlpiModel = require('../models/ticketGlpiModel');
|
||||||
const { logInfo, logError } = require('../utils/logger');
|
const { logInfo, logError } = require('../utils/logger');
|
||||||
|
|
||||||
class GlpiService {
|
// Método principal que o controller vai chamar
|
||||||
|
const syncTicketsToGlpi = async () => {
|
||||||
// Método principal que o controller vai chamar
|
|
||||||
static async syncTicketsToGlpi() {
|
|
||||||
try {
|
try {
|
||||||
logInfo('🔄 Iniciando sincronização para GLPI...');
|
logInfo('🔄 Iniciando sincronização para GLPI...');
|
||||||
|
|
||||||
// 1. Buscar tickets pendentes usando SEU MODEL
|
// 1. Buscar tickets pendentes usando SEU MODEL
|
||||||
const pendingTickets = await TicketSnModel.getPendingTickets();
|
const pendingTickets = await TicketSnModel.getPendingTickets();
|
||||||
|
|
||||||
|
|
||||||
if (pendingTickets.length === 0) {
|
if (pendingTickets.length === 0) {
|
||||||
logInfo('✅ Nenhum ticket pendente para sincronizar');
|
logInfo('✅ Nenhum ticket pendente para sincronizar');
|
||||||
return;
|
return;
|
||||||
@ -22,7 +21,7 @@ class GlpiService {
|
|||||||
|
|
||||||
// 2. Processar cada ticket
|
// 2. Processar cada ticket
|
||||||
for (const ticket of pendingTickets) {
|
for (const ticket of pendingTickets) {
|
||||||
await this.processSingleTicket(ticket);
|
await processSingleTicket(ticket);
|
||||||
}
|
}
|
||||||
|
|
||||||
logInfo('🎉 Sincronização com GLPI concluída!');
|
logInfo('🎉 Sincronização com GLPI concluída!');
|
||||||
@ -31,10 +30,10 @@ class GlpiService {
|
|||||||
logError(error, '❌ Erro na sincronização GLPI');
|
logError(error, '❌ Erro na sincronização GLPI');
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
// Processar um ticket individual
|
// Processar um ticket individual
|
||||||
static async processSingleTicket(ticket) {
|
const processSingleTicket = async (ticket) => {
|
||||||
try {
|
try {
|
||||||
logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`);
|
logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`);
|
||||||
|
|
||||||
@ -45,18 +44,21 @@ class GlpiService {
|
|||||||
await TicketSnModel.updateSyncStatus(ticket.id, 'duplicate', existingTicket);
|
await TicketSnModel.updateSyncStatus(ticket.id, 'duplicate', existingTicket);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Converter dados para formato GLPI (O MODEL vai fazer o mapeamento)
|
// 2. Converter dados para formato GLPI (O MODEL vai fazer o mapeamento)
|
||||||
const glpiData = {
|
const glpiData = {
|
||||||
// Dados básicos do PostgreSQL
|
// Dados básicos do PostgreSQL
|
||||||
ticket_number: ticket.ticket_number,
|
ticket_number: ticket.ticket_number,
|
||||||
short_description: ticket.short_description,
|
short_description: ticket.short_description,
|
||||||
description: ticket.description,
|
description: ticket.description,
|
||||||
|
caller_email: ticket.caller_email,
|
||||||
|
caller_name: ticket.caller_id,
|
||||||
|
caller_phone: ticket.telefone,
|
||||||
|
caller_ramal: ticket.ramal,
|
||||||
|
category: ticket.category,
|
||||||
|
type: ticket.tipo, // incidente ou requisicao
|
||||||
|
|
||||||
// Dados do ServiceNow originais (se necessário para mapeamento)
|
// Dados do ServiceNow originais (se necessário para mapeamento)
|
||||||
state: ticket.status, // Já vem do ServiceNow
|
state: ticket.status, // Já vem do ServiceNow
|
||||||
urgency: '2', // Default - O MODEL vai mapear
|
|
||||||
impact: '2', // Default - O MODEL vai mapear
|
|
||||||
priority: '3', // Default - O MODEL vai mapear
|
priority: '3', // Default - O MODEL vai mapear
|
||||||
|
|
||||||
// Localização para de/para
|
// Localização para de/para
|
||||||
@ -79,14 +81,8 @@ class GlpiService {
|
|||||||
logError(error, `❌ Erro no ticket ${ticket.ticket_number}`);
|
logError(error, `❌ Erro no ticket ${ticket.ticket_number}`);
|
||||||
await TicketSnModel.updateSyncStatus(ticket.id, 'error');
|
await TicketSnModel.updateSyncStatus(ticket.id, 'error');
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const syncTicketsToGlpi = async () => {
|
|
||||||
await GlpiService.syncTicketsToGlpi();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
syncTicketsToGlpi,
|
syncTicketsToGlpi
|
||||||
GlpiService
|
|
||||||
};
|
};
|
||||||
@ -13,7 +13,7 @@ const fetchTicketsFromServiceNow = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// comentado para evitar muitos arquivos
|
// comentado para evitar muitos arquivos
|
||||||
fs.writeFileSync('servicenow_tickets.json', JSON.stringify(ticketsResponse.data, null, 2));
|
//fs.writeFileSync('servicenow_tickets.json', JSON.stringify(ticketsResponse.data, null, 2));
|
||||||
//logInfo('💾 Backup JSON de incidentes salvo', { count: ticketsResponse.data.result.length });
|
//logInfo('💾 Backup JSON de incidentes salvo', { count: ticketsResponse.data.result.length });
|
||||||
|
|
||||||
// Salvar os tickets no banco de dados
|
// Salvar os tickets no banco de dados
|
||||||
@ -38,7 +38,7 @@ const fetchRequestsFromServiceNow = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Comentado para evitar muitos arquivos
|
// Comentado para evitar muitos arquivos
|
||||||
fs.writeFileSync('servicenow_requests.json', JSON.stringify(requestsResponse.data, null, 2));
|
//fs.writeFileSync('servicenow_requests.json', JSON.stringify(requestsResponse.data, null, 2));
|
||||||
//logInfo('💾 Backup JSON de requisições salvo', { count: requestsResponse.data.result.length });
|
//logInfo('💾 Backup JSON de requisições salvo', { count: requestsResponse.data.result.length });
|
||||||
|
|
||||||
// Salvar as requisições no banco de dados
|
// Salvar as requisições no banco de dados
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user