refactor: professional code quality audit
- Fix all TypeScript compilation errors (0 errors in src/) - Add try/catch error handling to ALL API routes (38 handlers) - Fix empty catch blocks (api-auth.ts now logs warnings) - Add aria-labels to all icon-only buttons (WCAG 2.1 AA) - Fix Prisma type mismatches (OR filter, Person.companyId, Estimate.projectId, Ticket.createdAt) - Fix webhook PATCH route (moved to [id]/route.ts) - Fix mobile-bottom-nav type safety - Fix KB route where-clause type - Fix test-mail banner null vs undefined - Fix task comment fetch: add cancellation + error toast - ESLint: 0 errors, 0 warnings - 175 files, 24.741 lines of code
This commit is contained in:
1 parent
969202764a
commit
fa8dd46571
175 files changed
+24741
-2
No files matched your search
Executable
+62
@@ -0,0 +1,62 @@
|
||||
import { NextResponse } from 'next/server'
|
||||
import { db } from '@/lib/db'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
// GET /api/aos/expenses — expenses list with company/project, stats, reports
|
||||
export async function GET() {
|
||||
try {
|
||||
const expenses = await db.expense.findMany({ orderBy: { date: 'desc' } })
|
||||
|
||||
const companyIds = [...new Set(expenses.map((e) => e.companyId).filter(Boolean) as string[])]
|
||||
const projectIds = [...new Set(expenses.map((e) => e.projectId).filter(Boolean) as string[])]
|
||||
const [companies, projects] = await Promise.all([
|
||||
db.company.findMany({ where: { id: { in: companyIds } }, select: { id: true, name: true } }),
|
||||
db.project.findMany({ where: { id: { in: projectIds } }, select: { id: true, name: true } }),
|
||||
])
|
||||
const companyMap = new Map(companies.map((c) => [c.id, c.name]))
|
||||
const projectMap = new Map(projects.map((p) => [p.id, p.name]))
|
||||
|
||||
const enriched = expenses.map((e) => ({
|
||||
...e,
|
||||
companyName: e.companyId ? companyMap.get(e.companyId) ?? null : null,
|
||||
projectName: e.projectId ? projectMap.get(e.projectId) ?? null : null,
|
||||
}))
|
||||
|
||||
const stats = {
|
||||
total: expenses.length,
|
||||
billable: expenses.filter((e) => e.billable).length,
|
||||
recurring: expenses.filter((e) => e.recurring).length,
|
||||
billableCents: expenses.filter((e) => e.billable && e.status !== 'invoiced').reduce((s, e) => s + e.grossCents, 0),
|
||||
totalGrossCents: expenses.reduce((s, e) => s + e.grossCents, 0),
|
||||
invoiced: expenses.filter((e) => e.status === 'invoiced').length,
|
||||
pendingApproval: expenses.filter((e) => e.status === 'recorded').length,
|
||||
datevExported: expenses.filter((e) => e.datevExported).length,
|
||||
}
|
||||
|
||||
// expense report: by category
|
||||
const byCategory: Record<string, { count: number; grossCents: number }> = {}
|
||||
for (const e of expenses) {
|
||||
if (!byCategory[e.categoryId]) byCategory[e.categoryId] = { count: 0, grossCents: 0 }
|
||||
byCategory[e.categoryId].count++
|
||||
byCategory[e.categoryId].grossCents += e.grossCents
|
||||
}
|
||||
|
||||
// monthly (last 6 months)
|
||||
const now = new Date()
|
||||
const monthly: { label: string; cents: number }[] = []
|
||||
for (let i = 5; i >= 0; i--) {
|
||||
const m = new Date(now.getFullYear(), now.getMonth() - i, 1)
|
||||
const label = m.toLocaleDateString('de-DE', { month: 'short' })
|
||||
const cents = expenses
|
||||
.filter((e) => e.date.getMonth() === m.getMonth() && e.date.getFullYear() === m.getFullYear())
|
||||
.reduce((s, e) => s + e.grossCents, 0)
|
||||
monthly.push({ label, cents })
|
||||
}
|
||||
|
||||
return NextResponse.json({ expenses: enriched, stats, report: { byCategory, monthly } })
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : 'internal server error'
|
||||
return NextResponse.json({ error: msg }, { status: 500 })
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user