import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common'; import { PrismaService } from '../../common/prisma.service'; import { CreateTravelerProfileDto, CreateSavedRouteDto, RegisterPassengerDto } from './passengers.dto'; import { VerifaydaService } from '../verifayda/verifayda.service'; interface PassengerFilters { search?: string; verified?: boolean; page?: number; pageSize?: number; } @Injectable() export class PassengersService { constructor( private prisma: PrismaService, private verifaydaService: VerifaydaService, ) {} async findAll(filters: PassengerFilters = {}) { const { search, verified, page = 1, pageSize = 20 } = filters; const skip = (page - 1) * pageSize; const where: any = {}; if (search) { where.user = { OR: [ { fullName: { contains: search, mode: 'insensitive' } }, { email: { contains: search, mode: 'insensitive' } }, { phone: { contains: search, mode: 'insensitive' } }, ], }; } if (verified !== undefined) { where.user = { ...where.user, nationalId: verified ? { not: null } : null, }; } const [items, total] = await Promise.all([ this.prisma.passenger.findMany({ where, skip, take: pageSize, orderBy: { createdAt: 'desc' }, include: { user: { select: { id: true, fullName: true, email: true, phone: true, nationalId: true, nationality: true, }, }, loyalty: true, _count: { select: { bookings: true, }, }, }, }), this.prisma.passenger.count({ where }), ]); return { items: items.map(passenger => ({ id: passenger.id, fullName: passenger.user.fullName, email: passenger.user.email, phone: passenger.user.phone, nationalId: passenger.user.nationalId, nationality: passenger.user.nationality, verified: !!passenger.user.nationalId, loyaltyTier: passenger.loyalty?.tier || 'BRONZE', loyaltyPoints: passenger.loyalty?.pointsBalance || 0, totalBookings: passenger._count.bookings, createdAt: passenger.createdAt, })), meta: { page, pageSize, total, totalPages: Math.ceil(total / pageSize), }, }; } async getProfile(passengerId: string) { const passenger = await this.prisma.passenger.findUnique({ where: { id: passengerId }, include: { user: { select: { fullName: true, email: true, phone: true } }, bookings: { orderBy: { createdAt: 'desc' }, take: 10, include: { schedule: { include: { originStation: true, destinationStation: true, train: true } }, seats: { include: { seat: { include: { coach: true } } } } } }, loyalty: true, wallet: true, travelerProfiles: true, savedRoutes: true, }, }); if (!passenger) throw new NotFoundException('Passenger not found'); return { id: passenger.id, fullName: passenger.user.fullName, email: passenger.user.email, phone: passenger.user.phone, createdAt: passenger.createdAt, bookings: passenger.bookings.map((b) => ({ id: b.id, bookingRef: b.bookingRef, status: b.status, totalFare: b.totalMinor / 100, createdAt: b.createdAt, trip: { number: b.schedule.train.number, origin: { id: b.schedule.originStation.id, name: b.schedule.originStation.name, code: b.schedule.originStation.code, city: b.schedule.originStation.city }, destination: { id: b.schedule.destinationStation.id, name: b.schedule.destinationStation.name, code: b.schedule.destinationStation.code, city: b.schedule.destinationStation.city }, departureAt: b.schedule.departureAt, }, passengers: b.seats.map((bs) => ({ fullName: bs.passengerName, seat: { number: bs.seat.seatNumber, coach: bs.seat.coach.number, class: 'N/A' } })), })), }; } async getStats(passengerId: string) { const [totalTrips, totalSpendResult, loyalty] = await Promise.all([ this.prisma.booking.count({ where: { passengerId, status: 'COMPLETED' } }), this.prisma.booking.aggregate({ where: { passengerId, status: 'COMPLETED' }, _sum: { totalMinor: true } }), this.prisma.loyaltyAccount.findUnique({ where: { passengerId } }), ]); const totalSpend = (totalSpendResult._sum.totalMinor ?? 0) / 100; return { totalTrips, totalSpend, loyaltyPoints: loyalty?.pointsBalance ?? 0, co2Saved: totalTrips * 6 }; } async savePassengers(passengers: any[], userId?: string, deviceId?: string) { if (!passengers || !Array.isArray(passengers)) { throw new BadRequestException('Passengers array is required'); } if (passengers.length === 0) { throw new BadRequestException('At least one passenger is required'); } const savedProfiles = await Promise.all( passengers.map((p) => this.prisma.savedPassengerProfile.create({ data: { userId, deviceId, passengerName: p.name || p.passengerName, dateOfBirth: new Date(p.dateOfBirth), idDocumentType: p.nationalId ? 'NATIONAL_ID' : 'PASSPORT', passportNumber: p.passportNumber, passportCountry: p.passportCountry, nationality: p.nationality, phone: p.phone, email: p.email, }, }) ) ); return { count: savedProfiles.length, passengerIds: savedProfiles.map(p => p.id), passengers: savedProfiles.map(p => ({ id: p.id, passengerName: p.passengerName, dateOfBirth: p.dateOfBirth, nationality: p.nationality, })), message: 'Passenger details saved successfully', }; } createTravelerProfile(dto: CreateTravelerProfileDto) { return this.prisma.travelerProfile.create({ data: { ...dto, dateOfBirth: dto.dateOfBirth ? new Date(dto.dateOfBirth) : null } }); } getTravelerProfiles(passengerId: string) { return this.prisma.travelerProfile.findMany({ where: { passengerId } }); } createSavedRoute(dto: CreateSavedRouteDto) { return this.prisma.savedRoute.create({ data: dto }); } getSavedRoutes(passengerId: string) { return this.prisma.savedRoute.findMany({ where: { passengerId }, orderBy: { tripCount: 'desc' } }); } async updatePassenger(id: string, dto: any) { const passenger = await this.prisma.passenger.findUnique({ where: { id } }); if (!passenger) throw new NotFoundException('Passenger not found'); return this.prisma.passenger.update({ where: { id }, data: { user: { update: { fullName: dto.fullName || undefined, email: dto.email || undefined, phone: dto.phone || undefined, nationality: dto.nationality || undefined, }, }, }, include: { user: { select: { fullName: true, email: true, phone: true, nationality: true } }, loyalty: true, }, }); } async registerPassenger(dto: RegisterPassengerDto) { const isEthiopian = !!dto.nationalId; const isLoggedIn = !!dto.userId; let verifiedData: any = null; if (isEthiopian && dto.verifyWithFayda !== false) { try { const verification = await this.verifaydaService.verifyNationalId(dto.nationalId!); if (verification.verified && verification.passengerData) { verifiedData = verification.passengerData; } } catch (error) { console.warn('Fayda verification failed, using manual data:', error); } } const finalData = { passengerName: verifiedData?.fullName || dto.passengerName, dateOfBirth: verifiedData?.dateOfBirth || new Date(dto.dateOfBirth), nationality: verifiedData?.nationality || dto.nationality || (isEthiopian ? 'Ethiopian' : null), gender: verifiedData?.gender || dto.gender, phone: dto.phone, email: dto.email, }; if (isLoggedIn) { const user = await this.prisma.user.findUnique({ where: { id: dto.userId }, include: { passenger: true }, }); if (!user) { throw new BadRequestException('User not found'); } if (!user.faydaVerified && verifiedData) { await this.prisma.user.update({ where: { id: dto.userId }, data: { fullName: finalData.passengerName, nationality: finalData.nationality, nationalId: dto.nationalId, passportNumber: dto.passportNumber, faydaVerified: !!verifiedData, faydaVerifiedAt: verifiedData ? new Date() : null, }, }); } return { id: user.passenger?.id || user.id, passengerName: finalData.passengerName, dateOfBirth: finalData.dateOfBirth, nationality: finalData.nationality, verified: !!verifiedData, linked: true, message: 'Passenger details saved and linked to user account', }; } const profile = await this.prisma.savedPassengerProfile.create({ data: { deviceId: dto.deviceId, passengerName: finalData.passengerName, dateOfBirth: finalData.dateOfBirth, idDocumentType: isEthiopian ? 'NATIONAL_ID' : 'PASSPORT', passportNumber: dto.passportNumber, passportCountry: dto.passportCountry, nationality: finalData.nationality, phone: dto.phone, email: dto.email, }, }); return { id: profile.id, passengerName: finalData.passengerName, dateOfBirth: finalData.dateOfBirth, nationality: finalData.nationality, verified: !!verifiedData, linked: false, message: 'Passenger details saved for guest booking', }; } async deletePassenger(id: string) { const passenger = await this.prisma.passenger.findUnique({ where: { id } }); if (!passenger) throw new NotFoundException('Passenger not found'); await this.prisma.passenger.delete({ where: { id } }); return { deleted: true, passengerId: id }; } async checkPassengerUsage(id: string) { const [bookingCount, loyaltyAccount, walletAccount] = await Promise.all([ this.prisma.booking.count({ where: { passengerId: id } }), this.prisma.loyaltyAccount.findUnique({ where: { passengerId: id } }), this.prisma.walletAccount.findUnique({ where: { passengerId: id } }), ]); const usage = []; if (bookingCount > 0) usage.push(`${bookingCount} booking(s)`); if (loyaltyAccount) usage.push('Loyalty account'); if (walletAccount) usage.push('Wallet account'); return { isInUse: usage.length > 0, affectedModules: usage, }; } }