import { Injectable } from '@nestjs/common'; import { PrismaService } from '../../common/prisma.service'; import { GenerateReportDto, ReportType } from './reports.dto'; @Injectable() export class ReportsService { constructor(private prisma: PrismaService) {} async generateReport(dto: GenerateReportDto) { const dateFrom = new Date(dto.dateFrom); const dateTo = new Date(dto.dateTo); let data: any; switch (dto.reportType) { case ReportType.REVENUE: data = await this.generateRevenueReport(dateFrom, dateTo); break; case ReportType.OCCUPANCY: data = await this.generateOccupancyReport(dateFrom, dateTo); break; case ReportType.AGENT_SALES: data = await this.generateAgentSalesReport(dateFrom, dateTo, dto.agentId); break; case ReportType.CANCELLATIONS: data = await this.generateCancellationsReport(dateFrom, dateTo); break; case ReportType.PAYMENT_METHODS: data = await this.generatePaymentMethodsReport(dateFrom, dateTo); break; default: data = {}; } const report = await this.prisma.operationalReport.create({ data: { reportType: dto.reportType, dateFrom, dateTo, data } }); return { reportId: report.id, reportType: dto.reportType, data }; } private async generateRevenueReport(dateFrom: Date, dateTo: Date) { const bookings = await this.prisma.booking.findMany({ where: { createdAt: { gte: dateFrom, lte: dateTo }, status: { in: ['CONFIRMED', 'COMPLETED'] } }, include: { paymentIntent: true } }); const totalRevenue = bookings.reduce((sum, b) => sum + b.totalMinor, 0); const byPaymentMethod = bookings.reduce((acc, b) => { const method = b.paymentIntent?.method ?? 'UNKNOWN'; acc[method] = (acc[method] || 0) + b.totalMinor; return acc; }, {} as Record); return { totalBookings: bookings.length, totalRevenueMinor: totalRevenue, totalRevenue: totalRevenue / 100, currency: 'ETB', byPaymentMethod }; } private async generateOccupancyReport(dateFrom: Date, dateTo: Date) { const schedules = await this.prisma.trainSchedule.findMany({ where: { departureAt: { gte: dateFrom, lte: dateTo } }, include: { coachAssignments: { include: { coach: { include: { seats: true } } } }, bookings: { where: { status: { in: ['CONFIRMED', 'COMPLETED'] } }, include: { seats: true } }, }, }); const tripData = schedules.map(schedule => { const totalSeats = schedule.coachAssignments.reduce((sum, a) => sum + a.coach.seats.length, 0); const bookedSeats = schedule.bookings.reduce((sum, b) => sum + b.seats.length, 0); const occupancyRate = totalSeats > 0 ? (bookedSeats / totalSeats) * 100 : 0; return { scheduleId: schedule.id, departureAt: schedule.departureAt, totalSeats, bookedSeats, occupancyRate: +occupancyRate.toFixed(2) }; }); const avgOccupancy = tripData.length > 0 ? tripData.reduce((sum, t) => sum + t.occupancyRate, 0) / tripData.length : 0; return { totalSchedules: schedules.length, averageOccupancyRate: +avgOccupancy.toFixed(2), schedules: tripData }; } private async generateAgentSalesReport(dateFrom: Date, dateTo: Date, agentId?: string) { const agentBookings = await this.prisma.agentBooking.findMany({ where: { createdAt: { gte: dateFrom, lte: dateTo }, ...(agentId ? { agentId } : {}) }, include: { agent: { include: { user: true } }, booking: true } }); const byAgent = agentBookings.reduce((acc, ab) => { const agentName = ab.agent.user.fullName; if (!acc[agentName]) { acc[agentName] = { bookings: 0, revenueMinor: 0, cashCollected: 0 }; } acc[agentName].bookings += 1; acc[agentName].revenueMinor += ab.booking.totalMinor; acc[agentName].cashCollected += ab.cashReceived ?? 0; return acc; }, {} as Record); return { totalAgentBookings: agentBookings.length, byAgent }; } private async generateCancellationsReport(dateFrom: Date, dateTo: Date) { const cancellations = await this.prisma.bookingCancellation.findMany({ where: { createdAt: { gte: dateFrom, lte: dateTo } }, include: { booking: true } }); const totalRefunded = cancellations.reduce((sum, c) => sum + c.refundAmount, 0); return { totalCancellations: cancellations.length, totalRefundedMinor: totalRefunded, totalRefunded: totalRefunded / 100, currency: 'ETB' }; } private async generatePaymentMethodsReport(dateFrom: Date, dateTo: Date) { const payments = await this.prisma.paymentIntent.findMany({ where: { createdAt: { gte: dateFrom, lte: dateTo }, status: 'SUCCEEDED' } }); const byMethod = payments.reduce((acc, p) => { const method = p.method; if (!acc[method]) { acc[method] = { count: 0, totalMinor: 0 }; } acc[method].count += 1; acc[method].totalMinor += p.amountMinor; return acc; }, {} as Record); return { totalPayments: payments.length, byMethod }; } async getReport(reportId: string) { return this.prisma.operationalReport.findUnique({ where: { id: reportId } }); } async listReports(reportType?: string) { return this.prisma.operationalReport.findMany({ where: reportType ? { reportType } : {}, orderBy: { createdAt: 'desc' }, take: 50 }); } }