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
|
GLPI_DB_CHARSET=utf8mb4
|
||||||
|
|
||||||
|
|
||||||
# Database Configuration
|
# Banco SNGLPI (PostgreSQL)
|
||||||
DB_HOST=localhost
|
SNGLPI_DB_HOST=10.0.120.75
|
||||||
DB_PORT=3306 # or 5432 for PostgreSQL
|
SNGLPI_DB_PORT=5432
|
||||||
DB_USER=your_database_user
|
SNGLPI_DB_NAME=snglpi
|
||||||
DB_PASSWORD=your_database_password
|
SNGLPI_DB_USER=desenvolvimento
|
||||||
DB_NAME=your_database_name
|
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
|
# Logging Configuration
|
||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,3 +5,4 @@ config/*.json
|
|||||||
*.tmp
|
*.tmp
|
||||||
servicenow_requests.json
|
servicenow_requests.json
|
||||||
servicenow_tickets.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 glpiPool = require('../data/glpiDataBase');
|
||||||
const { logInfo, logError } = require('../utils/logger');
|
const { logInfo, logError } = require('../utils/logger');
|
||||||
|
const LocationMappingModel = require('./locationMappingModel');
|
||||||
|
|
||||||
class TicketGlpiModel {
|
class TicketGlpiModel {
|
||||||
|
|
||||||
@ -41,9 +43,10 @@ class TicketGlpiModel {
|
|||||||
// Mapeamento de SLA baseado no tipo e na descrição
|
// Mapeamento de SLA baseado no tipo e na descrição
|
||||||
static mapSlasIdTtr(ticketData) {
|
static mapSlasIdTtr(ticketData) {
|
||||||
const isRequisicao = ticketData.type === 'requisicao';
|
const isRequisicao = ticketData.type === 'requisicao';
|
||||||
const isTelefonia = ticketData.short_description === 'Problemas com Telefonia Fixa' || 'Solicitações relacionadas a telefonia fixa';
|
const shortDesc = ticketData.short_description;
|
||||||
const isLinkDedicado = ticketData.short_description === 'Problemas com Internet';
|
const isTelefonia = shortDesc === 'Problemas com Telefonia Fixa' || shortDesc === 'Solicitações relacionadas a telefonia fixa';
|
||||||
const isWifi = ticketData.short_description === 'Problemas com Wi-Fi/Rede Corporativa' || ticketData.short_description === 'Wi-fi Clientes';
|
const isLinkDedicado = shortDesc === 'Problemas com Internet';
|
||||||
|
const isWifi = shortDesc === 'Problemas com Wi-Fi/Rede Corporativa' || shortDesc === 'Wi-fi Clientes';
|
||||||
|
|
||||||
if (isRequisicao && isTelefonia) {
|
if (isRequisicao && isTelefonia) {
|
||||||
return 37;
|
return 37;
|
||||||
@ -60,8 +63,6 @@ class TicketGlpiModel {
|
|||||||
return 0; // Default se nenhum critério for atendido
|
return 0; // Default se nenhum critério for atendido
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
static formatDescription(ticketData) {
|
static formatDescription(ticketData) {
|
||||||
// Função auxiliar para extrair informações usando regex
|
// Função auxiliar para extrair informações usando regex
|
||||||
const extractInfo = (regex, description) => {
|
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;">Campo</th>
|
||||||
<th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Valor</th>
|
<th style="padding: 8px; border: 1px solid #ddd; text-align: left;">Valor</th>
|
||||||
</tr>
|
</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>
|
<tr>
|
||||||
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Email:</strong></td>
|
<td style="padding: 8px; border: 1px solid #ddd;"><strong>Email:</strong></td>
|
||||||
<td style="padding: 8px; border: 1px solid #ddd;">${email}</td>
|
<td style="padding: 8px; border: 1px solid #ddd;">${email}</td>
|
||||||
@ -123,6 +128,15 @@ class TicketGlpiModel {
|
|||||||
try {
|
try {
|
||||||
await connection.beginTransaction();
|
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
|
// Extrai o email da descrição, se existir e não houver email no ticketData
|
||||||
let email = ticketData.caller_email;
|
let email = ticketData.caller_email;
|
||||||
if (!email && ticketData.description) {
|
if (!email && ticketData.description) {
|
||||||
@ -142,7 +156,7 @@ class TicketGlpiModel {
|
|||||||
|
|
||||||
// Prepara os dados para o GLPI
|
// Prepara os dados para o GLPI
|
||||||
const glpiTicketData = {
|
const glpiTicketData = {
|
||||||
entities_id: 1371,
|
entities_id: entityId, // Usar o entity_id mapeado
|
||||||
name: ticketData.short_description || 'Ticket sem descrição',
|
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(),
|
||||||
|
|||||||
@ -75,25 +75,26 @@ class TicketSnModel {
|
|||||||
try {
|
try {
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
ts.id,
|
||||||
ticket_number,
|
ts.ticket_number,
|
||||||
short_description,
|
ts.short_description,
|
||||||
description,
|
ts.description,
|
||||||
status,
|
ts.status,
|
||||||
caller_id,
|
ts.caller_id,
|
||||||
caller_email,
|
ts.caller_email,
|
||||||
location_id,
|
ts.location_id,
|
||||||
location_name,
|
ts.location_name,
|
||||||
ramal,
|
ts.ramal,
|
||||||
telefone,
|
ts.telefone,
|
||||||
opened_at,
|
ts.opened_at,
|
||||||
tipo,
|
ts.tipo,
|
||||||
created_at,
|
ts.created_at,
|
||||||
updated_at
|
ts.updated_at
|
||||||
FROM tickets_sn
|
FROM tickets_sn ts
|
||||||
WHERE glpi_sync_status IS NULL
|
LEFT JOIN ticket_sync tsync ON ts.id = tsync.sn_ticket_id
|
||||||
AND status NOT IN ('Encerrado', 'Encerrado - Omitido')
|
WHERE tsync.id IS NULL
|
||||||
ORDER BY opened_at
|
AND ts.status NOT IN ('Encerrado', 'Encerrado - Omitido')
|
||||||
|
ORDER BY ts.opened_at
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await pool.query(query);
|
const result = await pool.query(query);
|
||||||
@ -106,53 +107,79 @@ class TicketSnModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Atualiza o status de sincronização de um ticket específico // Inativo
|
// Busca um ticket pelo número
|
||||||
static async updateSyncStatus(ticketId, status, glpiTicketId = null) {
|
static async findByTicketNumber(ticketNumber) {
|
||||||
try {
|
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 = `
|
const query = `
|
||||||
UPDATE tickets_sn
|
UPDATE tickets_sn
|
||||||
SET glpi_sync_status = $1,
|
SET ${fields.join(', ')}
|
||||||
glpi_ticket_id = $2,
|
WHERE id = $${paramCount}
|
||||||
glpi_sync_date = NOW()
|
RETURNING *
|
||||||
WHERE id = $3
|
|
||||||
RETURNING id, ticket_number
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const result = await pool.query(query, [status, glpiTicketId, ticketId]);
|
const result = await pool.query(query, values);
|
||||||
|
|
||||||
if (result.rows.length > 0) {
|
if (result.rows.length > 0) {
|
||||||
logInfo(`🔄 Status atualizado: ${status} para ticket ${result.rows[0].ticket_number}`, {
|
logInfo(`✅ Ticket atualizado: ${ticketId}`);
|
||||||
ticket_id: result.rows[0].id,
|
return result.rows[0];
|
||||||
ticket_number: result.rows[0].ticket_number,
|
|
||||||
glpi_ticket_id: glpiTicketId,
|
|
||||||
status: status
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, `❌ Erro ao atualizar status de sync para ticket ${ticketId}`);
|
logError(error, `❌ Erro ao atualizar ticket ${ticketId}`);
|
||||||
}
|
throw error;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 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 [];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,11 +188,10 @@ class TicketSnModel {
|
|||||||
* @module 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.
|
* @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,
|
* 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 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 getPendingTickets busca tickets que ainda não foram sincronizados com o GLPI (não possuem registro em ticket_sync)
|
||||||
* A função updateSyncStatus atualiza o status de sincronização de um ticket específico.
|
* e ignora os tickets com status 'Encerrado' ou 'Encerrado - Omitido'.
|
||||||
* A função getSyncStatus retorna um resumo do status de sincronização dos tickets.
|
|
||||||
*/
|
*/
|
||||||
module.exports = TicketSnModel;
|
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
|
// src/services/glpiService.js
|
||||||
const TicketSnModel = require('../models/ticketSnModel');
|
const TicketSnModel = require('../models/ticketSnModel');
|
||||||
const TicketGlpiModel = require('../models/ticketGlpiModel');
|
const TicketGlpiModel = require('../models/ticketGlpiModel');
|
||||||
|
const TicketSyncModel = require('../models/ticketSyncModel');
|
||||||
const { logInfo, logError } = require('../utils/logger');
|
const { logInfo, logError } = require('../utils/logger');
|
||||||
|
|
||||||
// Método principal que o controller vai chamar
|
// Método principal que o controller vai chamar
|
||||||
@ -8,10 +9,9 @@ const syncTicketsToGlpi = async () => {
|
|||||||
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
|
||||||
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;
|
||||||
@ -34,6 +34,8 @@ const syncTicketsToGlpi = async () => {
|
|||||||
|
|
||||||
// Processar um ticket individual
|
// Processar um ticket individual
|
||||||
const processSingleTicket = async (ticket) => {
|
const processSingleTicket = async (ticket) => {
|
||||||
|
let syncRecord = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`);
|
logInfo(`🔍 Processando ticket: ${ticket.ticket_number}`);
|
||||||
|
|
||||||
@ -41,45 +43,54 @@ const processSingleTicket = async (ticket) => {
|
|||||||
const existingTicket = await TicketGlpiModel.ticketExists(ticket.ticket_number);
|
const existingTicket = await TicketGlpiModel.ticketExists(ticket.ticket_number);
|
||||||
if (existingTicket) {
|
if (existingTicket) {
|
||||||
logInfo(`⏭️ Ticket já existe no GLPI: ${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;
|
return;
|
||||||
}
|
}
|
||||||
// 2. Converter dados para formato GLPI (O MODEL vai fazer o mapeamento)
|
|
||||||
|
// 2. Converter dados para formato GLPI
|
||||||
const glpiData = {
|
const glpiData = {
|
||||||
// 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_id: ticket.caller_id,
|
||||||
caller_email: ticket.caller_email,
|
caller_email: ticket.caller_email,
|
||||||
caller_name: ticket.caller_id,
|
|
||||||
caller_phone: ticket.telefone,
|
caller_phone: ticket.telefone,
|
||||||
caller_ramal: ticket.ramal,
|
caller_ramal: ticket.ramal,
|
||||||
category: ticket.category,
|
type: ticket.tipo,
|
||||||
type: ticket.tipo, // incidente ou requisicao
|
state: ticket.status,
|
||||||
|
priority: '3',
|
||||||
// Dados do ServiceNow originais (se necessário para mapeamento)
|
location_id: ticket.location_id,
|
||||||
state: ticket.status, // Já vem do ServiceNow
|
|
||||||
priority: '3', // Default - O MODEL vai mapear
|
|
||||||
|
|
||||||
// Localização para de/para
|
|
||||||
location_name: ticket.location_name,
|
location_name: ticket.location_name,
|
||||||
|
|
||||||
// Datas
|
|
||||||
opened_at: ticket.opened_at,
|
opened_at: ticket.opened_at,
|
||||||
updated_at: ticket.updated_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);
|
const glpiTicketId = await TicketGlpiModel.createTicket(glpiData);
|
||||||
|
|
||||||
// 4. Atualizar status no PostgreSQL usando SEU MODEL
|
// 4. Criar registro de sincronização
|
||||||
await TicketSnModel.updateSyncStatus(ticket.id, 'completed', glpiTicketId);
|
syncRecord = await TicketSyncModel.createSyncRecord(ticket.id, glpiTicketId);
|
||||||
|
await TicketSyncModel.updateGlpiSyncStatus(syncRecord.id, 'completed', glpiTicketId);
|
||||||
|
|
||||||
logInfo(`✅ Ticket ${ticket.ticket_number} sincronizado! GLPI ID: ${glpiTicketId}`);
|
logInfo(`✅ Ticket ${ticket.ticket_number} sincronizado! GLPI ID: ${glpiTicketId}`);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logError(error, `❌ Erro no ticket ${ticket.ticket_number}`);
|
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