129 lines
4.5 KiB
TypeScript
129 lines
4.5 KiB
TypeScript
import { NextResponse } from "next/server"
|
|
import { ConvexHttpClient } from "convex/browser"
|
|
|
|
import { api } from "@/convex/_generated/api"
|
|
import type { Id } from "@/convex/_generated/dataModel"
|
|
import { assertAdminSession } from "@/lib/auth-server"
|
|
import { env } from "@/lib/env"
|
|
import { prisma } from "@/lib/prisma"
|
|
import { sendSmtpMail } from "@/server/email-smtp"
|
|
|
|
export const runtime = "nodejs"
|
|
|
|
function fmtHours(ms: number) {
|
|
return (ms / 3600000).toFixed(2)
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
const session = await assertAdminSession()
|
|
if (!session) return NextResponse.json({ error: "Não autorizado" }, { status: 401 })
|
|
|
|
const convexUrl = process.env.NEXT_PUBLIC_CONVEX_URL
|
|
if (!convexUrl) return NextResponse.json({ error: "Convex não configurado" }, { status: 500 })
|
|
if (!env.SMTP) return NextResponse.json({ error: "SMTP não configurado" }, { status: 500 })
|
|
|
|
const { searchParams } = new URL(request.url)
|
|
const range = searchParams.get("range") ?? "30d"
|
|
const threshold = Number(searchParams.get("threshold") ?? 90)
|
|
|
|
const client = new ConvexHttpClient(convexUrl)
|
|
const tenantId = session.user.tenantId ?? "tenant-atlas"
|
|
|
|
// Ensure user exists in Convex to obtain a typed viewerId
|
|
let viewerId: Id<"users"> | null = null
|
|
try {
|
|
const ensured = await client.mutation(api.users.ensureUser, {
|
|
tenantId,
|
|
name: session.user.name ?? session.user.email,
|
|
email: session.user.email,
|
|
avatarUrl: session.user.avatarUrl ?? undefined,
|
|
role: session.user.role.toUpperCase(),
|
|
})
|
|
viewerId = (ensured?._id ?? null) as Id<"users"> | null
|
|
} catch (error) {
|
|
console.error("Failed to synchronize user with Convex for alerts", error)
|
|
return NextResponse.json({ error: "Falha ao sincronizar usuário com Convex" }, { status: 500 })
|
|
}
|
|
if (!viewerId) return NextResponse.json({ error: "Usuário não encontrado no Convex" }, { status: 403 })
|
|
|
|
const report = await client.query(api.reports.hoursByClient, {
|
|
tenantId,
|
|
viewerId,
|
|
range,
|
|
})
|
|
|
|
type HoursByClientItem = {
|
|
companyId: Id<"companies">
|
|
name: string
|
|
internalMs: number
|
|
externalMs: number
|
|
totalMs: number
|
|
contractedHoursPerMonth: number | null
|
|
}
|
|
const items = (report.items ?? []) as HoursByClientItem[]
|
|
const alerts = items.filter((i) => i.contractedHoursPerMonth != null && (i.totalMs / 3600000) / (i.contractedHoursPerMonth || 1) * 100 >= threshold)
|
|
|
|
type ManagerUser = { email: string; name: string | null }
|
|
|
|
for (const item of alerts) {
|
|
// Find managers of the company in Prisma
|
|
const managers: ManagerUser[] = await prisma.user.findMany({
|
|
where: {
|
|
tenantId,
|
|
companyId: item.companyId,
|
|
role: "MANAGER",
|
|
},
|
|
select: { email: true, name: true },
|
|
})
|
|
if (managers.length === 0) continue
|
|
|
|
const subject = `Alerta: uso de horas em ${item.name} acima de ${threshold}%`
|
|
const body = `
|
|
<p>Olá,</p>
|
|
<p>O uso de horas contratadas para <strong>${item.name}</strong> atingiu <strong>${(((item.totalMs/3600000)/(item.contractedHoursPerMonth || 1))*100).toFixed(1)}%</strong>.</p>
|
|
<ul>
|
|
<li>Horas internas: <strong>${fmtHours(item.internalMs)}</strong></li>
|
|
<li>Horas externas: <strong>${fmtHours(item.externalMs)}</strong></li>
|
|
<li>Total: <strong>${fmtHours(item.totalMs)}</strong></li>
|
|
<li>Contratadas/mês: <strong>${item.contractedHoursPerMonth}</strong></li>
|
|
</ul>
|
|
<p>Reveja a alocação da equipe e, se necessário, ajuste o atendimento.</p>
|
|
`
|
|
let delivered = 0
|
|
for (const m of managers) {
|
|
try {
|
|
await sendSmtpMail(
|
|
{
|
|
host: env.SMTP!.host,
|
|
port: env.SMTP!.port,
|
|
username: env.SMTP!.username,
|
|
password: env.SMTP!.password,
|
|
from: env.SMTP!.from!,
|
|
},
|
|
m.email,
|
|
subject,
|
|
body
|
|
)
|
|
delivered += 1
|
|
} catch (error) {
|
|
console.error("Failed to send alert to", m.email, error)
|
|
}
|
|
}
|
|
try {
|
|
await client.mutation(api.alerts.log, {
|
|
tenantId,
|
|
companyId: item.companyId,
|
|
companyName: item.name,
|
|
usagePct: (((item.totalMs/3600000)/(item.contractedHoursPerMonth || 1))*100),
|
|
threshold,
|
|
range,
|
|
recipients: managers.map((m) => m.email),
|
|
deliveredCount: delivered,
|
|
})
|
|
} catch (error) {
|
|
console.error("Failed to log alert in Convex", error)
|
|
}
|
|
}
|
|
|
|
return NextResponse.json({ sent: alerts.length })
|
|
}
|