import { Injectable, NotFoundException } from '@nestjs/common'; import { PrismaService } from '../../common/prisma.service'; import { SearchTripsDto, FareQuoteDto } from './search.dto'; const POINTS_TO_MINOR = 10; @Injectable() export class SearchService { constructor(private prisma: PrismaService) {} async searchTrips(dto: SearchTripsDto) { const date = new Date(dto.date), nextDay = new Date(date.getTime() + 86400000); const trips = await this.prisma.trip.findMany({ where: { originStationId: dto.originStationId, destinationStationId: dto.destinationStationId, departureAt: { gte: date, lt: nextDay }, status: { in: ['SCHEDULED', 'BOARDING'] } }, include: { service: true, originStation: true, destinationStation: true, coaches: { include: { seats: true } } }, }); return trips.map((trip) => { const seatsByClass = (cls: string) => trip.coaches.filter((c) => c.serviceClass === cls).flatMap((c) => c.seats); const avail = (cls: string) => seatsByClass(cls).filter((s) => s.status === 'AVAILABLE').length; return { id: trip.id, number: trip.service.number, origin: { id: trip.originStation.id, code: trip.originStation.code, name: trip.originStation.name, city: trip.originStation.city }, destination: { id: trip.destinationStation.id, code: trip.destinationStation.code, name: trip.destinationStation.name, city: trip.destinationStation.city }, departureAt: trip.departureAt, arrivalAt: trip.arrivalAt, status: trip.status, availability: { ECONOMY: avail('ECONOMY'), BUSINESS: avail('BUSINESS'), FIRST: avail('FIRST') }, fares: { ECONOMY: this.defaultFare('ECONOMY') / 100, BUSINESS: this.defaultFare('BUSINESS') / 100, FIRST: this.defaultFare('FIRST') / 100 }, }; }); } async getFareQuote(dto: FareQuoteDto) { const trip = await this.prisma.trip.findUnique({ where: { id: dto.tripId } }); if (!trip) throw new NotFoundException('Trip not found'); const count = dto.passengerCount ?? 1; const baseFareMinor = this.defaultFare(dto.serviceClass) * count; let discountMinor = 0; if (dto.promoCode) { const promo = await this.prisma.promotion.findUnique({ where: { code: dto.promoCode } }); if (promo?.active && promo.validUntil > new Date()) discountMinor = promo.percentOff ? Math.round(baseFareMinor * promo.percentOff / 100) : (promo.amountOffMinor ?? 0); } const loyaltyMinor = (dto.loyaltyRedemptionPoints ?? 0) * POINTS_TO_MINOR; const taxesMinor = Math.round(baseFareMinor * 0.05); return { tripId: dto.tripId, serviceClass: dto.serviceClass, passengerCount: count, baseFareMinor, discountMinor, loyaltyRedemptionMinor: loyaltyMinor, taxesFeesMinor: taxesMinor, totalMinor: Math.max(0, baseFareMinor - discountMinor - loyaltyMinor + taxesMinor), currency: 'ETB' }; } private defaultFare(serviceClass: string): number { return ({ ECONOMY: 45000, BUSINESS: 90000, FIRST: 135000 } as any)[serviceClass] ?? 45000; } }