FEATURE: Implementado serviço de notificação para Admin e colaboradores, Adicionado validador de ticket updade, e modificado email para envio bonito
This commit is contained in:
parent
ad28159185
commit
53bdd1c5a5
@ -45,11 +45,28 @@ async function insertTicket(ticketData) {
|
|||||||
const [result] = await db.query(query, values)
|
const [result] = await db.query(query, values)
|
||||||
return result.insertId
|
return result.insertId
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logError('Erro ao inserir ticket no GLPI', err)
|
logError('[GLPI][REPOSITORY]Erro ao inserir ticket no GLPI', err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkTicketUpdateDate(glpiTicketId) {
|
||||||
|
const query = `
|
||||||
|
SELECT date_mod
|
||||||
|
FROM glpi_tickets
|
||||||
|
WHERE id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [rows] = await db.query(query, [glpiTicketId])
|
||||||
|
return rows[0]
|
||||||
|
} catch (err) {
|
||||||
|
logError('[GLPI][REPOSITORY]Erro ao verificar data de atualização do ticket no GLPI', err)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
insertTicket
|
insertTicket,
|
||||||
|
checkTicketUpdateDate
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ function buildStatus(kind, type) {
|
|||||||
/**
|
/**
|
||||||
* Verifica se a notificaÇõÇœo jÇ foi enviada com sucesso
|
* Verifica se a notificaÇõÇœo jÇ foi enviada com sucesso
|
||||||
*/
|
*/
|
||||||
async function notificationAlreadySentFunc(hubsoftTicketId, type = 'func') {
|
async function notificationAlreadySent(hubsoftTicketId, type = 'func') {
|
||||||
const status = buildStatus('sent', type);
|
const status = buildStatus('sent', type);
|
||||||
const query = `
|
const query = `
|
||||||
SELECT 1
|
SELECT 1
|
||||||
@ -38,12 +38,12 @@ async function notificationAlreadySentFunc(hubsoftTicketId, type = 'func') {
|
|||||||
/**
|
/**
|
||||||
* Marca tickets como pendente de notificaÇõÇœo
|
* Marca tickets como pendente de notificaÇõÇœo
|
||||||
*/
|
*/
|
||||||
async function markNotificationsAsPendingFunc(hubsoftTicketIds, type = 'func') {
|
async function markNotificationsAsPending(hubsoftTicketIds, type = 'func') {
|
||||||
if (!hubsoftTicketIds || hubsoftTicketIds.length === 0) {
|
if (!hubsoftTicketIds || hubsoftTicketIds.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const status = buildStatus('pending', type);
|
const status = buildStatus('pending', type); // Define o status como 'pending_func' ou 'pending_adm'
|
||||||
const query = `
|
const query = `
|
||||||
INSERT INTO watchdog_notifications (ticket_id, notified_at, status)
|
INSERT INTO watchdog_notifications (ticket_id, notified_at, status)
|
||||||
SELECT
|
SELECT
|
||||||
@ -68,7 +68,7 @@ async function markNotificationsAsPendingFunc(hubsoftTicketIds, type = 'func') {
|
|||||||
/**
|
/**
|
||||||
* Marca tickets como notificados com sucesso
|
* Marca tickets como notificados com sucesso
|
||||||
*/
|
*/
|
||||||
async function markNotificationsAsSentFunc(hubsoftTicketIds, type = 'func') {
|
async function markNotificationsAsSent(hubsoftTicketIds, type = 'func') {
|
||||||
const ids = (hubsoftTicketIds || [])
|
const ids = (hubsoftTicketIds || [])
|
||||||
.map(id => Number(id))
|
.map(id => Number(id))
|
||||||
.filter(Number.isFinite);
|
.filter(Number.isFinite);
|
||||||
@ -99,7 +99,7 @@ async function markNotificationsAsSentFunc(hubsoftTicketIds, type = 'func') {
|
|||||||
/**
|
/**
|
||||||
* Marca tickets como falha de notificaÇõÇœo
|
* Marca tickets como falha de notificaÇõÇœo
|
||||||
*/
|
*/
|
||||||
async function markNotificationsAsFailedFunc(hubsoftTicketIds, type = 'func') {
|
async function markNotificationsAsFailed(hubsoftTicketIds, type = 'func') {
|
||||||
const ids = (hubsoftTicketIds || [])
|
const ids = (hubsoftTicketIds || [])
|
||||||
.map(id => Number(id))
|
.map(id => Number(id))
|
||||||
.filter(Number.isFinite);
|
.filter(Number.isFinite);
|
||||||
@ -127,8 +127,39 @@ async function markNotificationsAsFailedFunc(hubsoftTicketIds, type = 'func') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marca tickets como fluxo concluido
|
||||||
|
*/
|
||||||
|
async function markNotificationsAsCompleted(hubsoftTicketIds, type = 'func') {
|
||||||
|
const ids = (hubsoftTicketIds || [])
|
||||||
|
.map(id => Number(id))
|
||||||
|
.filter(Number.isFinite);
|
||||||
|
|
||||||
async function getPendingTicketsForNotificationFunc(type = 'func') {
|
if (ids.length === 0) return;
|
||||||
|
|
||||||
|
const status = buildStatus('completed', type);
|
||||||
|
const query = `
|
||||||
|
INSERT INTO watchdog_notifications (ticket_id, notified_at, status)
|
||||||
|
SELECT
|
||||||
|
unnest($1::bigint[]),
|
||||||
|
NOW(),
|
||||||
|
$2
|
||||||
|
ON CONFLICT (ticket_id)
|
||||||
|
DO UPDATE SET
|
||||||
|
notified_at = EXCLUDED.notified_at,
|
||||||
|
status = $2;
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.query(query, [ids, status]);
|
||||||
|
} catch (error) {
|
||||||
|
logError('[WATCHDOG][REPOSITORY] Erro ao marcar notificaÇÎÇæÇÎÇÝes como concluido', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function getPendingTicketsForNotification(type = 'func') {
|
||||||
const status = buildStatus('pending', type);
|
const status = buildStatus('pending', type);
|
||||||
const query = `
|
const query = `
|
||||||
SELECT
|
SELECT
|
||||||
@ -154,10 +185,51 @@ async function getPendingTicketsForNotificationFunc(type = 'func') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Busca chamados com status sent_func com notified_at anterior a thresholdDate
|
||||||
|
*/
|
||||||
|
|
||||||
|
async function getSentNotificationsBefore(thresholdDate, type = 'func') {
|
||||||
|
const status = buildStatus('sent', type);
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
SELECT
|
||||||
|
wn.ticket_id AS hubsoft_ticket_id,
|
||||||
|
ht.protocolo_hub as protocolo_hub,
|
||||||
|
ht.ticket_mundiale AS ticket_mundiale,
|
||||||
|
wn.notified_at AS closed_at,
|
||||||
|
sd.glpi_ticket_id AS glpi_ticket_id
|
||||||
|
FROM watchdog_notifications wn
|
||||||
|
INNER JOIN sync_data sd
|
||||||
|
ON wn.ticket_id = sd.hubsoft_ticket_id
|
||||||
|
INNER JOIN hubsoft_tickets ht
|
||||||
|
ON wn.ticket_id = ht.id_atendimento
|
||||||
|
WHERE wn.status = $1 AND wn.notified_at > $2;
|
||||||
|
`;
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { rows } = await db.query(query, [status, thresholdDate]);
|
||||||
|
return rows;
|
||||||
|
} catch (error) {
|
||||||
|
logError('Erro ao buscar notificaÇõÇœes enviadas antes da data limite', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getTicketsPendingResponse(sinceMinutes = 30, type = 'func') {
|
||||||
|
const thresholdDate = new Date(Date.now() - sinceMinutes * 60 * 1000);
|
||||||
|
return getSentNotificationsBefore(thresholdDate, type);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
notificationAlreadySent: notificationAlreadySentFunc,
|
notificationAlreadySent,
|
||||||
markNotificationsAsSent: markNotificationsAsSentFunc,
|
markNotificationsAsSent,
|
||||||
markNotificationsAsFailed: markNotificationsAsFailedFunc,
|
markNotificationsAsFailed,
|
||||||
markNotificationsAsPending: markNotificationsAsPendingFunc,
|
markNotificationsAsPending,
|
||||||
getPendingTicketsForNotification: getPendingTicketsForNotificationFunc
|
markNotificationsAsCompleted,
|
||||||
|
getPendingTicketsForNotification,
|
||||||
|
getSentNotificationsBefore,
|
||||||
|
getTicketsPendingResponse
|
||||||
};
|
};
|
||||||
|
|||||||
@ -101,18 +101,18 @@ async function getTicketsClosedSince(thresholdDate) {
|
|||||||
if (process.env.HUBSOFT_MOCK_ENABLED === 'true') {
|
if (process.env.HUBSOFT_MOCK_ENABLED === 'true') {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id_atendimento: 2780,
|
id_atendimento: 2949,
|
||||||
protocolo: '20260106155510498970',
|
protocolo: '20260120133512641803',
|
||||||
hubsoft_closed_at: new Date('2026-01-06T08:20:45')
|
hubsoft_closed_at: new Date('2026-01-06T08:20:45')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id_atendimento: 2769,
|
id_atendimento: 2950,
|
||||||
protocolo: '20260105170715994323',
|
protocolo: '20260120133618108141',
|
||||||
hubsoft_closed_at: new Date('2026-01-06T10:02:13')
|
hubsoft_closed_at: new Date('2026-01-06T10:02:13')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id_atendimento: 2779,
|
id_atendimento: 2955,
|
||||||
protocolo: '20260106145016864639',
|
protocolo: '20260120134024457448',
|
||||||
hubsoft_closed_at: new Date('2025-12-18T14:35:56')
|
hubsoft_closed_at: new Date('2025-12-18T14:35:56')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -65,7 +65,13 @@ async function syncTicketsUseCase() {
|
|||||||
if (!service) continue
|
if (!service) continue
|
||||||
|
|
||||||
const glpiId = await service.sendToGlpi(ticket)
|
const glpiId = await service.sendToGlpi(ticket)
|
||||||
|
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
logInfo(`[USECASE] Ambiente de desenvolvimento. Ignorando notificação do ticket ${ticket.id_atendimento}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
await notifyTicketCreated.notifyTicketCreated(ticket.id_atendimento, glpiId)
|
await notifyTicketCreated.notifyTicketCreated(ticket.id_atendimento, glpiId)
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logError(err, `[USECASE] Falha ao processar ticket ${ticket.id_atendimento}`)
|
logError(err, `[USECASE] Falha ao processar ticket ${ticket.id_atendimento}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,65 +3,16 @@
|
|||||||
require('dotenv').config({ path: '.env.development' })
|
require('dotenv').config({ path: '.env.development' })
|
||||||
|
|
||||||
const { logError, logInfo } = require('../../../shared/utils/logger')
|
const { logError, logInfo } = require('../../../shared/utils/logger')
|
||||||
const repository = require('../repository/watchdog.repository.js')
|
const { notifyCollaborators } = require('../services/notifyCollaborators.service.js')
|
||||||
const model = require('../model/email.model.js')
|
const { notifyAdmins } = require('../services/notifyAdmins.service.js')
|
||||||
|
|
||||||
async function runWatchdog() {
|
async function runWatchdog() {
|
||||||
|
const { sentCount: sentFunc } = await notifyCollaborators({ thresholdMinutes: 31 })
|
||||||
|
const { sentCount: sentAdm } = await notifyAdmins({ sinceMinutes: 30 })
|
||||||
|
|
||||||
logInfo('[WATCHDOG] [JOB] Coletando chamados fechados hÇ 31 minutos')
|
logInfo(`[WATCHDOG] [JOB] Enviadas ${sentFunc} notificacoes para colaboradores e ${sentAdm} para ADM.`)
|
||||||
|
|
||||||
const thresholdDate = new Date(Date.now() - 31 * 60 * 1000)
|
|
||||||
|
|
||||||
const closedTickets = await repository.getClosedTicketsSince(thresholdDate)
|
|
||||||
logInfo(`[WATCHDOG] [JOB] Encontrados ${closedTickets.length} chamados fechados`)
|
|
||||||
|
|
||||||
let notificationType = 'func'
|
|
||||||
|
|
||||||
for (const ticket of closedTickets) {
|
|
||||||
|
|
||||||
const hubGlpiTicket = await repository.checkTicketInHubGlpi(ticket.id_atendimento)
|
|
||||||
|
|
||||||
if (!hubGlpiTicket.exists) {
|
|
||||||
logInfo(`[WATCHDOG] [JOB] Chamado ${ticket.id_atendimento} nao encontrado no HubGlpi. Ignorando.`)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hubGlpiTicket.status === 'closed') {
|
|
||||||
logInfo(`[WATCHDOG] [JOB] Chamado ${ticket.id_atendimento} ja esta fechado no HubGlpi. Ignorando.`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (await repository.notificationAlreadySent(ticket.id_atendimento, notificationType)) {
|
|
||||||
logInfo(`[WATCHDOG] [JOB] NotificaÇõÇœo jÇ enviada para o chamado ${ticket.id_atendimento}.`)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
await repository.markNotificationsAsPending([ticket.id_atendimento], notificationType)
|
|
||||||
|
|
||||||
}
|
|
||||||
const ticketsToNotify = await repository.getPendingTicketsForNotification(notificationType)
|
|
||||||
logInfo(`[WATCHDOG] [JOB] ${ticketsToNotify.length} chamados pendentes para notificaÇõÇœo.`)
|
|
||||||
|
|
||||||
if (!ticketsToNotify.length) {
|
|
||||||
logInfo('[WATCHDOG] [JOB] Nenhum chamado pendente para notificaÇõÇœo')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const payload = await model.prepareNotificationPayload(ticketsToNotify)
|
|
||||||
const hubsoftTicketIds = ticketsToNotify.map(t => Number(t.hubsoft_ticket_id)).filter(Number.isFinite);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await repository.sendClosureNotifications(payload)
|
|
||||||
|
|
||||||
|
|
||||||
await repository.markNotificationsAsSent(hubsoftTicketIds, notificationType)
|
|
||||||
} catch (err) {
|
|
||||||
logError('[WATCHDOG] Erro ao enviar notificaÇõÇæes', err)
|
|
||||||
await repository.markNotificationsAsFailed(hubsoftTicketIds, notificationType)
|
|
||||||
}
|
|
||||||
|
|
||||||
logInfo(`[WATCHDOG] [JOB] Enviadas ${ticketsToNotify.length} notificaÇõÇæes`)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
runWatchdog().catch((error) => {
|
runWatchdog().catch((error) => {
|
||||||
logError('[WATCHDOG] [JOB] Erro ao executar o job do Watchdog', error)
|
logError('[WATCHDOG] [JOB] Erro ao executar o job do Watchdog', error)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,83 +1,118 @@
|
|||||||
// src/modules/watchdog/model/email.model.js
|
// src/modules/watchdog/model/email.model.js
|
||||||
|
|
||||||
function prepareNotificationPayload(tickets) {
|
function prepareNotificationPayload(tickets, type = 'func') {
|
||||||
|
const normalizedType = String(type || 'func').toLowerCase()
|
||||||
|
const isAdm = normalizedType === 'adm'
|
||||||
|
|
||||||
return {
|
return {
|
||||||
subject: buildSubject(tickets),
|
subject: buildSubject(tickets, normalizedType),
|
||||||
bodyEmail: buildBodyEmail(tickets),
|
bodyEmail: isAdm ? buildBodyEmailAdm(tickets) : buildBodyEmail(tickets),
|
||||||
recipients: getRecipients(),
|
recipients: getRecipients(normalizedType),
|
||||||
cc: getCc()
|
cc: getCc(normalizedType)
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSubject(tickets) {
|
function buildSubject(tickets, type = 'func') {
|
||||||
|
if (type === 'adm') {
|
||||||
|
if (tickets.length === 0) {
|
||||||
|
return '🧤⚽ [GOLEIRO ADM] Nenhum chamado foi para os pênaltis'
|
||||||
|
}
|
||||||
|
|
||||||
|
return `🚨⚽ [GOLEIRO ADM] ${tickets.length} chamados foram para os pênaltis`
|
||||||
|
}
|
||||||
|
|
||||||
if (tickets.length === 0) {
|
if (tickets.length === 0) {
|
||||||
return '[GOLEIRO] Nenhum chamado foi agarrado';
|
return '🧤 [GOLEIRO] Nenhum chamado foi agarrado'
|
||||||
}
|
}
|
||||||
|
|
||||||
return `🧤 [GOLEIRO] ${tickets.length} chamados foram agarrados`;
|
return `🧤 [GOLEIRO] ${tickets.length} chamados foram agarrados`
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildBodyEmail(tickets) {
|
function buildBodyEmail(tickets) {
|
||||||
let body = `
|
let body = `
|
||||||
<p>🚨 <strong>Atenção!</strong></p>
|
<p>🧤 <strong>Atenção, time!</strong></p>
|
||||||
<p>O goleiro defendeu os seguintes chamados:</p>
|
<p>O goleiro entrou em ação e defendeu os seguintes chamados:</p>
|
||||||
<p>Esses chamados foram <strong>fechados no Hubsoft</strong>, mas ainda constam como <strong>abertos no GLPI</strong>.</p>
|
<p>
|
||||||
|
Esses chamados foram <strong>fechados no Hubsoft</strong>,
|
||||||
|
mas ainda constam como <strong>abertos no GLPI</strong>.
|
||||||
|
</p>
|
||||||
<br>
|
<br>
|
||||||
<ul>
|
<ul>
|
||||||
`;
|
`
|
||||||
|
|
||||||
tickets.forEach(ticket => {
|
tickets.forEach(ticket => {
|
||||||
body += `
|
body += `
|
||||||
<li>
|
<li>
|
||||||
Protocolo Hubsoft: <strong>${ticket.protocolo_hub}</strong><br>
|
🧾 <strong>Protocolo Hubsoft:</strong> ${ticket.protocolo_hub}<br>
|
||||||
Mundiale ID: <strong>${ticket.ticket_mundiale}</strong><br>
|
🧠 <strong>Mundiale ID:</strong> ${ticket.ticket_mundiale}<br>
|
||||||
GLPI ID: <strong>${ticket.glpi_ticket_id ?? 'não encontrado'}</strong><br>
|
🛠 <strong>GLPI ID:</strong> ${ticket.glpi_ticket_id ?? 'não encontrado'}<br>
|
||||||
Fechado em: ${formatDate(ticket.closed_at)}
|
⏱ <strong>Fechado em:</strong> ${formatDate(ticket.closed_at)}
|
||||||
</li>
|
</li>
|
||||||
<br>
|
<br>
|
||||||
`;
|
`
|
||||||
});
|
})
|
||||||
|
|
||||||
body += `
|
body += `
|
||||||
</ul>
|
</ul>
|
||||||
<br>
|
<br>
|
||||||
<p>⚽ Favor verificar e alinhar os status no GLPI.</p>
|
<p>⚠️ Favor verificar e alinhar os status no GLPI.</p>
|
||||||
<p><em>Watchdog Hub × GLPI</em></p>
|
<p><em>Watchdog Hub × GLPI</em></p>
|
||||||
`;
|
`
|
||||||
|
|
||||||
return body;
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBodyEmailAdm(tickets) {
|
||||||
|
let body = `
|
||||||
|
<p>🚨⚽ <strong>Pênalti!</strong></p>
|
||||||
|
<p>Os seguintes chamados passaram da defesa inicial:</p>
|
||||||
|
<p>
|
||||||
|
Esses chamados foram <strong>fechados no Hubsoft há mais de 1 hora</strong>
|
||||||
|
e ainda <strong>não tiveram atualização no GLPI</strong>.
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<ul>
|
||||||
|
`
|
||||||
|
|
||||||
|
tickets.forEach(ticket => {
|
||||||
|
body += `
|
||||||
|
<li>
|
||||||
|
🧾 <strong>Protocolo Hubsoft:</strong> ${ticket.protocolo_hub}<br>
|
||||||
|
🧠 <strong>Mundiale ID:</strong> ${ticket.ticket_mundiale}<br>
|
||||||
|
🛠 <strong>GLPI ID:</strong> ${ticket.glpi_ticket_id ?? 'não encontrado'}<br>
|
||||||
|
⏱ <strong>Fechado em:</strong> ${formatDate(ticket.closed_at)}
|
||||||
|
</li>
|
||||||
|
<br>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
body += `
|
||||||
|
</ul>
|
||||||
|
<br>
|
||||||
|
<p>📢 Favor acionar o time responsável e alinhar os status no GLPI.</p>
|
||||||
|
<p><em>Watchdog Hub × GLPI</em></p>
|
||||||
|
`
|
||||||
|
|
||||||
|
return body
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDate(value) {
|
function formatDate(value) {
|
||||||
if (!value) return 'não informado';
|
if (!value) return 'não informado'
|
||||||
|
|
||||||
const d = new Date(value);
|
const d = new Date(value)
|
||||||
if (Number.isNaN(d.getTime())) return String(value); // fallback seguro
|
if (Number.isNaN(d.getTime())) return String(value)
|
||||||
|
|
||||||
return d.toLocaleString('pt-BR', { timeZone: 'America/Sao_Paulo' });
|
return d.toLocaleString('pt-BR', { timeZone: 'America/Sao_Paulo' })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function formatDate(date) {
|
|
||||||
try {
|
|
||||||
return new Date(date).toLocaleString('pt-BR');
|
|
||||||
} catch {
|
|
||||||
return date;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function getRecipients() {
|
function getRecipients() {
|
||||||
return process.env.WATCHDOG_RECIPIENT_EMAILS?.split(',') || [];
|
return process.env.WATCHDOG_RECIPIENT_EMAILS?.split(',') || []
|
||||||
}
|
}
|
||||||
|
|
||||||
function getCc() {
|
function getCc() {
|
||||||
return process.env.WATCHDOG_CC_EMAILS?.split(',') || [];
|
return process.env.WATCHDOG_CC_EMAILS?.split(',') || []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
prepareNotificationPayload
|
prepareNotificationPayload
|
||||||
};
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ const wdRepository = require('../../../infra/db/repositories/hubglpi/watchdog.re
|
|||||||
const hubsoftTicketsRepo = require('../../../infra/db/repositories/hubsoft/tickets.repository.js');
|
const hubsoftTicketsRepo = require('../../../infra/db/repositories/hubsoft/tickets.repository.js');
|
||||||
const hubglpiSyncRepo = require('../../../infra/db/repositories/hubglpi/sync.repository.js');
|
const hubglpiSyncRepo = require('../../../infra/db/repositories/hubglpi/sync.repository.js');
|
||||||
const senderMail = require('../../../infra/mail/sender.js')
|
const senderMail = require('../../../infra/mail/sender.js')
|
||||||
|
const glpiRepository = require('../../../infra/db/repositories/glpi/tickets.repository.js')
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Busca tickets encerrados no Hubsoft a partir de uma data
|
* Busca tickets encerrados no Hubsoft a partir de uma data
|
||||||
@ -71,13 +73,34 @@ async function getPendingTicketsForNotification(type = 'func') {
|
|||||||
return wdRepository.getPendingTicketsForNotification(type);
|
return wdRepository.getPendingTicketsForNotification(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getTicketsPendingResponse(sinceMinutes = 30, type = 'func') {
|
||||||
|
return wdRepository.getTicketsPendingResponse(sinceMinutes, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkTicketIsUpdated(glpiTicketId, sinceMinutes = 30) {
|
||||||
|
const ticketUpdateDate = await glpiRepository.checkTicketUpdateDate(glpiTicketId);
|
||||||
|
|
||||||
|
if (ticketUpdateDate.date_mod < new Date(Date.now() - sinceMinutes * 60000)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function markNotificationAsCompleted(hubsoftTicketId, type = 'func') {
|
||||||
|
return wdRepository.markNotificationsAsCompleted([hubsoftTicketId], type);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getClosedTicketsSince,
|
getClosedTicketsSince,
|
||||||
checkTicketInHubGlpi,
|
checkTicketInHubGlpi,
|
||||||
|
checkTicketIsUpdated,
|
||||||
notificationAlreadySent,
|
notificationAlreadySent,
|
||||||
sendClosureNotifications,
|
sendClosureNotifications,
|
||||||
markNotificationsAsSent,
|
markNotificationsAsSent,
|
||||||
markNotificationsAsFailed,
|
markNotificationsAsFailed,
|
||||||
markNotificationsAsPending,
|
markNotificationsAsPending,
|
||||||
getPendingTicketsForNotification
|
getPendingTicketsForNotification,
|
||||||
|
getTicketsPendingResponse,
|
||||||
|
markNotificationAsCompleted
|
||||||
};
|
};
|
||||||
|
|||||||
62
src/modules/watchdog/services/notifyAdmins.service.js
Normal file
62
src/modules/watchdog/services/notifyAdmins.service.js
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// src/modules/watchdog/services/notifyAdmins.service.js
|
||||||
|
|
||||||
|
const { logError, logInfo } = require('../../../shared/utils/logger')
|
||||||
|
const repository = require('../repository/watchdog.repository.js')
|
||||||
|
const model = require('../model/email.model.js')
|
||||||
|
|
||||||
|
async function notifyAdmins({ sinceMinutes = 30 } = {}) {
|
||||||
|
logInfo(`[WATCHDOG] [ADM] Verificando chamados sem interacao ha mais de ${sinceMinutes} minutos`)
|
||||||
|
|
||||||
|
const ticketsPendingVerify = await repository.getTicketsPendingResponse(sinceMinutes, 'func')
|
||||||
|
logInfo(`[WATCHDOG] [ADM] ${ticketsPendingVerify.length} chamados com notificacao enviada e pendentes de resposta.`)
|
||||||
|
|
||||||
|
const notificationType = 'adm'
|
||||||
|
|
||||||
|
for (const ticket of ticketsPendingVerify) {
|
||||||
|
const hubsoftTicketId = Number(ticket.hubsoft_ticket_id ?? ticket.id_atendimento)
|
||||||
|
const glpiTicketId = Number(ticket.glpi_ticket_id)
|
||||||
|
|
||||||
|
if (!Number.isFinite(hubsoftTicketId)) {
|
||||||
|
logInfo('[WATCHDOG] [ADM] Ticket pendente sem id valido. Ignorando.')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUpdated = await repository.checkTicketIsUpdated(glpiTicketId, sinceMinutes)
|
||||||
|
if (isUpdated) {
|
||||||
|
logInfo(`[WATCHDOG] [ADM] Chamado ${hubsoftTicketId} atualizado no GLPI. Encerrando fluxo.`)
|
||||||
|
await repository.markNotificationAsCompleted(hubsoftTicketId, 'func')
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (await repository.notificationAlreadySent(hubsoftTicketId, notificationType)) {
|
||||||
|
logInfo(`[WATCHDOG] [ADM] Notificacao ADM ja enviada para o chamado ${hubsoftTicketId}.`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
await repository.markNotificationsAsPending([hubsoftTicketId], notificationType)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ticketsToNotify = await repository.getPendingTicketsForNotification(notificationType)
|
||||||
|
logInfo(`[WATCHDOG] [ADM] ${ticketsToNotify.length} chamados pendentes para notificacao.`)
|
||||||
|
|
||||||
|
if (!ticketsToNotify.length) {
|
||||||
|
return { sentCount: 0, failedCount: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = await model.prepareNotificationPayload(ticketsToNotify, notificationType)
|
||||||
|
const hubsoftTicketIds = ticketsToNotify
|
||||||
|
.map(t => Number(t.hubsoft_ticket_id))
|
||||||
|
.filter(Number.isFinite)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await repository.sendClosureNotifications(payload)
|
||||||
|
await repository.markNotificationsAsSent(hubsoftTicketIds, notificationType)
|
||||||
|
return { sentCount: ticketsToNotify.length, failedCount: 0 }
|
||||||
|
} catch (err) {
|
||||||
|
logError('[WATCHDOG] Erro ao enviar notificacoes para ADM', err)
|
||||||
|
await repository.markNotificationsAsFailed(hubsoftTicketIds, notificationType)
|
||||||
|
return { sentCount: 0, failedCount: hubsoftTicketIds.length }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { notifyAdmins }
|
||||||
58
src/modules/watchdog/services/notifyCollaborators.service.js
Normal file
58
src/modules/watchdog/services/notifyCollaborators.service.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// src/modules/watchdog/services/notifyCollaborators.service.js
|
||||||
|
|
||||||
|
const { logError, logInfo } = require('../../../shared/utils/logger')
|
||||||
|
const repository = require('../repository/watchdog.repository.js')
|
||||||
|
const model = require('../model/email.model.js')
|
||||||
|
|
||||||
|
async function notifyCollaborators({ thresholdMinutes = 31 } = {}) {
|
||||||
|
logInfo(`[WATCHDOG] [FUNC] Coletando chamados fechados ha mais de ${thresholdMinutes} minutos`)
|
||||||
|
|
||||||
|
const thresholdDate = new Date(Date.now() - thresholdMinutes * 60 * 1000)
|
||||||
|
const closedTickets = await repository.getClosedTicketsSince(thresholdDate)
|
||||||
|
logInfo(`[WATCHDOG] [FUNC] Encontrados ${closedTickets.length} chamados fechados`)
|
||||||
|
|
||||||
|
const notificationType = 'func'
|
||||||
|
|
||||||
|
for (const ticket of closedTickets) {
|
||||||
|
const hubGlpiTicket = await repository.checkTicketInHubGlpi(ticket.id_atendimento)
|
||||||
|
|
||||||
|
if (!hubGlpiTicket.exists) {
|
||||||
|
logInfo(`[WATCHDOG] [FUNC] Chamado ${ticket.id_atendimento} nao encontrado no HubGlpi. Ignorando.`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (hubGlpiTicket.status === 'closed') {
|
||||||
|
logInfo(`[WATCHDOG] [FUNC] Chamado ${ticket.id_atendimento} ja esta fechado no HubGlpi. Ignorando.`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (await repository.notificationAlreadySent(ticket.id_atendimento, notificationType)) {
|
||||||
|
logInfo(`[WATCHDOG] [FUNC] Notificacao ja enviada para o chamado ${ticket.id_atendimento}.`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
await repository.markNotificationsAsPending([ticket.id_atendimento], notificationType)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ticketsToNotify = await repository.getPendingTicketsForNotification(notificationType)
|
||||||
|
logInfo(`[WATCHDOG] [FUNC] ${ticketsToNotify.length} chamados pendentes para notificacao.`)
|
||||||
|
|
||||||
|
if (!ticketsToNotify.length) {
|
||||||
|
return { sentCount: 0, failedCount: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = await model.prepareNotificationPayload(ticketsToNotify, notificationType)
|
||||||
|
const hubsoftTicketIds = ticketsToNotify
|
||||||
|
.map(t => Number(t.hubsoft_ticket_id))
|
||||||
|
.filter(Number.isFinite)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await repository.sendClosureNotifications(payload)
|
||||||
|
await repository.markNotificationsAsSent(hubsoftTicketIds, notificationType)
|
||||||
|
return { sentCount: ticketsToNotify.length, failedCount: 0 }
|
||||||
|
} catch (err) {
|
||||||
|
logError('[WATCHDOG] Erro ao enviar notificacoes para colaboradores', err)
|
||||||
|
await repository.markNotificationsAsFailed(hubsoftTicketIds, notificationType)
|
||||||
|
return { sentCount: 0, failedCount: hubsoftTicketIds.length }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { notifyCollaborators }
|
||||||
Loading…
Reference in New Issue
Block a user