diff --git a/src/controller/processController.js b/src/controller/processController.js index 0d14cb9..4423ef2 100644 --- a/src/controller/processController.js +++ b/src/controller/processController.js @@ -62,8 +62,6 @@ const categoriaGLPI = { 'PABX IP - Até 30 ramais com telefones IPs (CAOA OUTROS ESTADOS)' : 5717 , 'Link de Internet Dedicado 700MB Full Duplex' : 5707 , 'Link de Internet Dedicado - Temporário' : 5707 - - } @@ -121,7 +119,7 @@ const formatTicketDataForGlpi = async (ticketData) => { type: 1, itilcategories_id: categoriaGLPI[ticketData.servico_nome] || 1, date_creation: new Date(), - // entidades_id: 0 //await glpiModel.selectEntityId() //TODO: Implementar a busca da entidade + // entidades_id: 0 //await glpiModel.selectyId() //TODO: Implementar a busca da entidade }; return formattedData; }; diff --git a/src/model/hubglpiModel.js b/src/model/hubglpiModel.js index 499795d..c3100b6 100644 --- a/src/model/hubglpiModel.js +++ b/src/model/hubglpiModel.js @@ -53,8 +53,7 @@ class HubglpiModel { logError(`Erro ao inserir/atualizar ticket na tabela hubsoft_tickets: ${err}`); throw err; } -} - + } static async insertSyncData(syncData) { @@ -114,8 +113,7 @@ class HubglpiModel { logError(`Erro ao atualizar dados na tabela sync_data: ${err}`); throw err; } -} - + } static async get_idSyncByHubsoftId(hubsoft_ticket_id) { const query = ` @@ -192,8 +190,9 @@ class HubglpiModel { logError(`Erro ao obter glpi_ticket_id por id_atendimento, ${err}`); throw err; } -} -static async updateClosingTicket(syncId, closeMessage) { + } + + static async updateClosingTicket(syncId, closeMessage) { const client = await pool.connect(); try { await client.query('BEGIN'); // inicia transação @@ -238,10 +237,7 @@ static async updateClosingTicket(syncId, closeMessage) { } finally { client.release(); } -} - - - + } static async updateSyncDataStatus( ticketId, status_sync, source_last){ const query = ` @@ -262,7 +258,6 @@ static async updateClosingTicket(syncId, closeMessage) { } } - static async updateSyncaDataError(sync_error_message, id_atendimento) { const query = ` UPDATE sync_data @@ -304,8 +299,6 @@ static async updateClosingTicket(syncId, closeMessage) { throw err; } } - - } module.exports = HubglpiModel; diff --git a/src/modules/createTickets/controller/createTickets.controller.js b/src/modules/createTickets/controller/createTickets.controller.js new file mode 100644 index 0000000..de6a0f7 --- /dev/null +++ b/src/modules/createTickets/controller/createTickets.controller.js @@ -0,0 +1,46 @@ +// 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 ticketShared = require('../services/createTickets.service.js'); + +const { logInfo } = require('../../utils/logger.js'); + +async function processaAtendimentos() { + logInfo("[CONTROLLER] Iniciando processamento"); + + // 1️⃣ Buscar por fonte + const mundiale = await mundialeService.fetchNew(); + const implantacao = await implantacaoService.fetchNew(); + const app = await appMobileService.fetchNew(); + + // 2️⃣ Salvar no hubglpi + await mundialeService.saveHubGlpi(mundiale); + await implantacaoService.saveHubglpi(implantacao); + await appMobileService.saveHubglpi(app); + + // 3️⃣ Buscar pendentes que foram salvos no banco hubglpi + const pendentes = await ticketShared.buscarPendentesHubglpi(); + + // 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 { + logInfo(`Tipo desconhecido, ignorando ticket id=${ticket.id}`); + continue; + } + + await ticketShared.marcarComoProcessado(ticket.id); + } + + logInfo("[CONTROLLER] Finalizado."); +} diff --git a/src/modules/createTickets/services/createTickets.service.js b/src/modules/createTickets/services/createTickets.service.js new file mode 100644 index 0000000..0fbcadb --- /dev/null +++ b/src/modules/createTickets/services/createTickets.service.js @@ -0,0 +1,44 @@ +// src/modules/createTickets/services/createTickets.service.js +const repositoryHubGlpi = require('../../../shared/repositories/hubglpi.repository.js'); + +// -------------------------------------- +// Funções principais do serviço +// -------------------------------------- + +async function fetcPendingTickets() { + return repositoryHubGlpi.fetchPendingTickets(); +} + +async function resolveEntityId(ticketData, glpiModel) { + + const entityByService = await glpiModel.selectEntityIdCodServico( + ticketData.codigo_cliente, + ticketData.codigo_servico + ); + + if (entityByService) { + ticketData.entities_id = entityByService; + return ticketData; + } + + // Tenta entidade pelo cliente + const entityByClient = await glpiModel.selectEntityIdCodCliente( + ticketData.codigo_cliente + ); + + if (entityByClient) { + ticketData.entities_id = entityByClient; + return ticketData; + } + + // Fallback + ticketData.entities_id = 0; + return ticketData; +} + + + +module.exports = { + fetcPendingTickets, + resolveEntityId +} \ No newline at end of file diff --git a/src/modules/createTickets/services/implantacao.service.js b/src/modules/createTickets/services/implantacao.service.js new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/createTickets/services/mundiale.service.js b/src/modules/createTickets/services/mundiale.service.js new file mode 100644 index 0000000..81d6b12 --- /dev/null +++ b/src/modules/createTickets/services/mundiale.service.js @@ -0,0 +1,115 @@ +// src/modules/createTickets/services/mundiale.service.js +const repositoryHubGlpi = require('../../../shared/repositories/hubglpi.repository.js'); +const repositoryGlpi = require('../../../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'); + +// -------------------------------------- +// Funções principais do serviço +// -------------------------------------- + +async function fetchNew() { + return repositoryHubsoft.getMundialeTickets(); +} + +async function saveHubGlpi(tickets) { + if (!tickets.length) return; + + const ticketsFormatted = tickets.map(ticket => modelHubGlpi.mapHubsoftToHubglpi(ticket, 'MUNDIALE')); + + 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, modelGlpi); + + const formattedTickets = formatGlpiPayload(ticketsResolved); + + const payload = modelGlpi.mapHubGlpiToGlpi(formattedTickets); + + const glpiId = await repositoryGlpi.insertTickets([payload]); + + await repositoryHubGlpi.updateSyncDataCreated(ticket.id_atendimento, glpiId[0]); + +} + + +// -------------------------------------- +// Formatar dados antes de enviar para o GLPI +// -------------------------------------- + +function formatGlpiPayload(ticket) { + + const statusTexto = statusAtendimentoHubGlpi[ticket.status_atendimento] || 'Novo'; + const statusGlpi = statusAtendimentoGLPI[statusTexto] || 1; + + const categoria = categoriaGLPI[ticket.servico_nome] || 0; + + const title = `[Mundiale] ${ticket.cliente_nome} - ${ticket.servico_nome} - ${ticket.ticket_mundiale}`; + + const description = formatDescription(ticket); + + return { + name: title, + content: description, + status: statusGlpi, + itilcategories_id: categoria, + created_at: ticket.created_at, + }; +} + + +const formatDescription = (ticketData) => { + + let htmlDescription = ` + + + + + + + + + + + + + + + + + + + + + + + + + +
CampoValor
Nome:${ticketData.cliente_nome}
Codigo:${ticketData.codigo_cliente}
Serviço:${ticketData.servico_nome}
Ticket Mundiale${ticketData.ticket_mundiale}
Protocolo Hub:${ticketData.protocolo_hub || 'N/A'}
+ `; + + return htmlDescription; +}; + +module.exports = { + fetchNew, + saveHubGlpi, + sendToGlpi +} + +/** + * @module CreateTickets/MundialeService + * @description Serviço responsável por interagir com o Hubsoft e GLPI criação de tickets que provêm da Mundiale. + */ \ No newline at end of file diff --git a/src/modules/createTickets/services/sac.service.js b/src/modules/createTickets/services/sac.service.js new file mode 100644 index 0000000..e69de29 diff --git a/src/scripts/data/database.sql b/src/scripts/data/database.sql index 4c249b6..795b68d 100644 --- a/src/scripts/data/database.sql +++ b/src/scripts/data/database.sql @@ -85,4 +85,11 @@ CREATE TABLE sync_control ( -- Inserir o registro inicial para o nosso novo cron de comentários INSERT INTO sync_control (job_name, last_run_timestamp) VALUES ('hubsoft_comments_sync', '2024-01-01 00:00:00'); +-- ============================================= +-- ALTERAÇÕES NA TABELA hubsoft_tickets +-- ============================================= +ALTER TABLE hubsoft_tickets +ADD COLUMN ticket_type VARCHAR(50) NOT NULL DEFAULT 'MUNDIALE'; +ALTER TABLE hubsoft_tickets +ADD CONSTRAINT hubsoft_id_atendimento_unique UNIQUE (id_atendimento); diff --git a/src/shared/model/glpi.model.js b/src/shared/model/glpi.model.js new file mode 100644 index 0000000..01db487 --- /dev/null +++ b/src/shared/model/glpi.model.js @@ -0,0 +1,37 @@ +// src/shared/model/glpi.model.js + +function mapHubGlpiToGlpi(ticket) { + return { + 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, + content: ticket.content, + urgency: 3, + impact: 3, + priority: 3, + type: 2, + date_creation : ticket.date_creation || Now(), + slas_id_ttr: 37 + }; +} + +function defineStatusGlpi(Status) { + const mapStatusDBtoGLPI = { + 'Novo': 1, + 'Pendente': 4, + 'Em atendimento': 2, + 'Resolvido': 5 + }; + + return mapStatusDBtoGLPI[Status] || 'Novo'; +}; + + + +module.exports = { + mapHubGlpiToGlpi, + defineStatusGlpi +}; diff --git a/src/shared/model/hubglpi.model.js b/src/shared/model/hubglpi.model.js new file mode 100644 index 0000000..a3bba71 --- /dev/null +++ b/src/shared/model/hubglpi.model.js @@ -0,0 +1,34 @@ +// src/shared/model/hubglpi.model.js + +function mapHubsoftToHubglpi(ticket, type) { + 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, + 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 + }; +} + +function defineStatusHubglpi(hubsoftStatus) { + const statusMap = { + 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 new file mode 100644 index 0000000..627659c --- /dev/null +++ b/src/shared/repositories/glpi.repository.js @@ -0,0 +1,40 @@ +// src/shared/repositories/glpi.repository.js +const db = require('../../../config/database.js'); +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(","); + + const sql = ` + INSERT INTO glpi_tickets + (status, content, users_id_recipient, entities_id, type, requesttypes_id, + urgency, impact, priority, date, name) + VALUES ${placeholders} + `; + + const result = await mariadb.execute(sql, values.flat()); + + return result.insertId ? [result.insertId] : []; +} + + +module.exports = { + insertTickets +}; \ No newline at end of file diff --git a/src/shared/repositories/hubglpi.repository.js b/src/shared/repositories/hubglpi.repository.js new file mode 100644 index 0000000..1525019 --- /dev/null +++ b/src/shared/repositories/hubglpi.repository.js @@ -0,0 +1,132 @@ +// 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'); + +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 + ]); + + const rowPlaceholders = values + .map( + (_, i) => + `(${Array(13) + .fill(0) + .map((_, j) => `$${i * 13 + j + 1}`) + .join(", ")})` + ) + .join(", "); + + 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 flattened = values.flat(); + + await db.query(query, flattened); + + 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 + .map((_, i) => `($${i + 1})`) + .join(", "); + + const query = ` + INSERT INTO hubsoft_sync_data (hubsoft_ticket_id) + VALUES ${rowPlaceholders} + ON CONFLICT (hubsoft_ticket_id) DO UPDATE SET + hubsoft_ticket_id = $1 + `; + + const flattened = ids; + + await db.query(query, flattened); + + return true; + } + catch (error) { + logError("Erro ao inserir dados de sincronização no HubGLPI:", error); + throw error; + } +} + +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 + } 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 = ? + `; + + await db.query(sql, [glpiId, hubsoftTicketId]); +} + + + + +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 new file mode 100644 index 0000000..ab2276e --- /dev/null +++ b/src/shared/repositories/hubsoft.repository.js @@ -0,0 +1,60 @@ +// src/shared/repositories/hubsoft.repository.js +const { get } = require('pm2'); +const db = require('../../../config/database.js'); +const { logInfo, logError } = require('../../utils/logger.js'); + + +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); + return rows; + } catch (error) { + logError("Erro ao buscar tickets Mundiale:", error); + throw error; + } +} + +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); + return rows; + + } catch (error) { + logError("Erro ao buscar tickets Implantação:", error); + throw error; + } +} + +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); + return rows; + } catch (error) { + logError("Erro ao buscar tickets Cancelamento:", error); + throw error; + } +} + +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); + return rows; + } catch (error) { + logError("Erro ao buscar tickets SAC:", error); + throw error; + } +} + + +module.exports = { + getMundialeTickets, + getImplantacaoTickets, + getCancelamentoTickets, + getSacTickets + +}; \ No newline at end of file