FEAT: Entidades mapeadas a partir do CSV localizado na pasta tecnica
- Tabelas criadas para mapear entidades e sincronizar atualizações - Atualização do banco de dados para melhor leitura e espaço para melhorias - Atualização dos modelos para melhor sincronia com banco de dados - Adicionada variaveis ao .ENV - Criado sistema que efetua leitura do csv e aplica no banco de dados, revisando o conteudo do GLPI e do banco.
This commit is contained in:
parent
ba7148154a
commit
07230dd1e4
16
.env
16
.env
@ -18,12 +18,16 @@ GLPI_DB_NAME=glpi_data
|
||||
GLPI_DB_CHARSET=utf8mb4
|
||||
|
||||
|
||||
# Database Configuration
|
||||
DB_HOST=localhost
|
||||
DB_PORT=3306 # or 5432 for PostgreSQL
|
||||
DB_USER=your_database_user
|
||||
DB_PASSWORD=your_database_password
|
||||
DB_NAME=your_database_name
|
||||
# Banco SNGLPI (PostgreSQL)
|
||||
SNGLPI_DB_HOST=10.0.120.75
|
||||
SNGLPI_DB_PORT=5432
|
||||
SNGLPI_DB_NAME=snglpi
|
||||
SNGLPI_DB_USER=desenvolvimento
|
||||
SNGLPI_DB_PASSWORD=Ut@2S@$M9Xs@@W
|
||||
|
||||
# Caminho do CSV
|
||||
LOCATION_MAPPING_CSV_PATH=\\10.0.121.40\Tecnica\Controle CAOA\ENTIDADE SERVICE NOW\LOCATION_MAPPING.csv
|
||||
|
||||
|
||||
# Logging Configuration
|
||||
LOG_LEVEL=info
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ config/*.json
|
||||
*.tmp
|
||||
servicenow_requests.json
|
||||
servicenow_tickets.json
|
||||
location_mapping.log
|
||||
52
src/models/locationMappingModel.js
Normal file
52
src/models/locationMappingModel.js
Normal file
@ -0,0 +1,52 @@
|
||||
// src/models/locationMappingModel.js
|
||||
const pool = require('../data/database');
|
||||
const { logInfo, logError } = require('../utils/logger');
|
||||
|
||||
class LocationMappingModel {
|
||||
|
||||
// Buscar entity_id do GLPI baseado no location_id do ServiceNow
|
||||
static async getGlpiEntityId(snLocationId) {
|
||||
try {
|
||||
const query = `
|
||||
SELECT glpi_entity_id
|
||||
FROM location_mapping
|
||||
WHERE sn_location_id = $1
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [snLocationId]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
return result.rows[0].glpi_entity_id;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
logError(error, `Erro ao buscar entity_id para location_id: ${snLocationId}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Buscar entity_id do GLPI baseado no location_name do ServiceNow
|
||||
static async getGlpiEntityIdByName(snLocationName) {
|
||||
try {
|
||||
const query = `
|
||||
SELECT glpi_entity_id
|
||||
FROM location_mapping
|
||||
WHERE sn_location_name = $1
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [snLocationName]);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
return result.rows[0].glpi_entity_id;
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
logError(error, `Erro ao buscar entity_id para location_name: ${snLocationName}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LocationMappingModel;
|
||||
@ -1,5 +1,7 @@
|
||||
// src/models/ticketGlpiModel.js
|
||||
const glpiPool = require('../data/glpiDataBase');
|
||||
const { logInfo, logError } = require('../utils/logger');
|
||||
const LocationMappingModel = require('./locationMappingModel');
|
||||
|
||||
class TicketGlpiModel {
|
||||
|
||||
@ -41,9 +43,10 @@ class TicketGlpiModel {
|
||||
// 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';
|
||||
const shortDesc = ticketData.short_description;
|
||||
const isTelefonia = shortDesc === 'Problemas com Telefonia Fixa' || shortDesc === 'Solicitações relacionadas a telefonia fixa';
|
||||
const isLinkDedicado = shortDesc === 'Problemas com Internet';
|
||||
const isWifi = shortDesc === 'Problemas com Wi-Fi/Rede Corporativa' || shortDesc === 'Wi-fi Clientes';
|
||||
|
||||
if (isRequisicao && isTelefonia) {
|
||||
return 37;
|
||||
@ -60,8 +63,6 @@ class TicketGlpiModel {
|
||||
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) => {
|
||||
@ -91,6 +92,10 @@ class TicketGlpiModel {
|
||||
<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>Nome:</strong></td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">${ticketData.caller_id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Email:</strong></td>
|
||||
<td style="padding: 8px; border: 1px solid #ddd;">${email}</td>
|
||||
@ -123,6 +128,15 @@ class TicketGlpiModel {
|
||||
try {
|
||||
await connection.beginTransaction();
|
||||
|
||||
// Buscar entity_id com base no location_id ou location_name
|
||||
let entityId = 1371; // Valor padrão (fallback)
|
||||
|
||||
if (ticketData.location_id) {
|
||||
entityId = await LocationMappingModel.getGlpiEntityId(ticketData.location_id) || entityId;
|
||||
} else if (ticketData.location_name) {
|
||||
entityId = await LocationMappingModel.getGlpiEntityIdByName(ticketData.location_name) || entityId;
|
||||
}
|
||||
|
||||
// Extrai o email da descrição, se existir e não houver email no ticketData
|
||||
let email = ticketData.caller_email;
|
||||
if (!email && ticketData.description) {
|
||||
@ -142,7 +156,7 @@ class TicketGlpiModel {
|
||||
|
||||
// Prepara os dados para o GLPI
|
||||
const glpiTicketData = {
|
||||
entities_id: 1371,
|
||||
entities_id: entityId, // Usar o entity_id mapeado
|
||||
name: ticketData.short_description || 'Ticket sem descrição',
|
||||
date: ticketData.opened_at || new Date(),
|
||||
date_mod: ticketData.updated_at || new Date(),
|
||||
|
||||
@ -75,25 +75,26 @@ class TicketSnModel {
|
||||
try {
|
||||
const query = `
|
||||
SELECT
|
||||
id,
|
||||
ticket_number,
|
||||
short_description,
|
||||
description,
|
||||
status,
|
||||
caller_id,
|
||||
caller_email,
|
||||
location_id,
|
||||
location_name,
|
||||
ramal,
|
||||
telefone,
|
||||
opened_at,
|
||||
tipo,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM tickets_sn
|
||||
WHERE glpi_sync_status IS NULL
|
||||
AND status NOT IN ('Encerrado', 'Encerrado - Omitido')
|
||||
ORDER BY opened_at
|
||||
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.opened_at,
|
||||
ts.tipo,
|
||||
ts.created_at,
|
||||
ts.updated_at
|
||||
FROM tickets_sn ts
|
||||
LEFT JOIN ticket_sync tsync ON ts.id = tsync.sn_ticket_id
|
||||
WHERE tsync.id IS NULL
|
||||
AND ts.status NOT IN ('Encerrado', 'Encerrado - Omitido')
|
||||
ORDER BY ts.opened_at
|
||||
`;
|
||||
|
||||
const result = await pool.query(query);
|
||||
@ -106,53 +107,79 @@ class TicketSnModel {
|
||||
}
|
||||
}
|
||||
|
||||
// Atualiza o status de sincronização de um ticket específico // Inativo
|
||||
static async updateSyncStatus(ticketId, status, glpiTicketId = null) {
|
||||
// Busca um ticket pelo número
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Busca um ticket pelo ID
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Atualiza os dados de um ticket
|
||||
static async updateTicket(ticketId, updateData) {
|
||||
try {
|
||||
const fields = [];
|
||||
const values = [];
|
||||
let paramCount = 1;
|
||||
|
||||
// Construir dinamicamente a query baseada nos campos fornecidos
|
||||
for (const [key, value] of Object.entries(updateData)) {
|
||||
fields.push(`${key} = $${paramCount}`);
|
||||
values.push(value);
|
||||
paramCount++;
|
||||
}
|
||||
|
||||
// Adicionar updated_at
|
||||
fields.push(`updated_at = NOW()`);
|
||||
|
||||
// Adicionar o ID no final dos valores
|
||||
values.push(ticketId);
|
||||
|
||||
const query = `
|
||||
UPDATE tickets_sn
|
||||
SET glpi_sync_status = $1,
|
||||
glpi_ticket_id = $2,
|
||||
glpi_sync_date = NOW()
|
||||
WHERE id = $3
|
||||
RETURNING id, ticket_number
|
||||
SET ${fields.join(', ')}
|
||||
WHERE id = $${paramCount}
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [status, glpiTicketId, ticketId]);
|
||||
const result = await pool.query(query, values);
|
||||
|
||||
if (result.rows.length > 0) {
|
||||
logInfo(`🔄 Status atualizado: ${status} para ticket ${result.rows[0].ticket_number}`, {
|
||||
ticket_id: result.rows[0].id,
|
||||
ticket_number: result.rows[0].ticket_number,
|
||||
glpi_ticket_id: glpiTicketId,
|
||||
status: status
|
||||
});
|
||||
logInfo(`✅ Ticket atualizado: ${ticketId}`);
|
||||
return result.rows[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
} catch (error) {
|
||||
logError(error, `❌ Erro ao atualizar status de sync para ticket ${ticketId}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Coleta o status de sincronização para todos os tickets (uso em testes)
|
||||
static async getSyncStatus() {
|
||||
try {
|
||||
const query = `
|
||||
SELECT
|
||||
glpi_sync_status,
|
||||
COUNT(*) as total,
|
||||
COUNT(glpi_ticket_id) as synchronized
|
||||
FROM tickets_sn
|
||||
GROUP BY glpi_sync_status
|
||||
`;
|
||||
|
||||
const result = await pool.query(query);
|
||||
return result.rows;
|
||||
|
||||
} catch (error) {
|
||||
logError(error, '❌ Erro ao verificar status de sync');
|
||||
return [];
|
||||
logError(error, `❌ Erro ao atualizar ticket ${ticketId}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -161,11 +188,10 @@ 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.
|
||||
* e buscar/atualizar tickets específicos.
|
||||
*
|
||||
* 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.
|
||||
* A função getPendingTickets busca tickets que ainda não foram sincronizados com o GLPI (não possuem registro em ticket_sync)
|
||||
* e ignora os tickets com status 'Encerrado' ou 'Encerrado - Omitido'.
|
||||
*/
|
||||
module.exports = TicketSnModel;
|
||||
120
src/models/ticketSyncModel.js
Normal file
120
src/models/ticketSyncModel.js
Normal file
@ -0,0 +1,120 @@
|
||||
// src/models/ticketSyncModel.js
|
||||
const pool = require('../data/database');
|
||||
const { logInfo, logError } = require('../utils/logger');
|
||||
|
||||
class TicketSyncModel {
|
||||
|
||||
// Cria um registro de sincronização
|
||||
static async createSyncRecord(snTicketId, glpiTicketId = null) {
|
||||
try {
|
||||
const query = `
|
||||
INSERT INTO ticket_sync (sn_ticket_id, glpi_ticket_id)
|
||||
VALUES ($1, $2)
|
||||
RETURNING *
|
||||
`;
|
||||
const result = await pool.query(query, [snTicketId, glpiTicketId]);
|
||||
logInfo(`✅ Registro de sincronização criado para ticket SN: ${snTicketId}, GLPI: ${glpiTicketId || 'null'}`);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logError(error, `❌ Erro ao criar registro de sincronização para ticket ${snTicketId}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Atualiza o status de sincronização com o GLPI
|
||||
static async updateGlpiSyncStatus(syncId, status, glpiTicketId = null) {
|
||||
try {
|
||||
const query = `
|
||||
UPDATE ticket_sync
|
||||
SET glpi_sync_status = $1,
|
||||
glpi_ticket_id = COALESCE($2, glpi_ticket_id),
|
||||
last_glpi_sync = NOW()
|
||||
WHERE id = $3
|
||||
RETURNING *
|
||||
`;
|
||||
const result = await pool.query(query, [status, glpiTicketId, syncId]);
|
||||
logInfo(`✅ Status GLPI atualizado para: ${status}, sync ID: ${syncId}`);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logError(error, `❌ Erro ao atualizar status GLPI para sync ${syncId}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Atualiza o status de sincronização com o ServiceNow
|
||||
static async updateSnSyncStatus(syncId, status) {
|
||||
try {
|
||||
const query = `
|
||||
UPDATE ticket_sync
|
||||
SET sn_sync_status = $1,
|
||||
last_sn_sync = NOW()
|
||||
WHERE id = $2
|
||||
RETURNING *
|
||||
`;
|
||||
const result = await pool.query(query, [status, syncId]);
|
||||
logInfo(`✅ Status SN atualizado para: ${status}, sync ID: ${syncId}`);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logError(error, `❌ Erro ao atualizar status SN para sync ${syncId}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Busca registro de sincronização por ticket SN
|
||||
static async findBySnTicketId(snTicketId) {
|
||||
try {
|
||||
const query = `SELECT * FROM ticket_sync WHERE sn_ticket_id = $1`;
|
||||
const result = await pool.query(query, [snTicketId]);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logError(error, `❌ Erro ao buscar sync por SN ticket ID: ${snTicketId}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Busca registro de sincronização por ticket GLPI
|
||||
static async findByGlpiTicketId(glpiTicketId) {
|
||||
try {
|
||||
const query = `SELECT * FROM ticket_sync WHERE glpi_ticket_id = $1`;
|
||||
const result = await pool.query(query, [glpiTicketId]);
|
||||
return result.rows[0];
|
||||
} catch (error) {
|
||||
logError(error, `❌ Erro ao buscar sync por GLPI ticket ID: ${glpiTicketId}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Busca todos os registros de sincronização com status pendente para GLPI
|
||||
static async findPendingGlpiSync() {
|
||||
try {
|
||||
const query = `
|
||||
SELECT * FROM ticket_sync
|
||||
WHERE glpi_sync_status = 'pending'
|
||||
ORDER BY created_at
|
||||
`;
|
||||
const result = await pool.query(query);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logError(error, '❌ Erro ao buscar sincronizações pendentes para GLPI');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Busca todos os registros de sincronização com status pendente para ServiceNow
|
||||
static async findPendingSnSync() {
|
||||
try {
|
||||
const query = `
|
||||
SELECT * FROM ticket_sync
|
||||
WHERE sn_sync_status = 'pending'
|
||||
ORDER BY created_at
|
||||
`;
|
||||
const result = await pool.query(query);
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
logError(error, '❌ Erro ao buscar sincronizações pendentes para ServiceNow');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TicketSyncModel;
|
||||
@ -1,28 +0,0 @@
|
||||
CREATE TABLE tickets_sn (
|
||||
id SERIAL PRIMARY KEY, -- ID automático (1, 2, 3...)
|
||||
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
|
||||
tipo VARCHAR(10), -- Requisição ou incidente
|
||||
status VARCHAR(55), -- Resumo do problema
|
||||
description TEXT, -- Descrição completa
|
||||
caller_id VARCHAR(255), -- Nome de quem abriu o ticket
|
||||
caller_email VARCHAR(255), -- Email do solicitante
|
||||
location_id VARCHAR(64), -- ID da localização
|
||||
location_name VARCHAR(255), -- Nome da localização
|
||||
ramal VARCHAR(100), -- Ramal do solicitante
|
||||
telefone VARCHAR(100), -- Telefone do solicitante
|
||||
opened_at TIMESTAMP, -- Quando o ticket foi aberto
|
||||
created_at TIMESTAMP DEFAULT NOW(), -- Quando registramos no BD
|
||||
updated_at TIMESTAMP DEFAULT NOW(), -- Quando atualizamos pela última vez
|
||||
glpi_sync_status VARCHAR(20), -- Status da sincronização com GLPI
|
||||
glpi_ticket_id INT, -- ID do ticket no GLPI
|
||||
glpi_sync_date TIMESTAMP -- Data da sincronização com GLPI
|
||||
|
||||
);
|
||||
|
||||
|
||||
|
||||
SELECT * FROM tickets_sn;
|
||||
|
||||
|
||||
249
src/scripts/database/scriptBD.sql
Normal file
249
src/scripts/database/scriptBD.sql
Normal file
@ -0,0 +1,249 @@
|
||||
-- =============================================
|
||||
-- BANCO DE DADOS: snglpi
|
||||
-- DESCRIÇÃO: Sistema de integração entre ServiceNow e GLPI
|
||||
-- OBS: Chamados sempre são abertos pelo ServiceNow
|
||||
-- =============================================
|
||||
|
||||
-- =============================================
|
||||
-- TABELA: tickets_sn
|
||||
-- DESCRIÇÃO: Armazena tickets importados do ServiceNow
|
||||
-- =============================================
|
||||
CREATE TABLE tickets_sn (
|
||||
id SERIAL PRIMARY KEY, -- ID único do registro no banco snglpi
|
||||
ticket_number VARCHAR(32) NOT NULL UNIQUE, -- Número do ticket no ServiceNow (ex: INC0140378)
|
||||
sys_id VARCHAR(64) NOT NULL UNIQUE, -- Identificador único do ticket no ServiceNow
|
||||
short_description TEXT, -- Descrição resumida do ticket
|
||||
tipo VARCHAR(10), -- Tipo do ticket: 'incidente' ou 'requisicao'
|
||||
status VARCHAR(55), -- Status atual do ticket no ServiceNow
|
||||
description TEXT, -- Descrição completa do ticket
|
||||
caller_id VARCHAR(255), -- Nome do solicitante do ticket
|
||||
caller_email VARCHAR(255), -- Email do solicitante
|
||||
location_id VARCHAR(64), -- ID da localização no ServiceNow
|
||||
location_name VARCHAR(255), -- Nome da localização no ServiceNow
|
||||
ramal VARCHAR(100), -- Ramal do solicitante
|
||||
telefone VARCHAR(100), -- Telefone do solicitante
|
||||
opened_at TIMESTAMP, -- Data e hora de abertura no ServiceNow
|
||||
created_at TIMESTAMP DEFAULT NOW(), -- Data e hora de criação do registro
|
||||
updated_at TIMESTAMP DEFAULT NOW() -- Data e hora da última atualização do registro
|
||||
);
|
||||
|
||||
-- Índices para melhor performance nas consultas frequentes
|
||||
CREATE INDEX idx_tickets_sn_status ON tickets_sn(status);
|
||||
CREATE INDEX idx_tickets_sn_tipo ON tickets_sn(tipo);
|
||||
CREATE INDEX idx_tickets_sn_opened_at ON tickets_sn(opened_at);
|
||||
CREATE INDEX idx_tickets_sn_location_id ON tickets_sn(location_id);
|
||||
CREATE INDEX idx_tickets_sn_location_name ON tickets_sn(location_name);
|
||||
|
||||
-- =============================================
|
||||
-- TABELA: ticket_sync
|
||||
-- DESCRIÇÃO: Controla o status de sincronização entre ServiceNow e GLPI
|
||||
-- OBS: Chamados sempre são criados no ServiceNow primeiro
|
||||
-- =============================================
|
||||
CREATE TABLE ticket_sync (
|
||||
id SERIAL PRIMARY KEY, -- ID único do registro de sincronização
|
||||
sn_ticket_id INTEGER NOT NULL, -- Referência ao ticket na tabela tickets_sn
|
||||
glpi_ticket_id INTEGER, -- ID do ticket correspondente no GLPI
|
||||
last_sn_sync TIMESTAMP, -- Data e hora da última sincronização com ServiceNow
|
||||
last_glpi_sync TIMESTAMP, -- Data e hora da última sincronização com GLPI
|
||||
sn_sync_status VARCHAR(20) DEFAULT 'pending',-- Status da sincronização com ServiceNow: 'pending', 'completed', 'error'
|
||||
glpi_sync_status VARCHAR(20) DEFAULT 'pending', -- Status da sincronização com GLPI: 'pending', 'completed', 'error'
|
||||
created_at TIMESTAMP DEFAULT NOW(), -- Data e hora de criação do registro
|
||||
updated_at TIMESTAMP DEFAULT NOW(), -- Data e hora da última atualização do registro
|
||||
|
||||
-- Chaves estrangeiras
|
||||
CONSTRAINT fk_ticket_sync_sn_ticket
|
||||
FOREIGN KEY (sn_ticket_id)
|
||||
REFERENCES tickets_sn(id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Índices para melhor performance nas consultas frequentes
|
||||
CREATE INDEX idx_ticket_sync_sn_ticket ON ticket_sync(sn_ticket_id);
|
||||
CREATE INDEX idx_ticket_sync_glpi_ticket ON ticket_sync(glpi_ticket_id);
|
||||
CREATE INDEX idx_ticket_sync_status ON ticket_sync(sn_sync_status, glpi_sync_status);
|
||||
|
||||
-- =============================================
|
||||
-- TABELA: ticket_updates
|
||||
-- DESCRIÇÃO: Armazena as atualizações e tarefas relacionadas aos tickets
|
||||
-- OBS: Atualizações podem vir de ambos os sistemas (ServiceNow e GLPI)
|
||||
-- =============================================
|
||||
CREATE TABLE ticket_updates (
|
||||
id SERIAL PRIMARY KEY, -- ID único da atualização
|
||||
ticket_sync_id INTEGER NOT NULL, -- Referência ao registro de sincronização
|
||||
update_type VARCHAR(20) NOT NULL, -- Tipo de atualização: 'task', 'note', 'status_change'
|
||||
source_system VARCHAR(10) NOT NULL, -- Sistema de origem: 'sn' (ServiceNow) ou 'glpi'
|
||||
content TEXT, -- Conteúdo da atualização/tarefa
|
||||
author VARCHAR(255), -- Autor da atualização
|
||||
created_at_source TIMESTAMP, -- Data e hora de criação no sistema de origem
|
||||
sync_status VARCHAR(20) DEFAULT 'pending', -- Status da sincronização: 'pending', 'completed', 'error'
|
||||
sync_date TIMESTAMP, -- Data e hora da sincronização
|
||||
created_at TIMESTAMP DEFAULT NOW(), -- Data e hora de criação do registro
|
||||
updated_at TIMESTAMP DEFAULT NOW(), -- Data e hora da última atualização do registro
|
||||
|
||||
-- Chaves estrangeiras
|
||||
CONSTRAINT fk_ticket_updates_sync
|
||||
FOREIGN KEY (ticket_sync_id)
|
||||
REFERENCES ticket_sync(id)
|
||||
ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Índices para melhor performance nas consultas frequentes
|
||||
CREATE INDEX idx_ticket_updates_sync ON ticket_updates(ticket_sync_id);
|
||||
CREATE INDEX idx_ticket_updates_status ON ticket_updates(sync_status);
|
||||
CREATE INDEX idx_ticket_updates_source ON ticket_updates(source_system);
|
||||
CREATE INDEX idx_ticket_updates_type ON ticket_updates(update_type);
|
||||
CREATE INDEX idx_ticket_updates_created ON ticket_updates(created_at_source);
|
||||
|
||||
-- =============================================
|
||||
-- TABELA: location_mapping
|
||||
-- DESCRIÇÃO: Mapeamento entre localizações do ServiceNow e entidades do GLPI
|
||||
-- =============================================
|
||||
CREATE TABLE location_mapping (
|
||||
id SERIAL PRIMARY KEY, -- ID único do mapeamento
|
||||
sn_location_id VARCHAR(255), -- ID da localização no ServiceNow
|
||||
sn_location_name VARCHAR(255) NOT NULL, -- Nome da localização no ServiceNow
|
||||
glpi_entity_id INTEGER NOT NULL, -- ID da entidade correspondente no GLPI
|
||||
glpi_entity_name VARCHAR(255) NOT NULL, -- Nome da entidade no GLPI
|
||||
created_at TIMESTAMP DEFAULT NOW(), -- Data e hora de criação do registro
|
||||
updated_at TIMESTAMP DEFAULT NOW() -- Data e hora da última atualização do registro
|
||||
);
|
||||
|
||||
-- Índices para melhor performance nas consultas frequentes
|
||||
CREATE INDEX idx_location_mapping_sn_id ON location_mapping(sn_location_id);
|
||||
CREATE INDEX idx_location_mapping_sn_name ON location_mapping(sn_location_name);
|
||||
CREATE INDEX idx_location_mapping_glpi_id ON location_mapping(glpi_entity_id);
|
||||
CREATE INDEX idx_location_mapping_glpi_name ON location_mapping(glpi_entity_name);
|
||||
|
||||
-- =============================================
|
||||
-- TRIGGERS para atualizar automaticamente updated_at
|
||||
-- =============================================
|
||||
|
||||
-- Função para atualizar o campo updated_at
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Trigger para tickets_sn
|
||||
CREATE TRIGGER trigger_tickets_sn_updated_at
|
||||
BEFORE UPDATE ON tickets_sn
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Trigger para ticket_sync
|
||||
CREATE TRIGGER trigger_ticket_sync_updated_at
|
||||
BEFORE UPDATE ON ticket_sync
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Trigger para ticket_updates
|
||||
CREATE TRIGGER trigger_ticket_updates_updated_at
|
||||
BEFORE UPDATE ON ticket_updates
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- Trigger para location_mapping
|
||||
CREATE TRIGGER trigger_location_mapping_updated_at
|
||||
BEFORE UPDATE ON location_mapping
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
-- =============================================
|
||||
-- VIEWS para consultas úteis
|
||||
-- =============================================
|
||||
|
||||
-- View para tickets pendentes de sincronização com GLPI
|
||||
CREATE VIEW view_tickets_pending_glpi AS
|
||||
SELECT
|
||||
ts.ticket_number,
|
||||
ts.short_description,
|
||||
ts.status,
|
||||
ts.tipo,
|
||||
ts.opened_at,
|
||||
tsync.created_at as sync_created
|
||||
FROM tickets_sn ts
|
||||
JOIN ticket_sync tsync ON ts.id = tsync.sn_ticket_id
|
||||
WHERE tsync.glpi_sync_status = 'pending';
|
||||
|
||||
-- View para tickets pendentes de sincronização com ServiceNow
|
||||
CREATE VIEW view_tickets_pending_sn AS
|
||||
SELECT
|
||||
ts.ticket_number,
|
||||
ts.short_description,
|
||||
ts.status,
|
||||
ts.tipo,
|
||||
ts.opened_at,
|
||||
tsync.created_at as sync_created
|
||||
FROM tickets_sn ts
|
||||
JOIN ticket_sync tsync ON ts.id = tsync.sn_ticket_id
|
||||
WHERE tsync.sn_sync_status = 'pending';
|
||||
|
||||
-- View para mapeamento de localizações completo
|
||||
CREATE VIEW view_location_mapping_complete AS
|
||||
SELECT
|
||||
sn_location_id,
|
||||
sn_location_name,
|
||||
glpi_entity_id,
|
||||
glpi_entity_name,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM location_mapping
|
||||
ORDER BY sn_location_name;
|
||||
|
||||
-- =============================================
|
||||
-- COMENTÁRIOS NAS TABELAS E COLUNAS
|
||||
-- =============================================
|
||||
|
||||
COMMENT ON TABLE tickets_sn IS 'Armazena tickets importados do ServiceNow';
|
||||
COMMENT ON COLUMN tickets_sn.id IS 'ID único do registro no banco snglpi';
|
||||
COMMENT ON COLUMN tickets_sn.ticket_number IS 'Número do ticket no ServiceNow (ex: INC0140378)';
|
||||
COMMENT ON COLUMN tickets_sn.sys_id IS 'Identificador único do ticket no ServiceNow';
|
||||
COMMENT ON COLUMN tickets_sn.short_description IS 'Descrição resumida do ticket';
|
||||
COMMENT ON COLUMN tickets_sn.tipo IS 'Tipo do ticket: incidente ou requisicao';
|
||||
COMMENT ON COLUMN tickets_sn.status IS 'Status atual do ticket no ServiceNow';
|
||||
COMMENT ON COLUMN tickets_sn.description IS 'Descrição completa do ticket';
|
||||
COMMENT ON COLUMN tickets_sn.caller_id IS 'Nome do solicitante do ticket';
|
||||
COMMENT ON COLUMN tickets_sn.caller_email IS 'Email do solicitante';
|
||||
COMMENT ON COLUMN tickets_sn.location_id IS 'ID da localização no ServiceNow';
|
||||
COMMENT ON COLUMN tickets_sn.location_name IS 'Nome da localização no ServiceNow';
|
||||
COMMENT ON COLUMN tickets_sn.ramal IS 'Ramal do solicitante';
|
||||
COMMENT ON COLUMN tickets_sn.telefone IS 'Telefone do solicitante';
|
||||
COMMENT ON COLUMN tickets_sn.opened_at IS 'Data e hora de abertura no ServiceNow';
|
||||
COMMENT ON COLUMN tickets_sn.created_at IS 'Data e hora de criação do registro';
|
||||
COMMENT ON COLUMN tickets_sn.updated_at IS 'Data e hora da última atualização do registro';
|
||||
|
||||
COMMENT ON TABLE ticket_sync IS 'Controla o status de sincronização entre ServiceNow e GLPI';
|
||||
COMMENT ON COLUMN ticket_sync.id IS 'ID único do registro de sincronização';
|
||||
COMMENT ON COLUMN ticket_sync.sn_ticket_id IS 'Referência ao ticket na tabela tickets_sn';
|
||||
COMMENT ON COLUMN ticket_sync.glpi_ticket_id IS 'ID do ticket correspondente no GLPI';
|
||||
COMMENT ON COLUMN ticket_sync.last_sn_sync IS 'Data e hora da última sincronização com ServiceNow';
|
||||
COMMENT ON COLUMN ticket_sync.last_glpi_sync IS 'Data e hora da última sincronização com GLPI';
|
||||
COMMENT ON COLUMN ticket_sync.sn_sync_status IS 'Status da sincronização com ServiceNow: pending, completed, error';
|
||||
COMMENT ON COLUMN ticket_sync.glpi_sync_status IS 'Status da sincronização com GLPI: pending, completed, error';
|
||||
COMMENT ON COLUMN ticket_sync.created_at IS 'Data e hora de criação do registro';
|
||||
COMMENT ON COLUMN ticket_sync.updated_at IS 'Data e hora da última atualização do registro';
|
||||
|
||||
COMMENT ON TABLE ticket_updates IS 'Armazena as atualizações e tarefas relacionadas aos tickets';
|
||||
COMMENT ON COLUMN ticket_updates.id IS 'ID único da atualização';
|
||||
COMMENT ON COLUMN ticket_updates.ticket_sync_id IS 'Referência ao registro de sincronização';
|
||||
COMMENT ON COLUMN ticket_updates.update_type IS 'Tipo de atualização: task, note, status_change';
|
||||
COMMENT ON COLUMN ticket_updates.source_system IS 'Sistema de origem: sn (ServiceNow) ou glpi';
|
||||
COMMENT ON COLUMN ticket_updates.content IS 'Conteúdo da atualização/tarefa';
|
||||
COMMENT ON COLUMN ticket_updates.author IS 'Autor da atualização';
|
||||
COMMENT ON COLUMN ticket_updates.created_at_source IS 'Data e hora de criação no sistema de origem';
|
||||
COMMENT ON COLUMN ticket_updates.sync_status IS 'Status da sincronização: pending, completed, error';
|
||||
COMMENT ON COLUMN ticket_updates.sync_date IS 'Data e hora da sincronização';
|
||||
COMMENT ON COLUMN ticket_updates.created_at IS 'Data e hora de criação do registro';
|
||||
COMMENT ON COLUMN ticket_updates.updated_at IS 'Data e hora da última atualização do registro';
|
||||
|
||||
COMMENT ON TABLE location_mapping IS 'Mapeamento entre localizações do ServiceNow e entidades do GLPI';
|
||||
COMMENT ON COLUMN location_mapping.id IS 'ID único do mapeamento';
|
||||
COMMENT ON COLUMN location_mapping.sn_location_id IS 'ID da localização no ServiceNow';
|
||||
COMMENT ON COLUMN location_mapping.sn_location_name IS 'Nome da localização no ServiceNow';
|
||||
COMMENT ON COLUMN location_mapping.glpi_entity_id IS 'ID da entidade correspondente no GLPI';
|
||||
COMMENT ON COLUMN location_mapping.glpi_entity_name IS 'Nome da entidade no GLPI';
|
||||
COMMENT ON COLUMN location_mapping.created_at IS 'Data e hora de criação do registro';
|
||||
COMMENT ON COLUMN location_mapping.updated_at IS 'Data e hora da última atualização do registro';
|
||||
184
src/scripts/python/update_location_mapping.py
Normal file
184
src/scripts/python/update_location_mapping.py
Normal file
@ -0,0 +1,184 @@
|
||||
# scripts/update_location_mapping.py
|
||||
import csv
|
||||
import psycopg2
|
||||
import mysql.connector
|
||||
from mysql.connector import Error
|
||||
import logging
|
||||
from datetime import datetime
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
import pandas as pd
|
||||
|
||||
# Carrega as variáveis do .env
|
||||
load_dotenv()
|
||||
|
||||
# Configuração de logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('location_mapping.log'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
|
||||
# Configurações de banco de dados a partir do .env
|
||||
SNGLPI_DB_CONFIG = {
|
||||
'host': os.getenv('SNGLPI_DB_HOST'),
|
||||
'port': os.getenv('SNGLPI_DB_PORT'),
|
||||
'database': os.getenv('SNGLPI_DB_NAME'),
|
||||
'user': os.getenv('SNGLPI_DB_USER'),
|
||||
'password': os.getenv('SNGLPI_DB_PASSWORD')
|
||||
}
|
||||
|
||||
GLPI_DB_CONFIG = {
|
||||
'host': os.getenv('GLPI_DB_HOST'),
|
||||
'port': os.getenv('GLPI_DB_PORT'),
|
||||
'database': os.getenv('GLPI_DB_NAME'),
|
||||
'user': os.getenv('GLPI_DB_USER'),
|
||||
'password': os.getenv('GLPI_DB_PASSWORD'),
|
||||
'charset': os.getenv('GLPI_DB_CHARSET', 'utf8mb4')
|
||||
}
|
||||
|
||||
# Caminho para o arquivo CSV
|
||||
CSV_PATH = os.getenv('LOCATION_MAPPING_CSV_PATH')
|
||||
|
||||
# Função para conectar ao banco SNGLPI
|
||||
def get_snglpi_connection():
|
||||
try:
|
||||
conn = psycopg2.connect(**SNGLPI_DB_CONFIG)
|
||||
return conn
|
||||
except Exception as e:
|
||||
logging.error(f"Erro ao conectar ao banco SNGLPI: {e}")
|
||||
return None
|
||||
|
||||
# Função para conectar ao banco GLPI
|
||||
def get_glpi_connection():
|
||||
try:
|
||||
conn = mysql.connector.connect(**GLPI_DB_CONFIG)
|
||||
return conn
|
||||
except Error as e:
|
||||
logging.error(f"Erro ao conectar ao banco GLPI: {e}")
|
||||
return None
|
||||
|
||||
# Função para buscar location_id no SNGLPI
|
||||
def find_sn_location_id(location_name, cursor):
|
||||
try:
|
||||
query = """
|
||||
SELECT DISTINCT location_id
|
||||
FROM tickets_sn
|
||||
WHERE location_name = %s
|
||||
LIMIT 1
|
||||
"""
|
||||
cursor.execute(query, (location_name,))
|
||||
result = cursor.fetchone()
|
||||
return result[0] if result else None
|
||||
except Exception as e:
|
||||
logging.error(f"Erro ao buscar location_id para {location_name}: {e}")
|
||||
return None
|
||||
|
||||
# Função para buscar entity_id no GLPI
|
||||
def find_glpi_entity_id(entity_name, cursor):
|
||||
try:
|
||||
query = """
|
||||
SELECT id
|
||||
FROM glpi_entities
|
||||
WHERE name = %s
|
||||
LIMIT 1
|
||||
"""
|
||||
cursor.execute(query, (entity_name,))
|
||||
result = cursor.fetchone()
|
||||
return result[0] if result else None
|
||||
except Exception as e:
|
||||
logging.error(f"Erro ao buscar entity_id para {entity_name}: {e}")
|
||||
return None
|
||||
|
||||
# Função principal para atualizar a tabela de mapeamento
|
||||
def update_mapping_table():
|
||||
try:
|
||||
# Verificar se o arquivo CSV existe
|
||||
if not os.path.exists(CSV_PATH):
|
||||
logging.error(f"Arquivo CSV não encontrado: {CSV_PATH}")
|
||||
return False
|
||||
|
||||
# Conectar aos bancos
|
||||
snglpi_conn = get_snglpi_connection()
|
||||
glpi_conn = get_glpi_connection()
|
||||
|
||||
if not snglpi_conn or not glpi_conn:
|
||||
return False
|
||||
|
||||
snglpi_cur = snglpi_conn.cursor()
|
||||
glpi_cur = glpi_conn.cursor()
|
||||
|
||||
# Ler CSV
|
||||
df = pd.read_csv(CSV_PATH, encoding='latin-1', sep=';')
|
||||
print(df.head())
|
||||
print(df.columns)
|
||||
print(df)
|
||||
# Adicionar colunas para IDs
|
||||
df['sn_location_id'] = None
|
||||
df['glpi_entity_id'] = None
|
||||
|
||||
|
||||
# Processar cada linha do CSV
|
||||
mappings = []
|
||||
for index, row in df.iterrows():
|
||||
sn_location_name = row['sn_location_name']
|
||||
glpi_entity_name = row['glpi_entity_name']
|
||||
|
||||
# Buscar IDs
|
||||
sn_id = find_sn_location_id(sn_location_name, snglpi_cur)
|
||||
glpi_id = find_glpi_entity_id(glpi_entity_name, glpi_cur)
|
||||
|
||||
# Preencher o DataFrame
|
||||
df.loc[index, 'sn_location_id'] = sn_id
|
||||
df.loc[index, 'glpi_entity_id'] = glpi_id
|
||||
|
||||
if glpi_id:
|
||||
# Se o glpi_entity_id for encontrado, adiciona para inserção
|
||||
mappings.append((sn_id, sn_location_name, glpi_id, glpi_entity_name))
|
||||
logging.debug(f"Mapeamento: {sn_location_name} -> {glpi_entity_name} (ID: {glpi_id})")
|
||||
else:
|
||||
logging.warning(f"Não foi possível encontrar entity_id para: {glpi_entity_name}")
|
||||
|
||||
# Atualizar tabela de mapeamento
|
||||
if mappings:
|
||||
# Limpar tabela existente
|
||||
snglpi_cur.execute("DELETE FROM location_mapping")
|
||||
|
||||
# Inserir novos mapeamentos
|
||||
insert_query = """
|
||||
INSERT INTO location_mapping
|
||||
(sn_location_id, sn_location_name, glpi_entity_id, glpi_entity_name)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
"""
|
||||
snglpi_cur.executemany(insert_query, mappings)
|
||||
snglpi_conn.commit()
|
||||
|
||||
logging.info(f"Tabela atualizada com {len(mappings)} mapeamentos")
|
||||
else:
|
||||
logging.warning("Nenhum mapeamento válido encontrado para atualização")
|
||||
|
||||
# Fechar conexões
|
||||
snglpi_cur.close()
|
||||
glpi_cur.close()
|
||||
snglpi_conn.close()
|
||||
glpi_conn.close()
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"Erro no processo de atualização: {e}")
|
||||
# Rollback em caso de erro
|
||||
if 'snglpi_conn' in locals():
|
||||
snglpi_conn.rollback()
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.info("Iniciando atualização de mapeamento de localizações")
|
||||
success = update_mapping_table()
|
||||
if success:
|
||||
logging.info("Atualização concluída com sucesso")
|
||||
else:
|
||||
logging.error("Falha na atualização")
|
||||
@ -1,6 +1,7 @@
|
||||
// src/services/glpiService.js
|
||||
const TicketSnModel = require('../models/ticketSnModel');
|
||||
const TicketGlpiModel = require('../models/ticketGlpiModel');
|
||||
const TicketSyncModel = require('../models/ticketSyncModel');
|
||||
const { logInfo, logError } = require('../utils/logger');
|
||||
|
||||
// Método principal que o controller vai chamar
|
||||
@ -8,10 +9,9 @@ const syncTicketsToGlpi = async () => {
|
||||
try {
|
||||
logInfo('🔄 Iniciando sincronização para GLPI...');
|
||||
|
||||
// 1. Buscar tickets pendentes usando SEU MODEL
|
||||
// 1. Buscar tickets pendentes
|
||||
const pendingTickets = await TicketSnModel.getPendingTickets();
|
||||
|
||||
|
||||
if (pendingTickets.length === 0) {
|
||||
logInfo('✅ Nenhum ticket pendente para sincronizar');
|
||||
return;
|
||||
@ -34,6 +34,8 @@ const syncTicketsToGlpi = async () => {
|
||||
|
||||
// Processar um ticket individual
|
||||
const processSingleTicket = async (ticket) => {
|
||||
let syncRecord = null;
|
||||
|
||||
try {
|
||||
logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`);
|
||||
|
||||
@ -41,45 +43,54 @@ const processSingleTicket = async (ticket) => {
|
||||
const existingTicket = await TicketGlpiModel.ticketExists(ticket.ticket_number);
|
||||
if (existingTicket) {
|
||||
logInfo(`⏭️ Ticket já existe no GLPI: ${existingTicket}`);
|
||||
await TicketSnModel.updateSyncStatus(ticket.id, 'duplicate', existingTicket);
|
||||
// Criar registro de sincronização indicando que já existe
|
||||
syncRecord = await TicketSyncModel.createSyncRecord(ticket.id, existingTicket);
|
||||
await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'duplicate', existingTicket);
|
||||
return;
|
||||
}
|
||||
// 2. Converter dados para formato GLPI (O MODEL vai fazer o mapeamento)
|
||||
|
||||
// 2. Converter dados para formato GLPI
|
||||
const glpiData = {
|
||||
// Dados básicos do PostgreSQL
|
||||
ticket_number: ticket.ticket_number,
|
||||
short_description: ticket.short_description,
|
||||
description: ticket.description,
|
||||
caller_id: ticket.caller_id,
|
||||
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)
|
||||
state: ticket.status, // Já vem do ServiceNow
|
||||
priority: '3', // Default - O MODEL vai mapear
|
||||
|
||||
// Localização para de/para
|
||||
type: ticket.tipo,
|
||||
state: ticket.status,
|
||||
priority: '3',
|
||||
location_id: ticket.location_id,
|
||||
location_name: ticket.location_name,
|
||||
|
||||
// Datas
|
||||
opened_at: ticket.opened_at,
|
||||
updated_at: ticket.updated_at
|
||||
};
|
||||
|
||||
// 3. Inserir no GLPI (O MODEL faz INSERT e mapeamento)
|
||||
// 3. Inserir no GLPI
|
||||
const glpiTicketId = await TicketGlpiModel.createTicket(glpiData);
|
||||
|
||||
// 4. Atualizar status no PostgreSQL usando SEU MODEL
|
||||
await TicketSnModel.updateSyncStatus(ticket.id, 'completed', glpiTicketId);
|
||||
// 4. Criar registro de sincronização
|
||||
syncRecord = await TicketSyncModel.createSyncRecord(ticket.id, glpiTicketId);
|
||||
await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'completed', glpiTicketId);
|
||||
|
||||
logInfo(`✅ Ticket ${ticket.ticket_number} sincronizado! GLPI ID: ${glpiTicketId}`);
|
||||
|
||||
} catch (error) {
|
||||
logError(error, `❌ Erro no ticket ${ticket.ticket_number}`);
|
||||
await TicketSnModel.updateSyncStatus(ticket.id, 'error');
|
||||
|
||||
// Se temos um registro de sync, atualizar o status para error
|
||||
if (syncRecord) {
|
||||
await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'error');
|
||||
} else {
|
||||
// Se não temos um registro, criar um com status error
|
||||
try {
|
||||
syncRecord = await TicketSyncModel.createSyncRecord(ticket.id);
|
||||
await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'error');
|
||||
} catch (innerError) {
|
||||
logError(innerError, `❌ Erro ao registrar falha de sincronização para ticket ${ticket.id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user