diff --git a/.env.example b/.env.example index 6c4aeac..1969916 100644 --- a/.env.example +++ b/.env.example @@ -46,3 +46,4 @@ GLPI_DB_USER= GLPI_DB_PASSWORD= GLPI_DB_NAME=glpi_data GLPI_DB_CHARSET=utf8mb4 +GLPI_USER_ID= \ No newline at end of file diff --git a/src/controller/processController.js b/src/controller/processController.js index 4423ef2..86b662f 100644 --- a/src/controller/processController.js +++ b/src/controller/processController.js @@ -2,7 +2,7 @@ const hubsoftModel = require('../model/hubsoftModel.js'); const hubglpiModel = require('../model/hubglpiModel.js'); const glpiModel = require('../model/glpiModel.js'); -const { logError, logInfo } = require('../utils/logger'); +const { logError, logInfo } = require('../shared/utils/logger.js'); // ================================================================================ diff --git a/src/cron.js b/src/cron.js index 4fcb4ae..f090c16 100644 --- a/src/cron.js +++ b/src/cron.js @@ -2,7 +2,7 @@ const loadEnv = require('./config/envLoader'); loadEnv(); const cron = require('node-cron'); -const { processaAtendimentos } = require('./controller/processController.js'); +const { processAtendimentos}= require('./modules/createTickets/controller/createTickets.controller.js'); const commentService = require('./services/commentService.js'); // 1. Importar o novo serviço const { logInfo, logError } = require('./utils/logger.js'); @@ -21,7 +21,7 @@ cron.schedule('* * * * *', async () => { try { // --- Tarefa 1: Sincronizar criação de tickets --- logInfo('CRON (Etapa 1/2): Processando criação de tickets...'); - await processaAtendimentos(); + await processAtendimentos(); logInfo('CRON (Etapa 1/2): Criação de tickets concluída.'); // --- Tarefa 2: Sincronizar comentários --- diff --git a/src/model/glpiModel.js b/src/model/glpiModel.js index 2b82ff3..b1b5376 100644 --- a/src/model/glpiModel.js +++ b/src/model/glpiModel.js @@ -1,6 +1,6 @@ // src/models/glpiModel.js const dbConfig = require('../config/dbConfig.js'); -const { logError, logInfo} = require('../utils/logger'); +const { logError, logInfo} = require('../utils/logger.js'); const mysql = require('mysql2/promise'); const pool = mysql.createPool({ @@ -63,7 +63,6 @@ class GlpiModel { throw error; } } - static async selectEntityIdCodCliente(id) { const query = `SELECT id FROM glpi_entities WHERE name LIKE ? LIMIT 1;`; @@ -85,7 +84,6 @@ class GlpiModel { throw err; } } - static async selectEntityIdCodServico(idCliente, idServico) { const query = `SELECT id FROM glpi_entities WHERE name LIKE ? LIMIT 1;`; @@ -107,7 +105,6 @@ class GlpiModel { throw err; } } - static async insertGroupTickets(glpiTicketId) { const query = ` INSERT INTO glpi_groups_tickets (tickets_id, groups_id, type) @@ -122,18 +119,18 @@ class GlpiModel { logError(`Erro ao Adicionar Grupo Operação NOC: ${err}`); throw err; } + } + static async insertComment(commentData) { + const query = `INSERT INTO glpi_itilfollowups(itemtype, items_id, date, users_id, content, date_creation, date_mod) VALUES('Ticket',? , NOW(), ?, ?, NOW(), NOW())`; + const values = [commentData.tickets_id, parseInt(process.env.GLPI_USER_ID), commentData.content]; + try { + const [rows] = await pool.query(query, values); + return rows && rows.insertId ? { id: rows.insertId } : null; } - static async insertComment(commentData) { - const query = `INSERT INTO glpi_itilfollowups(itemtype, items_id, date, users_id, content, date_creation, date_mod) VALUES('Ticket',? , NOW(), ?, ?, NOW(), NOW())`; - const values = [commentData.tickets_id, parseInt(process.env.GLPI_USER), commentData.content]; - try { - const [rows] = await pool.query(query, values); - return rows && rows.insertId ? { id: rows.insertId } : null; - } - catch (err) { - logError(`Erro ao inserir comentário no GLPI: ${err}`); - throw err; - } + catch (err) { + logError(`Erro ao inserir comentário no GLPI: ${err}`); + throw err; } + } } module.exports = GlpiModel; diff --git a/src/model/hubsoftModel.js b/src/model/hubsoftModel.js index b05cfc1..60b47af 100644 --- a/src/model/hubsoftModel.js +++ b/src/model/hubsoftModel.js @@ -1,5 +1,5 @@ const dbConfig = require('../config/dbConfig.js'); -const { logError, logInfo } = require('../utils/logger'); +const { logError, logInfo } = require('../utils/logger.js'); const hubsoftDbConfig = { host: dbConfig.hubsoft.databaseHost, diff --git a/src/modules/createTickets/controller/createTickets.controller.js b/src/modules/createTickets/controller/createTickets.controller.js index de6a0f7..6302906 100644 --- a/src/modules/createTickets/controller/createTickets.controller.js +++ b/src/modules/createTickets/controller/createTickets.controller.js @@ -1,46 +1,51 @@ // src/modules/createTickets/controller/createTickets.controller.js const mundialeService = require('../services/mundiale.service.js'); const implantacaoService = require('../services/implantacao.service.js'); -const appMobileService = require('../services/appMobile.service.js'); +const sacService = require('../services/sac.service.js'); const ticketShared = require('../services/createTickets.service.js'); -const { logInfo } = require('../../utils/logger.js'); +const { logInfo } = require('../../../utils/logger.js'); -async function processaAtendimentos() { +async function processAtendimentos() { logInfo("[CONTROLLER] Iniciando processamento"); // 1️⃣ Buscar por fonte const mundiale = await mundialeService.fetchNew(); + logInfo(`Encontrados ${mundiale.length} tickets Mundiale para processar.`); const implantacao = await implantacaoService.fetchNew(); - const app = await appMobileService.fetchNew(); + logInfo(`Encontrados ${implantacao.length} tickets de Implantação para processar.`); + //const sac = await sacService.fetchNew(); // 2️⃣ Salvar no hubglpi await mundialeService.saveHubGlpi(mundiale); - await implantacaoService.saveHubglpi(implantacao); - await appMobileService.saveHubglpi(app); + await implantacaoService.saveHubGlpi(implantacao); + //await sacService.saveHubGlpi(sac); // 3️⃣ Buscar pendentes que foram salvos no banco hubglpi - const pendentes = await ticketShared.buscarPendentesHubglpi(); + const pendentes = await ticketShared.fetchPendingTickets(); // 4️⃣ Roteamento por tipo for (const ticket of pendentes) { - + if (ticket.ticket_type === 'MUNDIALE') { await mundialeService.sendToGlpi(ticket); } else if (ticket.ticket_type === 'IMPLANTACAO') { await implantacaoService.sendToGlpi(ticket); - } else if (ticket.ticket_type === 'APP_MOBILE') { - await appMobileService.sendToGlpi(ticket); + } else if (ticket.ticket_type === 'SAC') { + await sacService.sendToGlpi(ticket); } else { logInfo(`Tipo desconhecido, ignorando ticket id=${ticket.id}`); continue; } - await ticketShared.marcarComoProcessado(ticket.id); } logInfo("[CONTROLLER] Finalizado."); } + +module.exports = { processAtendimentos }; + + diff --git a/src/modules/createTickets/services/createTickets.service.js b/src/modules/createTickets/services/createTickets.service.js index 0fbcadb..c4b98a9 100644 --- a/src/modules/createTickets/services/createTickets.service.js +++ b/src/modules/createTickets/services/createTickets.service.js @@ -1,17 +1,18 @@ // src/modules/createTickets/services/createTickets.service.js const repositoryHubGlpi = require('../../../shared/repositories/hubglpi.repository.js'); +const repositoryGlpi = require('../../../shared/repositories/glpi.repository.js'); // -------------------------------------- // Funções principais do serviço // -------------------------------------- -async function fetcPendingTickets() { +async function fetchPendingTickets() { return repositoryHubGlpi.fetchPendingTickets(); } -async function resolveEntityId(ticketData, glpiModel) { +async function resolveEntityId(ticketData) { - const entityByService = await glpiModel.selectEntityIdCodServico( + const entityByService = await repositoryGlpi.getEntitiesByService( ticketData.codigo_cliente, ticketData.codigo_servico ); @@ -21,8 +22,7 @@ async function resolveEntityId(ticketData, glpiModel) { return ticketData; } - // Tenta entidade pelo cliente - const entityByClient = await glpiModel.selectEntityIdCodCliente( + const entityByClient = await repositoryGlpi.getEntitiesByClient( ticketData.codigo_cliente ); @@ -31,14 +31,30 @@ async function resolveEntityId(ticketData, glpiModel) { return ticketData; } - // Fallback ticketData.entities_id = 0; return ticketData; } +// Funcao para definir ticket como processado +async function setAsCreated(dataTicket) { + +} + + +//Cria uma nova entidade no GLPI +async function createEntity(ticketData) { + + const entity_name = `${ticketData.codigo_cliente} - ${ticketData.nome_razao_social}`; + + await repositoryGlpi.insertEntity(entity_name); + + return; +} module.exports = { - fetcPendingTickets, - resolveEntityId + fetchPendingTickets, + resolveEntityId, + setAsCreated, + createEntity } \ No newline at end of file diff --git a/src/modules/createTickets/services/implantacao.service.js b/src/modules/createTickets/services/implantacao.service.js index e69de29..446a21a 100644 --- a/src/modules/createTickets/services/implantacao.service.js +++ b/src/modules/createTickets/services/implantacao.service.js @@ -0,0 +1,254 @@ +// src/modules/createTickets/services/mundiale.service.js +const repositoryHubGlpi = require('../../../shared/repositories/hubglpi.repository.js'); +const repositoryGlpi = require('../../../shared/repositories/glpi.repository.js'); +const repositoryHubsoft = require('../../../shared/repositories/hubsoft.repository.js'); +const modelHubGlpi = require('../../../shared/model/hubglpi.model.js'); +const modelGlpi = require('../../../shared/model/glpi.model.js'); +const ticketShared = require('./createTickets.service.js'); +const { logInfo, logWarning } = require('../../../utils/logger.js'); + +// -------------------------------------- +// Funções principais do serviço +// -------------------------------------- + +async function fetchNew() { + return repositoryHubsoft.getImplantacaoTickets(); +} + +async function saveHubGlpi(tickets) { + if (!tickets.length) return; + + const ticketsFormatted = tickets.map(ticket => + modelHubGlpi.mapHubsoftToHubglpi(ticket, 'IMPLANTACAO') + ); + + const inserted = await repositoryHubGlpi.insertTickets(ticketsFormatted); + + if (inserted) { + await repositoryHubGlpi.insertSyncData( + ticketsFormatted.map(ticket => ticket.id_atendimento) + ); + } + + return inserted; +} + +async function sendToGlpi(ticket) { + + const ticketsResolved = await ticketShared.resolveEntityId(ticket); + + if (ticketsResolved.entities_id === 0) { + logWarning(`Entidade não encontrada para o ticket id=${ticket.id_atendimento}.`); + } + + formatGlpiPayload(ticketsResolved); + + const payload = modelGlpi.mapHubGlpiToGlpi(ticketsResolved); + + const glpiId = await repositoryGlpi.insertTicket(payload); + logInfo(`Ticket GLPI criado com ID=${glpiId} para o ticket id=${ticket.id_atendimento}.`); + + await repositoryGlpi.insertGroupTicket(glpiId); + await repositoryHubGlpi.updateSyncDataCreated(ticket.id_atendimento, glpiId); +} + + +// -------------------------------------- +// Formatar dados antes de enviar para o GLPI +// -------------------------------------- + +function formatGlpiPayload(ticket) { + const title = `IMPLANTAÇÃO - ${ticket.codigo_cliente}-${ticket.codigo_servico}-${ticket.cliente_nome} - ${ticket.servico_nome}`; + const description = formatDescription(ticket); + ticket.name = title; + ticket.content = description; + return; +} + + +// -------------------------------------- +// Dicionário de Serviços +// -------------------------------------- + +const serviceDictionary = { + // ---------- LAN-TO-LAN ---------- + "Lan-to-Lan 50 Mbps": { produto: "Lan-to-Lan", qtd: 1, descricao: "50 Mbps" }, + "Lan-to-Lan 100 Mbps": { produto: "Lan-to-Lan", qtd: 1, descricao: "100 Mbps" }, + "Lan-to-Lan 200 Mbps": { produto: "Lan-to-Lan", qtd: 1, descricao: "200 Mbps" }, + "Lan-to-Lan 300 Mbps": { produto: "Lan-to-Lan", qtd: 1, descricao: "300 Mbps" }, + "Lan-to-Lan 500 Mbps": { produto: "Lan-to-Lan", qtd: 1, descricao: "500 Mbps" }, + "Lan-to-Lan 700 Mbps": { produto: "Lan-to-Lan", qtd: 1, descricao: "700 Mbps" }, + "Lan-to-Lan": { produto: "Lan-to-Lan", qtd: 1, descricao: null }, + + // ---------- LINK DEDICADO ---------- + "Link de Internet Dedicado 20 Mbps Full Duplex": + { produto: "Link de Internet Dedicado", qtd: 1, descricao: "20 Mbps Full Duplex" }, + + "Link de Internet Dedicado 100 Mbps Full Duplex": + { produto: "Link de Internet Dedicado", qtd: 1, descricao: "100 Mbps Full Duplex" }, + + "Link de Internet Dedicado 1Gbps Full Duplex": + { produto: "Link de Internet Dedicado", qtd: 1, descricao: "1 Gbps Full Duplex" }, + + "Link de Internet Dedicado 2 Gbps Full Duplex": + { produto: "Link de Internet Dedicado", qtd: 1, descricao: "2 Gbps Full Duplex" }, + + // Default genérico caso venha coisa nova +}; + + +// -------------------------------------- +// Tabela HTML da descrição +// -------------------------------------- + +const formatDescription = (ticket) => { + + const documentoFormatado = formatDocument(ticket.cpf_cnpj, ticket.tipo_pessoa); + const docLabel = ticket.tipo_pessoa === "pf" ? "CPF" : "CNPJ"; + const servico = resolveService(ticket.servico_nome); + + return ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Dados Comercial +
Nº de Operação${ticket.protocolo_hub || "N/A"}
Gerente Responsável${ticket.usuario_que_abriu || "N/A"}
Código do Cliente${ticket.codigo_cliente}
+ Dados Cliente +
Cliente${ticket.cliente_nome}
${docLabel}${documentoFormatado}
Nome Contato${ticket.nome_razaosocial || "N/A"}
Email Contato${ticket.email || "N/A"}
Telefone Contato${ticket.telefone || "N/A"}
Endereço Instalação${ticket.endereco || "N/A"}
+ Dados do Serviço Contratado +
+ + + + + + + + + + + +
ProdutoQuantidadeDescrição
${servico.produto}${servico.qtd}${servico.descricao || ""}
+
+ Observações +
${ticket.descricao_abertura || "N/A"}
+ `; +}; + + + +// -------------------------------------- +// Formatadores de Documento +// -------------------------------------- + +function formatCPF(cpf) { + cpf = cpf.replace(/\D/g, ""); + return cpf.replace(/(\d{3})(\d{3})(\d{3})(\d{2})/, "$1.$2.$3-$4"); +} + +function formatCNPJ(cnpj) { + cnpj = cnpj.replace(/\D/g, ""); + return cnpj.replace(/(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})/, "$1.$2.$3/$4-$5"); +} + +function formatDocument(doc, tipo) { + if (!doc) return "N/A"; + return tipo === "pf" ? formatCPF(doc) : formatCNPJ(doc); +} + + +// -------------------------------------- +// Resolve Serviço +// -------------------------------------- + +function resolveService(servicoNome) { + return serviceDictionary[servicoNome] || { + produto: servicoNome, + qtd: 1, + descricao: null + }; +} + + +// -------------------------------------- +// Exportação +// -------------------------------------- + +module.exports = { + fetchNew, + saveHubGlpi, + sendToGlpi +}; diff --git a/src/modules/createTickets/services/mundiale.service.js b/src/modules/createTickets/services/mundiale.service.js index 81d6b12..8d083d5 100644 --- a/src/modules/createTickets/services/mundiale.service.js +++ b/src/modules/createTickets/services/mundiale.service.js @@ -1,11 +1,12 @@ // src/modules/createTickets/services/mundiale.service.js const repositoryHubGlpi = require('../../../shared/repositories/hubglpi.repository.js'); -const repositoryGlpi = require('../../../repositories/glpi.repository.js'); +const repositoryGlpi = require('../../../shared/repositories/glpi.repository.js'); const repositoryHubsoft = require('../../../shared/repositories/hubsoft.repository.js'); const modelHubGlpi = require('../../../shared/model/hubglpi.model.js'); const modelGlpi = require('../../../shared/model/glpi.model.js'); const ticketShared = require('./createTickets.service.js'); const { logInfo, logError, logWarning } = require('../../../utils/logger.js'); +const { log } = require('winston'); // -------------------------------------- // Funções principais do serviço @@ -31,18 +32,36 @@ async function saveHubGlpi(tickets) { async function sendToGlpi(ticket) { - const ticketsResolved = await ticketShared.resolveEntityId(ticket, modelGlpi); + const ticketsResolved = await ticketShared.resolveEntityId(ticket); - const formattedTickets = formatGlpiPayload(ticketsResolved); + formatGlpiPayload(ticketsResolved); - const payload = modelGlpi.mapHubGlpiToGlpi(formattedTickets); + const payload = modelGlpi.mapHubGlpiToGlpi(ticketsResolved); + + const glpiId = await repositoryGlpi.insertTicket(payload); + logInfo(`Ticket GLPI criado com ID: ${glpiId} para ticket Hubsoft ID: ${ticket.id_atendimento}`); + + await repositoryGlpi.insertGroupTicket(glpiId); + logInfo(`Grupo padrão atribuído ao ticket GLPI ID: ${glpiId}`); + + await repositoryHubGlpi.updateSyncDataCreated(ticket.id_atendimento, glpiId); + logInfo(`Ticket HubGlpi ID: ${ticket.id_atendimento} marcado como criado no Banco intermediario com ID: ${glpiId}`); - const glpiId = await repositoryGlpi.insertTickets([payload]); - - await repositoryHubGlpi.updateSyncDataCreated(ticket.id_atendimento, glpiId[0]); - } +// -------------------------------------- +// Mapeamento e formatação de dados +// -------------------------------------- + + +const statusAtendimentoGLPI = { + 'Novo': 1, + 'Pendente': 4, + 'Em atendimento': 2, + 'Resolvido': 5 +}; + + // -------------------------------------- // Formatar dados antes de enviar para o GLPI @@ -50,22 +69,14 @@ async function sendToGlpi(ticket) { function formatGlpiPayload(ticket) { - const statusTexto = statusAtendimentoHubGlpi[ticket.status_atendimento] || 'Novo'; - const statusGlpi = statusAtendimentoGLPI[statusTexto] || 1; + ticket.status = statusAtendimentoGLPI[ticket.status_atendimento] || 1; - const categoria = categoriaGLPI[ticket.servico_nome] || 0; + ticket.name = `Mundiale - Protocolo: ${ticket.ticket_mundiale} - ${ticket.cliente_nome}`; - const title = `[Mundiale] ${ticket.cliente_nome} - ${ticket.servico_nome} - ${ticket.ticket_mundiale}`; + ticket.content = formatDescription(ticket); - const description = formatDescription(ticket); - return { - name: title, - content: description, - status: statusGlpi, - itilcategories_id: categoria, - created_at: ticket.created_at, - }; + return; } diff --git a/src/scripts/data/database.sql b/src/scripts/data/database.sql index 795b68d..503a257 100644 --- a/src/scripts/data/database.sql +++ b/src/scripts/data/database.sql @@ -89,7 +89,18 @@ INSERT INTO sync_control (job_name, last_run_timestamp) VALUES ('hubsoft_comment -- ALTERAÇÕES NA TABELA hubsoft_tickets -- ============================================= ALTER TABLE hubsoft_tickets -ADD COLUMN ticket_type VARCHAR(50) NOT NULL DEFAULT 'MUNDIALE'; +ADD COLUMN ticket_type VARCHAR(50) NOT NULL DEFAULT 'MUNDIALE', +ADD COLUMN ticket_type VARCHAR(50), +ADD COLUMN descricao_abertura TEXT; +ADD COLUMN endereco TEXT, +ADD COLUMN telefone VARCHAR(25), +ADD COLUMN cpf_cnpj VARCHAR(30), +ADD COLUMN tipo_pessoa CHAR(2), +ADD COLUMN email VARCHAR(150), +ADD COLUMN nome_razaosocial VARCHAR(255);; ALTER TABLE hubsoft_tickets ADD CONSTRAINT hubsoft_id_atendimento_unique UNIQUE (id_atendimento); + + + diff --git a/src/shared/infra/database/glpi.pool.js b/src/shared/infra/database/glpi.pool.js new file mode 100644 index 0000000..b330413 --- /dev/null +++ b/src/shared/infra/database/glpi.pool.js @@ -0,0 +1,17 @@ +// src/shared/infra/database/glpi.pool.js +// MySQL / MariaDB - GLPI +const mysql = require('mysql2/promise'); +const { Pool } = require('pg'); + +const pool = mysql.createPool({ + host: process.env.GLPI_DB_HOST, + port: process.env.GLPI_DB_PORT, + user: process.env.GLPI_DB_USER, + password: process.env.GLPI_DB_PASSWORD, + database: process.env.GLPI_DB_NAME, + waitForConnections: true, + connectionLimit: 20, + queueLimit: 0, +}); + +module.exports = pool; diff --git a/src/shared/infra/database/hubglpi.pool.js b/src/shared/infra/database/hubglpi.pool.js new file mode 100644 index 0000000..45af961 --- /dev/null +++ b/src/shared/infra/database/hubglpi.pool.js @@ -0,0 +1,15 @@ +// src/shared/infra/database/hubglpi.pool.js +// PostgreSQL - HUBSOFT +const { Pool } = require("pg"); + +const hubglpiPool = new Pool({ + host: process.env.HUBGLPI_DB_HOST, + port: process.env.HUBGLPI_DB_PORT, + user: process.env.HUBGLPI_DB_USER, + password: process.env.HUBGLPI_DB_PASSWORD, + database: process.env.HUBGLPI_DB_NAME, + max: 20, + idleTimeoutMillis: 30000, +}); + +module.exports = hubglpiPool; diff --git a/src/shared/infra/database/hubsoft.pool.js b/src/shared/infra/database/hubsoft.pool.js new file mode 100644 index 0000000..5c38f07 --- /dev/null +++ b/src/shared/infra/database/hubsoft.pool.js @@ -0,0 +1,14 @@ +// PostgreSQL - HUBSOFT +const { Pool } = require("pg"); + +const hubsoftPool = new Pool({ + host: process.env.HUBSOFT_DATABASE_HOST, + port: process.env.HUBSOFT_DATABASE_PORT, + user: process.env.HUBSOFT_DATABASE_USER, + password: process.env.HUBSOFT_DATABASE_PASSWORD, + database: process.env.HUBSOFT_DATABASE_NAME, + max: 20, + idleTimeoutMillis: 30000, +}); + +module.exports = hubsoftPool; diff --git a/src/shared/infra/database/index.js b/src/shared/infra/database/index.js new file mode 100644 index 0000000..1821b4b --- /dev/null +++ b/src/shared/infra/database/index.js @@ -0,0 +1,7 @@ +// src/shared/infra/database/index.js + +module.exports = { + hubsoft: require("./hubsoft.pool"), + hubglpi: require("./hubglpi.pool"), + glpi: require("./glpi.pool") +}; diff --git a/src/shared/model/glpi.model.js b/src/shared/model/glpi.model.js index 01db487..ddd0f0c 100644 --- a/src/shared/model/glpi.model.js +++ b/src/shared/model/glpi.model.js @@ -5,15 +5,16 @@ function mapHubGlpiToGlpi(ticket) { entities_id: ticket.entities_id || 0, name: ticket.name, date: ticket.created_at, - date_mod: Now(), - status: defineStatusGlpi(ticket.status_atendimento), - users_id_recipient: process.env.GLPI_USER_ID || 0, + status: defineStatusGlpi(ticket.status), + users_id_recipient: parseInt(process.env.GLPI_USER_ID) || 0, content: ticket.content, urgency: 3, impact: 3, priority: 3, type: 2, - date_creation : ticket.date_creation || Now(), + requesttypes_id: 2, + date_creation : ticket.date_creation || new Date(), + itilcategories_id: 1, slas_id_ttr: 37 }; } @@ -26,7 +27,7 @@ function defineStatusGlpi(Status) { 'Resolvido': 5 }; - return mapStatusDBtoGLPI[Status] || 'Novo'; + return mapStatusDBtoGLPI[Status] || 1; }; diff --git a/src/shared/model/hubglpi.model.js b/src/shared/model/hubglpi.model.js index a3bba71..b6c0069 100644 --- a/src/shared/model/hubglpi.model.js +++ b/src/shared/model/hubglpi.model.js @@ -1,33 +1,99 @@ // src/shared/model/hubglpi.model.js function mapHubsoftToHubglpi(ticket, type) { - return { + return { id_atendimento: ticket.id_atendimento, codigo_cliente: ticket.codigo_cliente, status_atendimento: defineStatusHubglpi(ticket.id_atendimento_status), servico_nome: ticket.descricao, protocolo_hub: ticket.protocolo, - ticket_mundiale: parseInt(ticket.descricao_abertura.replace(/[^0-9]/g, ''))|| null, + + // Extrai número do protocolo do Mundiale + ticket_mundiale: extractMundiale(ticket.descricao_abertura, type), + codigo_servico: ticket.id_cliente_servico, cliente_nome: ticket.nome_contato, descricao_fechamento: ticket.descricao_fechamento || null, data_fechamento: ticket.data_fechamento || null, created_at: ticket.data_cadastro, - ticket_type: type + ticket_type: type, + descricao_abertura: ticket.descricao_abertura || null, + endereco: endereco(ticket), + telefone: sanitizePhone(ticket.telefone), + + cpf_cnpj: sanitizeCpfCnpj(ticket.cpf_cnpj), + + tipo_pessoa: ticket.tipo_pessoa || null, + email: ticket.email || null, + nome_razaosocial: ticket.nome_razaosocial || null }; } +/* ----------------- + Sanitização +------------------ */ + +function sanitizeCpfCnpj(value) { + if (!value) return null; + + // Se vier número gigantesco do JS tipo 3.2e+22 → converte pra string + const str = String(value); + + // Remove qualquer coisa que não seja número + const clean = str.replace(/\D/g, ""); + + return clean.length > 0 ? clean : null; +} + +function sanitizePhone(phone) { + if (!phone) return null; + const clean = String(phone).replace(/\D/g, ""); + return clean || null; +} + +function extractMundiale(descricao, type) { + if (type === 'IMPLANTACAO') return null; + + if (!descricao) return null; + + // Extrai apenas dígitos + const num = descricao.replace(/\D/g, ""); + + // Previne NaN + return num ? parseInt(num, 10) : null; +} + +/* ----------------- + Endereço +------------------ */ + +function endereco(ticket) { + if (!ticket.endereco || !ticket.numero || !ticket.cidade || !ticket.estado) { + return null; + } + + const complemento = ticket.complemento ? ` - ${ticket.complemento}` : ""; + const bairro = ticket.bairro ? `${ticket.bairro}` : ""; + const cep = ticket.cep ? ticket.cep : ""; + + return `${ticket.endereco}, ${ticket.numero}${complemento}, ${bairro} - ${ticket.cidade}/${ticket.estado} - CEP ${cep}`; +} + +/* ----------------- + Status Mapper +------------------ */ + function defineStatusHubglpi(hubsoftStatus) { const statusMap = { - 1: 'Pendente', - 2: 'Em atendimento', - 3: 'Resolvido', - 31: 'Pendente', - 32: 'Pendente', - 33: 'Novo' - } - return statusMap[hubsoftStatus] || 'Novo'; -}; + 1: "Pendente", + 2: "Em atendimento", + 3: "Resolvido", + 31: "Pendente", + 32: "Pendente", + 33: "Novo" + }; + return statusMap[hubsoftStatus] || "Novo"; +} module.exports = { mapHubsoftToHubglpi diff --git a/src/shared/repositories/glpi.repository.js b/src/shared/repositories/glpi.repository.js index 627659c..6eac3fd 100644 --- a/src/shared/repositories/glpi.repository.js +++ b/src/shared/repositories/glpi.repository.js @@ -1,40 +1,86 @@ // src/shared/repositories/glpi.repository.js -const db = require('../../../config/database.js'); +const { glpi } = require("../../shared/infra/database"); const { logInfo, logError } = require('../../utils/logger.js'); -async function insertTickets(tickets) { - - const values = tickets.map(t => [ - t.status, - t.content, - t.users_id_recipient, - t.entities_id, - t.type, - t.requesttypes_id, - t.urgency, - t.impact, - t.priority, - t.date, - t.name - ]); - - const placeholders = values - .map(() => "(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") - .join(","); - +async function insertTicket(ticket) { const sql = ` INSERT INTO glpi_tickets - (status, content, users_id_recipient, entities_id, type, requesttypes_id, - urgency, impact, priority, date, name) - VALUES ${placeholders} + (entities_id, name, date, date_mod, status, users_id_recipient, content, urgency, impact, priority, type, itilcategories_id, date_creation, slas_id_ttr) + VALUES (?, ?, ?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `; - const result = await mariadb.execute(sql, values.flat()); + const values = [ + ticket.entities_id, + ticket.name, + ticket.date, + ticket.status, + ticket.users_id_recipient, + ticket.content, + ticket.urgency, + ticket.impact, + ticket.priority, + ticket.type, + ticket.itilcategories_id, + ticket.date_creation, + ticket.slas_id_ttr + ]; - return result.insertId ? [result.insertId] : []; + const [result] = await glpi.execute(sql, values); + return result.insertId; } +async function getEntitiesByService(codigoCliente, codigoServico) { + try { + const sql = `SELECT id FROM glpi_entities WHERE name LIKE ? or name LIKE ? or name LIKE ? LIMIT 1;`; + const values = [`${codigoCliente}-${codigoServico}-%`, `${codigoCliente} -${codigoServico}-%`, `${codigoCliente} - ${codigoServico} -%`]; + const [rows] = await glpi.execute(sql, values); + if (rows.length > 0) { + return rows[0].id; + } + return null; + } catch (error) { + logError(`Erro ao buscar entidade por código de serviço: ${error}`); + throw error; + } +} + +async function getEntitiesByClient(codigoCliente) { + try { + const sql = `SELECT id FROM glpi_entities WHERE name LIKE ? or name LIKE ? LIMIT 1;`; + const values = [`${codigoCliente}-%`, `${codigoCliente} -%`]; + const [rows] = await glpi.execute(sql, values); + if (rows.length > 0) { + return rows[0].id; + } + return null; + } catch (error) { + logError(`Erro ao buscar entidade por código de cliente: ${error}`); + throw error; + } +} + +async function insertEntity(entity_name){ + +} + +async function insertGroupTicket(ticketId) { + try { + const sql = ` INSERT INTO glpi_groups_tickets (tickets_id, groups_id, type) + VALUES (?, 25, 2) + `; + const values = [ticketId]; + await glpi.execute(sql, values); + logInfo(`Grupo associado ao ticket GLPI ID: ${ticketId}`); + } catch (error) { + logError(`Erro ao associar grupo ao ticket GLPI ID ${ticketId}: ${error}`); + throw error; + } + +} module.exports = { - insertTickets + insertTicket, + getEntitiesByService, + getEntitiesByClient, + insertGroupTicket }; \ No newline at end of file diff --git a/src/shared/repositories/hubglpi.repository.js b/src/shared/repositories/hubglpi.repository.js index 1525019..61c7e5c 100644 --- a/src/shared/repositories/hubglpi.repository.js +++ b/src/shared/repositories/hubglpi.repository.js @@ -1,92 +1,125 @@ // src/shared/repositories/hubglpi.repository.js -const { get } = require('pm2'); -const db = require('../../../config/database.js'); -const { logInfo, logError } = require('../../utils/logger.js'); -const { query } = require('winston'); -const { hubsoft } = require('../../config/dbConfig.js'); + +const { hubglpi } = require("../../shared/infra/database"); +const { logInfo, logError } = require("../../utils/logger.js"); async function insertTickets(tickets) { - try { - const values = tickets.map(t => [ - t.id_atendimento, - t.codigo_cliente, - t.status_atendimento, - t.servico_nome, - t.protocolo_hub, - t.ticket_mundiale, - t.codigo_servico, - t.cliente_nome, - t.descricao_fechamento, - t.data_fechamento, - t.created_at, - t.updated_at, - t.ticket_type - ]); + try { + const values = tickets.map(t => [ + t.id_atendimento, + t.codigo_cliente, + t.status_atendimento, + t.servico_nome, + t.protocolo_hub, + t.ticket_mundiale, + t.codigo_servico, + t.cliente_nome, + t.descricao_fechamento, + t.data_fechamento, + t.created_at, + t.updated_at, + t.ticket_type, + t.descricao_abertura, + t.endereco, + t.telefone, + t.cpf_cnpj, + t.tipo_pessoa, + t.email, + t.nome_razaosocial + ]); - const rowPlaceholders = values - .map( - (_, i) => - `(${Array(13) - .fill(0) - .map((_, j) => `$${i * 13 + j + 1}`) - .join(", ")})` - ) - .join(", "); + const totalColumns = 20; - const query = ` - INSERT INTO hubsoft_tickets - (id_atendimento, codigo_cliente, status_atendimento, servico_nome, protocolo_hub, ticket_mundiale, - codigo_servico, cliente_nome, descricao_fechamento, data_fechamento, created_at, updated_at, type) - VALUES ${rowPlaceholders} - ON CONFLICT (id_atendimento) - DO UPDATE SET - codigo_cliente = EXCLUDED.codigo_cliente, - status_atendimento = EXCLUDED.status_atendimento, - servico_nome = EXCLUDED.servico_nome, - protocolo_hub = EXCLUDED.protocolo_hub, - ticket_mundiale = EXCLUDED.ticket_mundiale, - codigo_servico = EXCLUDED.codigo_servico, - cliente_nome = EXCLUDED.cliente_nome, - descricao_fechamento = EXCLUDED.descricao_fechamento, - data_fechamento = EXCLUDED.data_fechamento, - updated_at = EXCLUDED.updated_at, - type = EXCLUDED.type - `; + const rowPlaceholders = values + .map( + (_, rowIndex) => + `(${Array.from({ length: totalColumns }) + .map((_, colIndex) => `$${rowIndex * totalColumns + colIndex + 1}`) + .join(", ")})` + ) + .join(", "); - const flattened = values.flat(); + const query = ` + INSERT INTO public.hubsoft_tickets + ( + id_atendimento, + codigo_cliente, + status_atendimento, + servico_nome, + protocolo_hub, + ticket_mundiale, + codigo_servico, + cliente_nome, + descricao_fechamento, + data_fechamento, + created_at, + updated_at, + ticket_type, + descricao_abertura, + endereco, + telefone, + cpf_cnpj, + tipo_pessoa, + email, + nome_razaosocial + ) + VALUES ${rowPlaceholders} + ON CONFLICT (id_atendimento) + DO UPDATE SET + codigo_cliente = EXCLUDED.codigo_cliente, + status_atendimento = EXCLUDED.status_atendimento, + servico_nome = EXCLUDED.servico_nome, + protocolo_hub = EXCLUDED.protocolo_hub, + ticket_mundiale = EXCLUDED.ticket_mundiale, + codigo_servico = EXCLUDED.codigo_servico, + cliente_nome = EXCLUDED.cliente_nome, + descricao_fechamento = EXCLUDED.descricao_fechamento, + data_fechamento = EXCLUDED.data_fechamento, + updated_at = EXCLUDED.updated_at, + ticket_type = EXCLUDED.ticket_type, + descricao_abertura = EXCLUDED.descricao_abertura, + endereco = EXCLUDED.endereco, + telefone = EXCLUDED.telefone, + cpf_cnpj = EXCLUDED.cpf_cnpj, + tipo_pessoa = EXCLUDED.tipo_pessoa, + email = EXCLUDED.email, + nome_razaosocial = EXCLUDED.nome_razaosocial + `; - await db.query(query, flattened); + const flattened = values.flat(); - return { success: true, ids: tickets.map(t => t.id_atendimento) }; + await hubglpi.query(query, flattened); - } catch (error) { - logError("Erro ao inserir tickets no HubGLPI:", error); - throw error; - } + logInfo(`Tickets salvos/atualizados: ${tickets.length}`); + + return { success: true, ids: tickets.map(t => t.id_atendimento) }; + + } catch (error) { + logError("Erro ao inserir tickets no HubGLPI:", error); + throw error; + } } async function insertSyncData(ids) { try { - const values = ids.map(id => [id]); - - const rowPlaceholders = values + const rowPlaceholders = ids .map((_, i) => `($${i + 1})`) .join(", "); - + const query = ` - INSERT INTO hubsoft_sync_data (hubsoft_ticket_id) + INSERT INTO sync_data (hubsoft_ticket_id) VALUES ${rowPlaceholders} - ON CONFLICT (hubsoft_ticket_id) DO UPDATE SET - hubsoft_ticket_id = $1 + ON CONFLICT (hubsoft_ticket_id) + DO UPDATE SET hubsoft_ticket_id = EXCLUDED.hubsoft_ticket_id `; - const flattened = ids; + await hubglpi.query(query, ids); - await db.query(query, flattened); + logInfo(`Sync data criada/atualizada para ${ids.length} tickets.`); return true; - } - catch (error) { + + } catch (error) { logError("Erro ao inserir dados de sincronização no HubGLPI:", error); throw error; } @@ -94,39 +127,79 @@ async function insertSyncData(ids) { async function fetchPendingTickets() { try { - const query = `SELECT ht.id_atendimento, ht.codigo_cliente, ht.status_atendimento, ht.codigo_servico, ht.servico_nome, ht.protocolo_hub, ht.ticket_mundiale, ht.cliente_nome, ht.created_at, sd.id AS sync_data_id, sd.glpi_ticket_id, sd.status_sync, sd.sync_metadata, sd.last_sync_attempt, sd.sync_error_message, sd.created_at AS sync_created_at, sd.updated_at AS sync_updated_at, ht.ticket_type as ticket_type - FROM hubsoft_tickets AS ht - LEFT JOIN sync_data AS sd ON ht.id_atendimento = sd.hubsoft_ticket_id - WHERE sd.status_sync IS NULL OR sd.status_sync = 'pending_create' - ORDER BY ht.created_at ASC;` - const result = await db.query(query); - return result.rows + const query = ` + SELECT + ht.id_atendimento, + ht.codigo_cliente, + ht.status_atendimento, + ht.codigo_servico, + ht.servico_nome, + ht.protocolo_hub, + ht.ticket_mundiale, + ht.cliente_nome, + ht.created_at, + ht.ticket_type, + + -- NOVOS CAMPOS + ht.descricao_abertura, + ht.endereco, + ht.telefone, + ht.cpf_cnpj, + ht.tipo_pessoa, + ht.email, + ht.nome_razaosocial, + + -- CAMPOS DA SYNC_DATA + sd.id AS sync_data_id, + sd.glpi_ticket_id, + sd.status_sync, + sd.sync_metadata, + sd.last_sync_attempt, + sd.sync_error_message, + sd.created_at AS sync_created_at, + sd.updated_at AS sync_updated_at + + FROM hubsoft_tickets AS ht + LEFT JOIN sync_data AS sd + ON ht.id_atendimento = sd.hubsoft_ticket_id + WHERE sd.status_sync IS NULL + OR sd.status_sync = 'pending_create' + ORDER BY ht.created_at ASC; + `; + + const result = await hubglpi.query(query); + return result.rows; + } catch (error) { logError("Erro ao buscar tickets pendentes no HubGLPI:", error); throw error; } } + async function updateSyncDataCreated(hubsoftTicketId, glpiId) { - const sql = ` - UPDATE sync_data - SET - status_sync = 'created_glpi', - updated_at = NOW(), - last_sync_attempt = NOW(), - glpi_ticket_id = ? - WHERE hubsoft_ticket_id = ? - `; + try { + const sql = ` + UPDATE sync_data + SET + status_sync = 'created_glpi', + updated_at = NOW(), + last_sync_attempt = NOW(), + glpi_ticket_id = $1 + WHERE hubsoft_ticket_id = $2 + `; - await db.query(sql, [glpiId, hubsoftTicketId]); + await hubglpi.query(sql, [glpiId, hubsoftTicketId]); + + } catch (error) { + logError("Erro ao atualizar sync_data para ticket criado no GLPI:", error); + throw error; + } } - - - module.exports = { insertTickets, insertSyncData, fetchPendingTickets, updateSyncDataCreated -}; \ No newline at end of file +}; diff --git a/src/shared/repositories/hubsoft.repository.js b/src/shared/repositories/hubsoft.repository.js index ab2276e..44b2b52 100644 --- a/src/shared/repositories/hubsoft.repository.js +++ b/src/shared/repositories/hubsoft.repository.js @@ -1,6 +1,6 @@ // src/shared/repositories/hubsoft.repository.js const { get } = require('pm2'); -const db = require('../../../config/database.js'); +const { hubsoft } = require("../../shared/infra/database"); const { logInfo, logError } = require('../../utils/logger.js'); @@ -8,7 +8,7 @@ async function getMundialeTickets() { try { const query = `SELECT a.id_atendimento, a.id_usuario_abertura, a.id_atendimento_status, a.protocolo, a.descricao_abertura, a.data_cadastro, a.nome_contato, c.codigo_cliente, s.descricao, cs.id_cliente_servico FROM atendimento AS a INNER JOIN cliente_servico AS cs ON a.id_cliente_servico = cs.id_cliente_servico INNER JOIN cliente AS c ON cs.id_cliente = c.id_cliente INNER JOIN servico AS s ON cs.id_servico = s.id_servico WHERE a.id_tipo_atendimento = 4 AND a.id_usuario_abertura = 248 AND a.id_atendimento_status IN (1, 2, 33) AND s.ativo = true;`; - const { rows } = await db.query(query); + const { rows } = await hubsoft.query(query); return rows; } catch (error) { logError("Erro ao buscar tickets Mundiale:", error); @@ -18,8 +18,67 @@ async function getMundialeTickets() { async function getImplantacaoTickets() { try { - const query = `SELECT a.id_atendimento, a.id_usuario_abertura, a.id_atendimento_status, a.protocolo, a.descricao_abertura, a.data_cadastro, a.nome_contato, c.codigo_cliente, s.descricao, cs.id_cliente_servico FROM atendimento AS a INNER JOIN cliente_servico AS cs ON a.id_cliente_servico = cs.id_cliente_servico INNER JOIN cliente AS c ON cs.id_cliente = c.id_cliente INNER JOIN servico AS s ON cs.id_servico = s.id_servico WHERE a.id_tipo_atendimento = 21 AND a.id_atendimento_status IN (1, 2, 33) AND s.ativo = true;`; - const { rows } = await db.query(query); + const query = ` + SELECT + a.id_atendimento, + a.id_usuario_abertura, + a.id_atendimento_status, + a.protocolo, + a.descricao_abertura, + a.data_cadastro, + a.nome_contato, + + -- DADOS DO CLIENTE (novos) + c.nome_razaosocial, + c.telefone_primario AS telefone, + c.cpf_cnpj, + c.tipo_pessoa, + c.email_principal AS email, + + c.codigo_cliente, + s.descricao AS descricao, + cs.id_cliente_servico, + + -- ENDEREÇO DE INSTALAÇÃO + en.endereco, + en.numero, + en.complemento, + en.bairro, + en.cep, + ci.nome AS cidade, + es.sigla AS estado + + FROM atendimento AS a + + INNER JOIN cliente_servico AS cs + ON a.id_cliente_servico = cs.id_cliente_servico + + INNER JOIN cliente AS c + ON cs.id_cliente = c.id_cliente + + INNER JOIN servico AS s + ON cs.id_servico = s.id_servico + + -- endereço do cliente + INNER JOIN cliente_servico_endereco AS cse + ON cse.id_cliente_servico = cs.id_cliente_servico + AND cse.tipo = 'instalacao' + + INNER JOIN endereco_numero AS en + ON en.id_endereco_numero = cse.id_endereco_numero + + INNER JOIN cidade AS ci + ON ci.id_cidade = en.id_cidade + + INNER JOIN estado AS es + ON es.id_estado = ci.id_estado + + WHERE + a.id_tipo_atendimento = 21 + AND a.id_atendimento_status IN (1, 2, 33) + AND s.ativo = TRUE; + `; + const { rows } = await hubsoft.query(query); return rows; } catch (error) { @@ -31,7 +90,7 @@ async function getImplantacaoTickets() { async function getCancelamentoTickets() { try { const query = `SELECT a.id_atendimento, a.id_usuario_abertura, a.id_atendimento_status, a.protocolo, a.descricao_abertura, a.data_cadastro, a.nome_contato, c.codigo_cliente, s.descricao, cs.id_cliente_servico FROM atendimento AS a INNER JOIN cliente_servico AS cs ON a.id_cliente_servico = cs.id_cliente_servico INNER JOIN cliente AS c ON cs.id_cliente = c.id_cliente INNER JOIN servico AS s ON cs.id_servico = s.id_servico WHERE a.id_tipo_atendimento = 27 AND a.id_atendimento_status IN (1, 2, 33) AND s.ativo = true;`; - const { rows } = await db.query(query); + const { rows } = await hubsoft.query(query); return rows; } catch (error) { logError("Erro ao buscar tickets Cancelamento:", error); @@ -42,7 +101,7 @@ async function getCancelamentoTickets() { async function getSacTickets() { try { const query = `SELECT a.id_atendimento, a.id_usuario_abertura, a.id_atendimento_status, a.protocolo, a.descricao_abertura, a.data_cadastro, a.nome_contato, c.codigo_cliente, s.descricao, cs.id_cliente_servico FROM atendimento AS a INNER JOIN cliente_servico AS cs ON a.id_cliente_servico = cs.id_cliente_servico INNER JOIN cliente AS c ON cs.id_cliente = c.id_cliente INNER JOIN servico AS s ON cs.id_servico = s.id_servico WHERE a.id_tipo_atendimento = 41 AND a.id_atendimento_status IN (1, 2, 33) AND s.ativo = true;`; - const { rows } = await db.query(query); + const { rows } = await hubsoft.query(query); return rows; } catch (error) { logError("Erro ao buscar tickets SAC:", error);