Files
edr-platform/apps/edr-passenger-api/src/modules/reports/reports.service.ts

172 lines
5.5 KiB
TypeScript

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<string, number>);
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<string, any>);
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<string, any>);
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
});
}
}