feat: enhance vacation approval process by adding notification system for employees, including email alerts and in-app notifications; improve error handling and user feedback during vacation management
This commit is contained in:
@@ -145,6 +145,180 @@ export const listarAlertas = query({
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Verificar configuração do sistema de alertas (diagnóstico)
|
||||
*/
|
||||
export const verificarConfiguracaoAlertas = query({
|
||||
args: {
|
||||
_refresh: v.optional(v.number()) // Parâmetro ignorado, usado apenas para forçar refresh no frontend
|
||||
},
|
||||
returns: v.object({
|
||||
templateExiste: v.boolean(),
|
||||
templateInfo: v.union(
|
||||
v.object({
|
||||
_id: v.id('templatesMensagens'),
|
||||
codigo: v.string(),
|
||||
nome: v.string(),
|
||||
htmlCorpo: v.optional(v.string())
|
||||
}),
|
||||
v.null()
|
||||
),
|
||||
roleTiMasterExiste: v.boolean(),
|
||||
usuariosTiMaster: v.array(
|
||||
v.object({
|
||||
_id: v.id('usuarios'),
|
||||
nome: v.string(),
|
||||
email: v.optional(v.string()),
|
||||
temEmail: v.boolean()
|
||||
})
|
||||
),
|
||||
configSmtpAtiva: v.boolean(),
|
||||
configSmtpInfo: v.union(
|
||||
v.object({
|
||||
_id: v.id('configuracaoEmail'),
|
||||
servidor: v.string(),
|
||||
porta: v.number(),
|
||||
emailRemetente: v.string(),
|
||||
ativo: v.boolean()
|
||||
}),
|
||||
v.null()
|
||||
),
|
||||
emailsPendentes: v.number(),
|
||||
emailsFalha: v.number(),
|
||||
alertasAtivos: v.number(),
|
||||
alertasComEmail: v.number()
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
try {
|
||||
// 1. Verificar template
|
||||
let template = null;
|
||||
try {
|
||||
template = await ctx.db
|
||||
.query('templatesMensagens')
|
||||
.withIndex('by_codigo', (q) => q.eq('codigo', 'monitoramento_alerta_sistema'))
|
||||
.first();
|
||||
} catch (error) {
|
||||
console.warn('Erro ao buscar template:', error);
|
||||
}
|
||||
|
||||
// 2. Verificar role TI_MASTER
|
||||
let roleTiMaster = null;
|
||||
try {
|
||||
roleTiMaster = await ctx.db
|
||||
.query('roles')
|
||||
.withIndex('by_nome', (q) => q.eq('nome', 'ti_master'))
|
||||
.first();
|
||||
} catch (error) {
|
||||
console.warn('Erro ao buscar role TI_MASTER:', error);
|
||||
}
|
||||
|
||||
// 3. Verificar usuários TI_MASTER
|
||||
let usuariosTiMaster: Array<{
|
||||
_id: Id<'usuarios'>;
|
||||
nome: string;
|
||||
email?: string;
|
||||
temEmail: boolean;
|
||||
}> = [];
|
||||
|
||||
if (roleTiMaster) {
|
||||
try {
|
||||
const usuarios = await ctx.db
|
||||
.query('usuarios')
|
||||
.withIndex('by_role', (q) => q.eq('roleId', roleTiMaster!._id))
|
||||
.collect();
|
||||
|
||||
usuariosTiMaster = usuarios.map((u) => ({
|
||||
_id: u._id,
|
||||
nome: u.nome,
|
||||
email: u.email,
|
||||
temEmail: !!u.email
|
||||
}));
|
||||
} catch (error) {
|
||||
console.warn('Erro ao buscar usuários TI_MASTER:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Verificar configuração SMTP
|
||||
let configSmtp = null;
|
||||
try {
|
||||
configSmtp = await ctx.db
|
||||
.query('configuracaoEmail')
|
||||
.withIndex('by_ativo', (q) => q.eq('ativo', true))
|
||||
.first();
|
||||
} catch (error) {
|
||||
console.warn('Erro ao buscar configuração SMTP:', error);
|
||||
}
|
||||
|
||||
// 5. Verificar fila de emails
|
||||
let emailsPendentes = 0;
|
||||
let emailsFalha = 0;
|
||||
try {
|
||||
const todosEmails = await ctx.db.query('notificacoesEmail').collect();
|
||||
emailsPendentes = todosEmails.filter((e) => e.status === 'pendente').length;
|
||||
emailsFalha = todosEmails.filter((e) => e.status === 'falha').length;
|
||||
} catch (error) {
|
||||
console.warn('Erro ao buscar emails:', error);
|
||||
}
|
||||
|
||||
// 6. Verificar alertas
|
||||
let alertasAtivos = 0;
|
||||
let alertasComEmail = 0;
|
||||
try {
|
||||
const todosAlertas = await ctx.db.query('alertConfigurations').collect();
|
||||
alertasAtivos = todosAlertas.filter((a) => a.enabled).length;
|
||||
alertasComEmail = todosAlertas.filter(
|
||||
(a) => a.enabled && a.notifyByEmail
|
||||
).length;
|
||||
} catch (error) {
|
||||
console.warn('Erro ao buscar alertas:', error);
|
||||
}
|
||||
|
||||
return {
|
||||
templateExiste: !!template,
|
||||
templateInfo: template
|
||||
? {
|
||||
_id: template._id,
|
||||
codigo: template.codigo,
|
||||
nome: template.nome,
|
||||
htmlCorpo: template.htmlCorpo
|
||||
}
|
||||
: null,
|
||||
roleTiMasterExiste: !!roleTiMaster,
|
||||
usuariosTiMaster,
|
||||
configSmtpAtiva: !!configSmtp,
|
||||
configSmtpInfo: configSmtp
|
||||
? {
|
||||
_id: configSmtp._id,
|
||||
servidor: configSmtp.servidor,
|
||||
porta: configSmtp.porta,
|
||||
emailRemetente: configSmtp.emailRemetente,
|
||||
ativo: configSmtp.ativo
|
||||
}
|
||||
: null,
|
||||
emailsPendentes,
|
||||
emailsFalha,
|
||||
alertasAtivos,
|
||||
alertasComEmail
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Erro ao verificar configuração de alertas:', error);
|
||||
// Retornar valores padrão em caso de erro
|
||||
return {
|
||||
templateExiste: false,
|
||||
templateInfo: null,
|
||||
roleTiMasterExiste: false,
|
||||
usuariosTiMaster: [],
|
||||
configSmtpAtiva: false,
|
||||
configSmtpInfo: null,
|
||||
emailsPendentes: 0,
|
||||
emailsFalha: 0,
|
||||
alertasAtivos: 0,
|
||||
alertasComEmail: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter métricas com filtros
|
||||
*/
|
||||
@@ -340,62 +514,89 @@ export const verificarAlertasInternal = internalMutation({
|
||||
|
||||
// Criar notificação no chat se configurado
|
||||
if (alerta.notifyByChat) {
|
||||
// Buscar roles administrativas (nível <= 1) e filtrar usuários por roleId
|
||||
const rolesAdminOuTi = await ctx.db
|
||||
// Buscar apenas a role TI_MASTER
|
||||
const roleTiMaster = await ctx.db
|
||||
.query('roles')
|
||||
.filter((q) => q.lte(q.field('nivel'), 1))
|
||||
.collect();
|
||||
.withIndex('by_nome', (q) => q.eq('nome', 'ti_master'))
|
||||
.first();
|
||||
|
||||
const rolesPermitidas = new Set(rolesAdminOuTi.map((r) => r._id));
|
||||
if (!roleTiMaster) {
|
||||
console.warn('Role TI_MASTER não encontrada. Notificações de chat não serão enviadas.');
|
||||
} else {
|
||||
// Buscar usuários com role TI_MASTER
|
||||
const usuarios = await ctx.db
|
||||
.query('usuarios')
|
||||
.withIndex('by_role', (q) => q.eq('roleId', roleTiMaster._id))
|
||||
.collect();
|
||||
|
||||
const usuarios = await ctx.db.query('usuarios').collect();
|
||||
const usuariosTI = usuarios.filter((u) => rolesPermitidas.has(u.roleId));
|
||||
|
||||
for (const usuario of usuariosTI) {
|
||||
await ctx.db.insert('notificacoes', {
|
||||
usuarioId: usuario._id,
|
||||
tipo: 'nova_mensagem',
|
||||
titulo: `⚠️ Alerta de Sistema: ${alerta.metricName}`,
|
||||
descricao: `Métrica ${alerta.metricName} está em ${metricValue.toFixed(2)}% (limite: ${alerta.threshold}%)`,
|
||||
lida: false,
|
||||
criadaEm: Date.now()
|
||||
});
|
||||
for (const usuario of usuarios) {
|
||||
await ctx.db.insert('notificacoes', {
|
||||
usuarioId: usuario._id,
|
||||
tipo: 'nova_mensagem',
|
||||
titulo: `⚠️ Alerta de Sistema: ${alerta.metricName}`,
|
||||
descricao: `Métrica ${alerta.metricName} está em ${metricValue.toFixed(2)}% (limite: ${alerta.threshold}%)`,
|
||||
lida: false,
|
||||
criadaEm: Date.now()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enviar email se configurado (usar template HTML padronizado)
|
||||
if (alerta.notifyByEmail) {
|
||||
// Buscar usuários administradores/TI para receber o alerta por email
|
||||
const rolesAdminOuTi = await ctx.db
|
||||
// Buscar apenas a role TI_MASTER
|
||||
const roleTiMaster = await ctx.db
|
||||
.query('roles')
|
||||
.filter((q) => q.lte(q.field('nivel'), 1))
|
||||
.collect();
|
||||
.withIndex('by_nome', (q) => q.eq('nome', 'ti_master'))
|
||||
.first();
|
||||
|
||||
const rolesPermitidas = new Set(rolesAdminOuTi.map((r) => r._id));
|
||||
const usuarios = await ctx.db.query('usuarios').collect();
|
||||
const usuariosTI = usuarios.filter((u) => rolesPermitidas.has(u.roleId) && !!u.email);
|
||||
if (!roleTiMaster) {
|
||||
console.warn('⚠️ [Monitoramento] Role TI_MASTER não encontrada. Emails de alerta não serão enviados.');
|
||||
} else {
|
||||
// Buscar usuários com role TI_MASTER que possuem email
|
||||
const usuarios = await ctx.db
|
||||
.query('usuarios')
|
||||
.withIndex('by_role', (q) => q.eq('roleId', roleTiMaster._id))
|
||||
.collect();
|
||||
|
||||
for (const usuario of usuariosTI) {
|
||||
const email = usuario.email;
|
||||
if (!email) continue;
|
||||
if (usuarios.length === 0) {
|
||||
console.warn('⚠️ [Monitoramento] Nenhum usuário TI_MASTER encontrado para receber alertas por email.');
|
||||
} else {
|
||||
// Usar o createdBy do alerta como enviadoPor (quem criou o alerta)
|
||||
const enviadoPorId = alerta.createdBy;
|
||||
|
||||
// Montar variáveis para template de alerta de sistema
|
||||
const variaveisEmail = {
|
||||
destinatarioNome: usuario.nome,
|
||||
metricName: alerta.metricName,
|
||||
metricValue: metricValue.toFixed(2),
|
||||
threshold: alerta.threshold.toString()
|
||||
};
|
||||
for (const usuario of usuarios) {
|
||||
const email = usuario.email;
|
||||
if (!email) {
|
||||
console.warn(`⚠️ [Monitoramento] Usuário ${usuario._id} (TI_MASTER) não possui email cadastrado.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Importante: usar api.email.enviarEmailComTemplate (action pública),
|
||||
// e não internal.email, para corresponder à tipagem gerada em ./_generated/api.
|
||||
await ctx.scheduler.runAfter(0, api.email.enviarEmailComTemplate, {
|
||||
destinatario: email,
|
||||
destinatarioId: usuario._id,
|
||||
templateCodigo: 'monitoramento_alerta_sistema',
|
||||
variaveis: variaveisEmail,
|
||||
enviadoPor: usuario._id
|
||||
});
|
||||
// Montar variáveis para template de alerta de sistema
|
||||
const variaveisEmail = {
|
||||
destinatarioNome: usuario.nome,
|
||||
metricName: alerta.metricName,
|
||||
metricValue: metricValue.toFixed(2),
|
||||
threshold: alerta.threshold.toString()
|
||||
};
|
||||
|
||||
try {
|
||||
// Importante: usar api.email.enviarEmailComTemplate (action pública),
|
||||
// e não internal.email, para corresponder à tipagem gerada em ./_generated/api.
|
||||
await ctx.scheduler.runAfter(0, api.email.enviarEmailComTemplate, {
|
||||
destinatario: email,
|
||||
destinatarioId: usuario._id,
|
||||
templateCodigo: 'monitoramento_alerta_sistema',
|
||||
variaveis: variaveisEmail,
|
||||
enviadoPor: enviadoPorId // ✅ CORRIGIDO: usar createdBy do alerta
|
||||
});
|
||||
console.log(`✅ [Monitoramento] Email de alerta agendado para ${email} (${usuario.nome})`);
|
||||
} catch (error) {
|
||||
console.error(`❌ [Monitoramento] Erro ao agendar email de alerta para ${email}:`, error);
|
||||
// Continuar tentando enviar para outros usuários mesmo se um falhar
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user