mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
316 lines
11 KiB
TypeScript
316 lines
11 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|