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:
Rafael Alves Lopes 2025-09-17 20:56:32 -03:00
parent ba7148154a
commit 07230dd1e4
10 changed files with 749 additions and 116 deletions

16
.env
View File

@ -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
View File

@ -5,3 +5,4 @@ config/*.json
*.tmp
servicenow_requests.json
servicenow_tickets.json
location_mapping.log

View 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;

View File

@ -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(),

View File

@ -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 = `
UPDATE tickets_sn
SET glpi_sync_status = $1,
glpi_ticket_id = $2,
glpi_sync_date = NOW()
WHERE id = $3
RETURNING id, ticket_number
SELECT * FROM tickets_sn
WHERE ticket_number = $1
`;
const result = await pool.query(query, [status, glpiTicketId, ticketId]);
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
});
}
const result = await pool.query(query, [ticketNumber]);
return result.rows[0] || null;
} catch (error) {
logError(error, `❌ Erro ao atualizar status de sync para ticket ${ticketId}`);
logError(error, `❌ Erro ao buscar ticket por número: ${ticketNumber}`);
return null;
}
}
// Coleta o status de sincronização para todos os tickets (uso em testes)
static async getSyncStatus() {
// Busca um ticket pelo ID
static async findById(ticketId) {
try {
const query = `
SELECT
glpi_sync_status,
COUNT(*) as total,
COUNT(glpi_ticket_id) as synchronized
FROM tickets_sn
GROUP BY glpi_sync_status
SELECT * FROM tickets_sn
WHERE id = $1
`;
const result = await pool.query(query);
return result.rows;
const result = await pool.query(query, [ticketId]);
return result.rows[0] || null;
} catch (error) {
logError(error, '❌ Erro ao verificar status de sync');
return [];
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 ${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;
}
}
}
@ -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;

View 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;

View File

@ -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;

View 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';

View 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")

View File

@ -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}`);
}
}
}
};