import { Router } from 'express' import { prisma } from '../lib/prisma' import { requireCompanyAuth } from '../middleware/requireCompanyAuth' import { requireTenant } from '../middleware/requireTenant' import { requireSubscription } from '../middleware/requireSubscription' import { generateFinancialReport, toCsv } from '../services/financialReportService' const router = Router() router.use(requireCompanyAuth, requireTenant, requireSubscription) function getRangeFromPeriod(period: string) { const now = new Date() switch (period) { case 'WEEKLY': { const start = new Date(now) start.setDate(now.getDate() - 7) return { from: start, to: now } } case 'QUARTERLY': { const start = new Date(now.getFullYear(), now.getMonth() - 2, 1) return { from: start, to: now } } case 'ANNUAL': { const start = new Date(now.getFullYear(), 0, 1) return { from: start, to: now } } case 'MONTHLY': default: return { from: new Date(now.getFullYear(), now.getMonth(), 1), to: now } } } router.get('/summary', async (req, res, next) => { try { const { period = '30d' } = req.query as Record const days = parseInt(period.replace('d', '')) || 30 const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000) const [totalReservations, activeVehicles, totalRevenue, totalCustomers] = await Promise.all([ prisma.reservation.count({ where: { companyId: req.companyId, createdAt: { gte: since } } }), prisma.vehicle.count({ where: { companyId: req.companyId, status: 'AVAILABLE' } }), prisma.reservation.aggregate({ where: { companyId: req.companyId, status: { in: ['COMPLETED'] }, createdAt: { gte: since } }, _sum: { totalAmount: true } }), prisma.customer.count({ where: { companyId: req.companyId } }), ]) res.json({ data: { totalReservations, activeVehicles, totalRevenue: totalRevenue._sum.totalAmount ?? 0, totalCustomers, period } }) } catch (err) { next(err) } }) router.get('/dashboard', async (req, res, next) => { try { const now = new Date() const monthStart = new Date(now.getFullYear(), now.getMonth(), 1) const prevMonthStart = new Date(now.getFullYear(), now.getMonth() - 1, 1) const prevMonthEnd = new Date(monthStart.getTime() - 1) const [ totalBookings, previousBookings, activeVehicles, previousActiveVehicles, totalCustomers, previousCustomers, monthlyRevenueAgg, previousRevenueAgg, recentReservations, sourceGroups, subscription, ] = await Promise.all([ prisma.reservation.count({ where: { companyId: req.companyId, createdAt: { gte: monthStart } } }), prisma.reservation.count({ where: { companyId: req.companyId, createdAt: { gte: prevMonthStart, lte: prevMonthEnd } } }), prisma.vehicle.count({ where: { companyId: req.companyId, status: { in: ['AVAILABLE', 'RENTED'] } } }), prisma.vehicle.count({ where: { companyId: req.companyId, createdAt: { lte: prevMonthEnd }, status: { in: ['AVAILABLE', 'RENTED'] } } }), prisma.customer.count({ where: { companyId: req.companyId } }), prisma.customer.count({ where: { companyId: req.companyId, createdAt: { lte: prevMonthEnd } } }), prisma.reservation.aggregate({ where: { companyId: req.companyId, status: { in: ['CONFIRMED', 'ACTIVE', 'COMPLETED'] }, createdAt: { gte: monthStart } }, _sum: { totalAmount: true } }), prisma.reservation.aggregate({ where: { companyId: req.companyId, status: { in: ['CONFIRMED', 'ACTIVE', 'COMPLETED'] }, createdAt: { gte: prevMonthStart, lte: prevMonthEnd } }, _sum: { totalAmount: true } }), prisma.reservation.findMany({ where: { companyId: req.companyId }, include: { customer: true, vehicle: true }, orderBy: { createdAt: 'desc' }, take: 8, }), prisma.reservation.groupBy({ by: ['source'], where: { companyId: req.companyId }, _count: { id: true }, _sum: { totalAmount: true }, }), prisma.subscription.findUnique({ where: { companyId: req.companyId } }), ]) const pct = (current: number, previous: number) => { if (previous === 0) return current > 0 ? 100 : 0 return Math.round(((current - previous) / previous) * 100) } const typedRecentReservations = recentReservations as Array<{ id: string contractNumber: string | null startDate: Date endDate: Date status: string totalAmount: number customer: { firstName: string; lastName: string } vehicle: { make: string; model: string } }> const typedSourceGroups = sourceGroups as Array<{ source: string _count: { id: number } _sum: { totalAmount: number | null } }> res.json({ data: { kpis: { totalBookings, activeVehicles, monthlyRevenue: monthlyRevenueAgg._sum.totalAmount ?? 0, totalCustomers, bookingsChange: pct(totalBookings, previousBookings), vehiclesChange: pct(activeVehicles, previousActiveVehicles), revenueChange: pct(monthlyRevenueAgg._sum.totalAmount ?? 0, previousRevenueAgg._sum.totalAmount ?? 0), customersChange: pct(totalCustomers, previousCustomers), }, recentReservations: typedRecentReservations.map((reservation) => ({ id: reservation.id, bookingRef: reservation.contractNumber ?? reservation.id.slice(-8).toUpperCase(), customerName: `${reservation.customer.firstName} ${reservation.customer.lastName}`, vehicleName: `${reservation.vehicle.make} ${reservation.vehicle.model}`, startDate: reservation.startDate, endDate: reservation.endDate, status: reservation.status, totalAmount: reservation.totalAmount, })), sourceBreakdown: typedSourceGroups.map((group) => ({ source: group.source, count: group._count.id, revenue: group._sum.totalAmount ?? 0, })), subscription: subscription ? { status: subscription.status, planName: subscription.plan, trialEndsAt: subscription.trialEndAt, } : null, }, }) } catch (err) { next(err) } }) router.get('/sources', async (req, res, next) => { try { const sources = await prisma.reservation.groupBy({ by: ['source'], where: { companyId: req.companyId }, _count: { id: true } }) res.json({ data: sources }) } catch (err) { next(err) } }) router.get('/report', async (req, res, next) => { try { const { from, to, format = 'JSON', period } = req.query as Record const accountingSettings = await prisma.accountingSettings.findUnique({ where: { companyId: req.companyId } }) const derivedRange = getRangeFromPeriod(period || accountingSettings?.reportingPeriod || 'MONTHLY') const startDate = from ? new Date(from) : derivedRange.from const endDate = to ? new Date(to) : derivedRange.to const report = await generateFinancialReport(req.companyId, startDate, endDate) if (format === 'CSV') { res.setHeader('Content-Type', 'text/csv') res.setHeader('Content-Disposition', `attachment; filename="report-${startDate.toISOString().slice(0, 10)}-${endDate.toISOString().slice(0, 10)}.csv"`) return res.send(toCsv(report.rows)) } res.json({ data: report }) } catch (err) { next(err) } }) export default router