diff --git a/src/infra/db/repositories/hubsoft/tickets.repository.js b/src/infra/db/repositories/hubsoft/tickets.repository.js index ec00d4e..738722f 100644 --- a/src/infra/db/repositories/hubsoft/tickets.repository.js +++ b/src/infra/db/repositories/hubsoft/tickets.repository.js @@ -11,6 +11,7 @@ async function getTicketsByTipo({ try { const isImplantacao = Number(tipoAtendimento) === 21; const isCancelamento = Number(tipoAtendimento) === 27; + const istrocaTitularidade = Number(tipoAtendimento) === 60; let select = ` a.id_atendimento, @@ -32,7 +33,7 @@ async function getTicketsByTipo({ INNER JOIN servico AS s ON cs.id_servico = s.id_servico `; - if (isImplantacao || isCancelamento) { + if (isImplantacao || isCancelamento || istrocaTitularidade) { select += `, u.name AS vendedor, c.nome_razaosocial, diff --git a/src/modules/createTickets/controller/createTickets.controller.js b/src/modules/createTickets/controller/createTickets.controller.js deleted file mode 100644 index 6302906..0000000 --- a/src/modules/createTickets/controller/createTickets.controller.js +++ /dev/null @@ -1,51 +0,0 @@ -// src/modules/createTickets/controller/createTickets.controller.js -const mundialeService = require('../services/mundiale.service.js'); -const implantacaoService = require('../services/implantacao.service.js'); -const sacService = require('../services/sac.service.js'); -const ticketShared = require('../services/createTickets.service.js'); - -const { logInfo } = require('../../../utils/logger.js'); - -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(); - 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 sacService.saveHubGlpi(sac); - - // 3️⃣ Buscar pendentes que foram salvos no banco hubglpi - 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 === 'SAC') { - await sacService.sendToGlpi(ticket); - - } else { - logInfo(`Tipo desconhecido, ignorando ticket id=${ticket.id}`); - continue; - } - - } - - logInfo("[CONTROLLER] Finalizado."); -} - -module.exports = { processAtendimentos }; - - diff --git a/src/modules/createTickets/services/createTickets.service.js b/src/modules/createTickets/services/createTickets.service.js deleted file mode 100644 index c4b98a9..0000000 --- a/src/modules/createTickets/services/createTickets.service.js +++ /dev/null @@ -1,60 +0,0 @@ -// 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 fetchPendingTickets() { - return repositoryHubGlpi.fetchPendingTickets(); -} - -async function resolveEntityId(ticketData) { - - const entityByService = await repositoryGlpi.getEntitiesByService( - ticketData.codigo_cliente, - ticketData.codigo_servico - ); - - if (entityByService) { - ticketData.entities_id = entityByService; - return ticketData; - } - - const entityByClient = await repositoryGlpi.getEntitiesByClient( - ticketData.codigo_cliente - ); - - if (entityByClient) { - ticketData.entities_id = entityByClient; - return ticketData; - } - - 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 = { - fetchPendingTickets, - resolveEntityId, - setAsCreated, - createEntity -} \ No newline at end of file diff --git a/src/modules/createTickets/services/mundiale.service.js b/src/modules/createTickets/services/mundiale.service.js deleted file mode 100644 index 049d821..0000000 --- a/src/modules/createTickets/services/mundiale.service.js +++ /dev/null @@ -1,126 +0,0 @@ -// 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, logError, logWarning } = require('../../../utils/logger.js'); -const { log } = require('winston'); - -// -------------------------------------- -// 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); - - formatGlpiPayload(ticketsResolved); - - 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, 'MUNDIALE'); - 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}`); - -} - -// -------------------------------------- -// 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 -// -------------------------------------- - -function formatGlpiPayload(ticket) { - - ticket.status = statusAtendimentoGLPI[ticket.status_atendimento] || 1; - - ticket.name = `Mundiale - Protocolo: ${ticket.ticket_mundiale} - ${ticket.cliente_nome}`; - - ticket.content = formatDescription(ticket); - - - return; -} - - -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 deleted file mode 100644 index e69de29..0000000 diff --git a/src/modules/createTickets/services/implantacao.service.js b/src/modules/tickets/models/glpi/titularidade.model.js similarity index 56% rename from src/modules/createTickets/services/implantacao.service.js rename to src/modules/tickets/models/glpi/titularidade.model.js index f7d763f..d971d30 100644 --- a/src/modules/createTickets/services/implantacao.service.js +++ b/src/modules/tickets/models/glpi/titularidade.model.js @@ -1,113 +1,44 @@ -// 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'); +// src/modules/tickets/models/glpi/implantacao.model.js -// -------------------------------------- -// Funções principais do serviço -// -------------------------------------- - -async function fetchNew() { - return repositoryHubsoft.getImplantacaoTickets(); +function toGlpiPayload(ticket) { + return { + entities_id: ticket.entities_id || 0, + name: buildTitle(ticket), + content: buildHtml(ticket), + status: resolveGlpiStatus(ticket.status_atendimento), + date: ticket.created_at, + date_mod: new Date(), + users_id_recipient: process.env.GLPI_USER_ID || 0, + urgency: 3, + impact: 3, + priority: 3, + type: 2, + date_creation: ticket.created_at || new Date(), + itilcategories_id: 0, + slas_id_ttr: 37, + } } -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; +function resolveGlpiStatus(status) { + const map = { + 'Novo': 1, + 'Pendente': 4, + 'Em atendimento': 2, + 'Resolvido': 5 + } + return map[status] || 1 } -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, 'IMPLANTACAO'); - await repositoryHubGlpi.updateSyncDataCreated(ticket.id_atendimento, glpiId); +function buildTitle(ticket) { + return `TROCA DE TITULARIDADE - ${ticket.codigo_cliente}-${ticket.codigo_servico} - ${ticket.nome_razaosocial} - ${ticket.servico_nome}` } +function buildHtml(ticket) { + const docLabel = ticket.tipo_pessoa === 'pf' ? 'CPF' : 'CNPJ' + const documento = formatDocument(ticket.cpf_cnpj, ticket.tipo_pessoa) + const servico = resolveService(ticket.servico_nome) -// -------------------------------------- -// 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 ` + return ` @@ -124,7 +55,7 @@ const formatDescription = (ticket) => { - + @@ -141,17 +72,17 @@ const formatDescription = (ticket) => { - + - + - + @@ -201,54 +132,62 @@ const formatDescription = (ticket) => { - +
Gerente Responsável${ticket.usuario_que_abriu || "N/A"}${ticket.vendedor || "N/A"}
Cliente${ticket.cliente_nome}${ticket.nome_razaosocial}
${docLabel}${documentoFormatado}${documento}
Nome Contato${ticket.nome_razaosocial || "N/A"}${ticket.cliente_nome || "N/A"}
${ticket.descricao_abertura || "N/A"} + ${nl2br(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"); + return cpf?.replace(/\D/g, '') + .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"); + return cnpj?.replace(/\D/g, '') + .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); + if (!doc) return 'N/A' + return tipo === 'pf' ? formatCPF(doc) : formatCNPJ(doc) } +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 }, -// -------------------------------------- -// Resolve Serviço -// -------------------------------------- + "Link de Internet Dedicado 20 Mbps Full Duplex": + { produto: "Link de Internet Dedicado", qtd: 1, descricao: "20 Mbps Full Duplex" }, -function resolveService(servicoNome) { - return serviceDictionary[servicoNome] || { - produto: servicoNome, - qtd: 1, - descricao: null - }; -} + "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" }, -// -------------------------------------- -// Exportação -// -------------------------------------- + "Link de Internet Dedicado 2 Gbps Full Duplex": + { produto: "Link de Internet Dedicado", qtd: 1, descricao: "2 Gbps Full Duplex" }, -module.exports = { - fetchNew, - saveHubGlpi, - sendToGlpi }; + +function resolveService(name) { + return serviceDictionary[name] || { produto: name, qtd: 1, descricao: null } +} + +function nl2br(text) { + if (!text) return '' + return text.replace(/\r\n|\n|\r/g, '
') +} + +module.exports = { toGlpiPayload } \ No newline at end of file diff --git a/src/modules/tickets/repositories/ticket.repository.js b/src/modules/tickets/repositories/ticket.repository.js index a6b90c1..d94eab5 100644 --- a/src/modules/tickets/repositories/ticket.repository.js +++ b/src/modules/tickets/repositories/ticket.repository.js @@ -60,7 +60,10 @@ async function getSacTickets(watermark) { } async function getTrocaTTickets(watermark) { - return hubsoftTicketsRepo.get(watermark) + return hubsoftTicketsRepo.getTicketsByTipo({ + tipoAtendimento: TYPES.TITULARIDADE, + watermark + }); } async function insertTicketsHubGlpi(tickets){ diff --git a/src/modules/tickets/services/ticketNotifications.service.js b/src/modules/tickets/services/ticketNotifications.service.js index d695585..26e6f51 100644 --- a/src/modules/tickets/services/ticketNotifications.service.js +++ b/src/modules/tickets/services/ticketNotifications.service.js @@ -8,7 +8,7 @@ async function fetchPendingTickets() { async function notifyTicketCreated(hubId, glpiId) { const message = `Atendimento ${glpiId} criado com sucesso` - //await repository.sendHubsoftMessage(hubId, message) + await repository.sendHubsoftMessage(hubId, message) await repository.sendHubglpiMessage(hubId, message) } diff --git a/src/modules/tickets/services/trocaTitularidade.service.js b/src/modules/tickets/services/trocaTitularidade.service.js new file mode 100644 index 0000000..c50fab7 --- /dev/null +++ b/src/modules/tickets/services/trocaTitularidade.service.js @@ -0,0 +1,43 @@ +// src/modules/tickets/services/trocaTitularidade.service.js + +const repository = require('../repositories/ticket.repository.js') +const modelHubGlpi = require('../models/hubglpi/model.js') +const ticketEntityResolver = require('./resolveTicketEntity.service.js') +const ttmodel = require('../models/glpi/titularidade.model.js') +const { logInfo, logError } = require('../../../shared/utils/logger.js') + +async function fetchNew(watermark) { + logInfo('[TROCA_TITULARIDADE] Coletando novos chamados') + const raw = await repository.getTrocaTTickets(watermark) + return raw.map(t => modelHubGlpi.fromHubsoft(t, 'TITULARIDADE')) +} + +async function saveHubGlpi(tickets) { + if (!tickets.length) return + logInfo('[TROCA_TITULARIDADE] Salvando tickets no HubGLPI') + await repository.insertTicketsHubGlpi(tickets) + await repository.insertSyncDataByIds(tickets.map(t => t.id_atendimento)) +} + +async function sendToGlpi(ticket) { + logInfo(`[TROCA_TITULARIDADE] Enviando ticket ${ticket.id_atendimento} para GLPI`) + try { + const resolved = await ticketEntityResolver.resolveEntityId(ticket) + const payload = ttmodel.toGlpiPayload(resolved) + + const glpiId = await repository.insertTicketGlpi(payload) + await repository.insertGroupTicket(glpiId, 'TITULARIDADE') + await repository.updateSyncDataCreated(ticket.id_atendimento, glpiId) + + return glpiId + } catch (err) { + logError(err, `[TROCA_TITULARIDADE] Erro ao enviar ticket ${ticket.id_atendimento}`) + throw err + } +} + +module.exports = { + fetchNew, + saveHubGlpi, + sendToGlpi +} diff --git a/src/modules/tickets/useCases/syncTickets.usecase.js b/src/modules/tickets/useCases/syncTickets.usecase.js index 3861520..5aec2e5 100644 --- a/src/modules/tickets/useCases/syncTickets.usecase.js +++ b/src/modules/tickets/useCases/syncTickets.usecase.js @@ -6,6 +6,7 @@ const mundialeService = require('../services/mundiale.service.js') const implantacaoService = require('../services/implantacao.service.js') const cancelamentoService = require('../services/cancelamento.service.js') //const sacService = require('../services/sac.service.js') //TODO +const trocaTitularidadeService = require('../services/trocaTitularidade.service.js') //TODO const getAuthToken = require('../../../infra/api/hubsoft.auth.js') @@ -30,16 +31,25 @@ async function syncTicketsUseCase() { const cancelamento = await cancelamentoService.fetchNew(waterMark) logInfo(`[USECASE] ${cancelamento.length} tickets Cancelamento encontrados`) + //const sac = await sacService.fetchNew(waterMark) //TODO + //logInfo(`[USECASE] ${sac.length} tickets SAC encontrados`) + + const trocaTitularidade = await trocaTitularidadeService.fetchNew(waterMark) //TODO + logInfo(`[USECASE] ${trocaTitularidade.length} tickets Troca de Titularidade encontrados`) - await mundialeService.saveHubGlpi(mundiale) - await implantacaoService.saveHubGlpi(implantacao) - await cancelamentoService.saveHubGlpi(cancelamento) + //await mundialeService.saveHubGlpi(mundiale) + //await implantacaoService.saveHubGlpi(implantacao) + //await cancelamentoService.saveHubGlpi(cancelamento) + //await sacService.saveHubGlpi(sac) //TODO + await trocaTitularidadeService.saveHubGlpi(trocaTitularidade) //TODO const allFetchedTickets = [ ...mundiale, ...implantacao, - ...cancelamento + ...cancelamento, + //...sac, + ...trocaTitularidade ] const newWaterMark = resolveNewWatermark(allFetchedTickets, waterMark) @@ -73,7 +83,7 @@ function resolveTicketService(type) { IMPLANTACAO: implantacaoService, CANCELAMENTO: cancelamentoService, //SAC: sacService, //TODO - //TITULARIDADE: trocaTitularidadeService //TODO + TITULARIDADE: trocaTitularidadeService //TODO } return map[type]