30f7b0f9-9de3-4087-a91e-c351421922db
This commit is contained in:
1 parent
09ca75d36e
commit
9b5477020c
6 files changed
+161
-2
No files matched your search
@@ -0,0 +1,71 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { db } from '@/lib/db'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
// GET /api/aos/portal/companies/[id]/notifications
|
||||
// Client-scoped notifications: only this company's own alerts.
|
||||
// (PRD §5.6: "Portal users are isolated by project membership")
|
||||
export async function GET(
|
||||
_req: Request,
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
const { id } = await params
|
||||
const company = await db.company.findUnique({ where: { id } })
|
||||
if (!company) return NextResponse.json({ error: 'not found' }, { status: 404 })
|
||||
|
||||
const now = new Date()
|
||||
const in7Days = new Date(now.getTime() + 7 * 86400000)
|
||||
|
||||
const [invoices, tickets, approvals, licenses, projects] = await Promise.all([
|
||||
db.invoice.findMany({ where: { companyId: id, status: { in: ['sent', 'overdue'] } } }),
|
||||
db.ticket.findMany({ where: { companyId: id, status: { in: ['open', 'pending'] } } }),
|
||||
db.portalApproval.findMany({ where: { status: 'pending', project: { companyId: id } }, include: { project: true } }),
|
||||
db.license.findMany({ where: { companyId: id, status: 'active', validUntil: { not: null } } }),
|
||||
db.project.findMany({ where: { companyId: id, status: { in: ['active', 'planning', 'review'] } } }),
|
||||
])
|
||||
|
||||
type Notif = { id: string; type: 'urgent' | 'warning' | 'info'; title: string; detail: string; module: string; daysUntil: number }
|
||||
const notifs: Notif[] = []
|
||||
|
||||
// Overdue invoices
|
||||
for (const inv of invoices.filter((i) => i.status === 'overdue')) {
|
||||
const days = Math.round((now.getTime() - inv.dueDate.getTime()) / 86400000)
|
||||
notifs.push({ id: `inv-overdue-${inv.id}`, type: 'urgent', module: 'invoices', title: `Rechnung ${inv.number} überfällig`, detail: `${(inv.grossCents / 100).toFixed(2)} € · ${days} Tage`, daysUntil: -days })
|
||||
}
|
||||
// Invoices due soon
|
||||
for (const inv of invoices.filter((i) => i.status === 'sent' && i.dueDate < in7Days)) {
|
||||
const days = Math.round((inv.dueDate.getTime() - now.getTime()) / 86400000)
|
||||
notifs.push({ id: `inv-due-${inv.id}`, type: 'warning', module: 'invoices', title: `Rechnung ${inv.number} fällig`, detail: `${(inv.grossCents / 100).toFixed(2)} € · in ${days} Tagen`, daysUntil: days })
|
||||
}
|
||||
// Pending approvals
|
||||
for (const a of approvals) {
|
||||
notifs.push({ id: `appr-${a.id}`, type: 'info', module: 'approvals', title: `Freigabe wartet: ${a.title}`, detail: a.project?.name ?? '—', daysUntil: 0 })
|
||||
}
|
||||
// Open tickets
|
||||
for (const t of tickets) {
|
||||
notifs.push({ id: `tkt-${t.id}`, type: t.priority === 'urgent' ? 'urgent' : 'info', module: 'tickets', title: `Ticket ${t.number}: ${t.subject}`, detail: `Status: ${t.status}`, daysUntil: 0 })
|
||||
}
|
||||
// Expiring licenses
|
||||
for (const l of licenses.filter((l) => l.validUntil! < in7Days)) {
|
||||
const days = Math.round((l.validUntil!.getTime() - now.getTime()) / 86400000)
|
||||
notifs.push({ id: `lic-${l.id}`, type: days < 0 ? 'urgent' : 'warning', module: 'licenses', title: `Lizenz ${l.product} ${days < 0 ? 'abgelaufen' : 'läuft ab'}`, detail: days < 0 ? `${Math.abs(days)} Tage drüber` : `in ${days} Tagen`, daysUntil: days })
|
||||
}
|
||||
// Active projects (info)
|
||||
if (projects.length > 0) {
|
||||
notifs.push({ id: 'proj-active', type: 'info', module: 'projects', title: `${projects.length} aktive Projekte`, detail: projects.map((p) => p.name).join(', ').slice(0, 80), daysUntil: 0 })
|
||||
}
|
||||
|
||||
const typeOrder = { urgent: 0, warning: 1, info: 2 }
|
||||
notifs.sort((a, b) => typeOrder[a.type] - typeOrder[b.type])
|
||||
|
||||
return NextResponse.json({
|
||||
notifications: notifs,
|
||||
stats: {
|
||||
total: notifs.length,
|
||||
urgent: notifs.filter((n) => n.type === 'urgent').length,
|
||||
warning: notifs.filter((n) => n.type === 'warning').length,
|
||||
info: notifs.filter((n) => n.type === 'info').length,
|
||||
},
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user