From 9df7e62cf5b520acd30cd465ca6d3e6b55975693 Mon Sep 17 00:00:00 2001 From: Rafael Lopes Date: Thu, 18 Sep 2025 20:52:39 -0300 Subject: [PATCH] =?UTF-8?q?FEAT:=20Titulo=20da=20solicita=C3=A7=C3=A3o=20a?= =?UTF-8?q?dicionado=20dinamicamente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adição de Titulo composto dinamicamente "TIPO - ENTIDADE - PROBLEMA" - Adicionado Fluxo completo da criação do banco de dados - Adicionado verificação se o arquivo .csv foi modificado nos ultimos 2 minutos, se sim a tabela de mapping é atualizada --- src/models/locationMappingModel.js | 60 +++++++++++++++++++ src/models/ticketGlpiModel.js | 36 ++++++++++- src/scripts/database/scriptBD.sql | 12 ++++ src/scripts/python/update_location_mapping.py | 28 +++++++-- 4 files changed, 129 insertions(+), 7 deletions(-) diff --git a/src/models/locationMappingModel.js b/src/models/locationMappingModel.js index 7460ea1..bde7410 100644 --- a/src/models/locationMappingModel.js +++ b/src/models/locationMappingModel.js @@ -47,6 +47,66 @@ class LocationMappingModel { return null; } } + + + + // Buscar entity (id e name) do GLPI baseado no location_id do ServiceNow + static async getGlpiEntity(snLocationId) { + try { + const query = ` + SELECT glpi_entity_id, glpi_entity_name + FROM location_mapping + WHERE sn_location_id = $1 + `; + + const result = await pool.query(query, [snLocationId]); + + if (result.rows.length > 0) { + return { + id: result.rows[0].glpi_entity_id, + name: result.rows[0].glpi_entity_name + }; + } + + return null; + } catch (error) { + logError(error, `Erro ao buscar entity para location_id: ${snLocationId}`); + return null; + } + + + } + + // Buscar entity (id e name) do GLPI baseado no location_name do ServiceNow + static async getGlpiEntityByName(snLocationName) { + try { + const query = ` + SELECT glpi_entity_id, glpi_entity_name + FROM location_mapping + WHERE sn_location_name = $1 + `; + + const result = await pool.query(query, [snLocationName]); + + if (result.rows.length > 0) { + return { + id: result.rows[0].glpi_entity_id, + name: result.rows[0].glpi_entity_name + }; + } + + return null; + } catch (error) { + logError(error, `Erro ao buscar entity para location_name: ${snLocationName}`); + return null; + } +} + +} + + + + module.exports = LocationMappingModel; \ No newline at end of file diff --git a/src/models/ticketGlpiModel.js b/src/models/ticketGlpiModel.js index 852300b..1f37001 100644 --- a/src/models/ticketGlpiModel.js +++ b/src/models/ticketGlpiModel.js @@ -63,6 +63,36 @@ class TicketGlpiModel { return 0; // Default se nenhum critério for atendido } + // Formata o título do ticket + static async formatTitle(ticketData) { + try { + let entityData = null; + + if (ticketData.location_id) { + entityData = await LocationMappingModel.getGlpiEntity(ticketData.location_id); + } else if (ticketData.location_name) { + entityData = await LocationMappingModel.getGlpiEntityByName(ticketData.location_name); + } + + const entityName = entityData?.name || 'Sem Entidade'; + const tipo = ticketData.type === 'requisicao' ? 'REQUISIÇÃO' : 'INCIDENTE'; + const tituloSn = ticketData.short_description || 'Sem título'; + + return { + title: `${tipo} - ${entityName} - ${tituloSn}`, + entityId: entityData?.id || 127 // fallback para entity_id default + }; + } catch (error) { + logError(error, 'Erro ao formatar título do ticket'); + const tipo = ticketData.type === 'requisicao' ? 'REQUISIÇÃO' : 'INCIDENTE'; + return { + title: `${tipo} - ${ticketData.short_description || 'Sem título'}`, + entityId: 127 + }; + } +} + + static formatDescription(ticketData) { // Função auxiliar para extrair informações usando regex const extractInfo = (regex, description) => { @@ -129,7 +159,7 @@ class TicketGlpiModel { await connection.beginTransaction(); // Buscar entity_id com base no location_id ou location_name - let entityId = 1371; // Valor padrão (fallback) + let entityId = 127; // Valor padrão (fallback) if (ticketData.location_id) { entityId = await LocationMappingModel.getGlpiEntityId(ticketData.location_id) || entityId; @@ -147,6 +177,8 @@ class TicketGlpiModel { } } + // Formata o título do ticket + const { title: formattedTitle } = await TicketGlpiModel.formatTitle(ticketData); // Formata a descrição para HTML const formattedDescription = TicketGlpiModel.formatDescription(ticketData); // Mapeia o tipo do ticket @@ -157,7 +189,7 @@ class TicketGlpiModel { // Prepara os dados para o GLPI const glpiTicketData = { entities_id: entityId, // Usar o entity_id mapeado - name: ticketData.short_description || 'Ticket sem descrição', + name: formattedTitle, date: ticketData.opened_at || new Date(), date_mod: ticketData.updated_at || new Date(), status: TicketGlpiModel.mapStatus(ticketData.state || '1'), diff --git a/src/scripts/database/scriptBD.sql b/src/scripts/database/scriptBD.sql index db80e0a..ecddafa 100644 --- a/src/scripts/database/scriptBD.sql +++ b/src/scripts/database/scriptBD.sql @@ -4,6 +4,18 @@ -- OBS: Chamados sempre são abertos pelo ServiceNow -- ============================================= +CREATE DATABASE snglpi + WITH + OWNER = desenvolvimento + ENCODING = 'UTF8' + LC_COLLATE = 'en_US.UTF-8' + LC_CTYPE = 'en_US.UTF-8' + LOCALE_PROVIDER = 'libc' + TABLESPACE = pg_default + CONNECTION LIMIT = -1 + IS_TEMPLATE = False; + + -- ============================================= -- TABELA: tickets_sn -- DESCRIÇÃO: Armazena tickets importados do ServiceNow diff --git a/src/scripts/python/update_location_mapping.py b/src/scripts/python/update_location_mapping.py index 28d150f..11e8170 100644 --- a/src/scripts/python/update_location_mapping.py +++ b/src/scripts/python/update_location_mapping.py @@ -8,6 +8,7 @@ from datetime import datetime import os from dotenv import load_dotenv import pandas as pd +from datetime import timedelta # Carrega as variáveis do .env load_dotenv() @@ -43,6 +44,22 @@ GLPI_DB_CONFIG = { # Caminho para o arquivo CSV CSV_PATH = os.getenv('LOCATION_MAPPING_CSV_PATH') +# Função para verificar se o CSV foi modificado recentemente +def csv_recently_modified(minutes=2): + if not os.path.exists(CSV_PATH): + logging.error(f"Arquivo CSV não encontrado: {CSV_PATH}") + return False + + last_mod_time = datetime.fromtimestamp(os.path.getmtime(CSV_PATH)) + if datetime.now() - last_mod_time <= timedelta(minutes=minutes): + logging.info(f"CSV modificado recentemente ({last_mod_time}), rodando atualização.") + return True + else: + logging.info(f"CSV não foi modificado nos últimos {minutes} minutos (última modificação: {last_mod_time}).") + return False + + + # Função para conectar ao banco SNGLPI def get_snglpi_connection(): try: @@ -177,8 +194,9 @@ def update_mapping_table(): 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") \ No newline at end of file + if csv_recently_modified(minutes=2): + success = update_mapping_table() + if success: + logging.info("Atualização concluída com sucesso") + else: + logging.error("Falha na atualização") \ No newline at end of file