mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 22:25:42 +00:00
@@ -881,6 +881,7 @@ export class BookingsService {
|
|||||||
status: 'PENDING_PAYMENT',
|
status: 'PENDING_PAYMENT',
|
||||||
bookingType: 'ONE_WAY',
|
bookingType: 'ONE_WAY',
|
||||||
totalMinor: resolvedTotalMinor,
|
totalMinor: resolvedTotalMinor,
|
||||||
|
currency: displayCurrency,
|
||||||
adultCount,
|
adultCount,
|
||||||
childCount,
|
childCount,
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
@@ -1058,6 +1059,7 @@ export class BookingsService {
|
|||||||
status: 'PENDING_PAYMENT',
|
status: 'PENDING_PAYMENT',
|
||||||
bookingType: 'ROUND_TRIP',
|
bookingType: 'ROUND_TRIP',
|
||||||
totalMinor,
|
totalMinor,
|
||||||
|
currency: displayCurrency,
|
||||||
adultCount,
|
adultCount,
|
||||||
childCount,
|
childCount,
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
@@ -1250,6 +1252,7 @@ export class BookingsService {
|
|||||||
status: 'PENDING_PAYMENT',
|
status: 'PENDING_PAYMENT',
|
||||||
bookingType: 'TRANSIT',
|
bookingType: 'TRANSIT',
|
||||||
totalMinor,
|
totalMinor,
|
||||||
|
currency: displayCurrency,
|
||||||
adultCount,
|
adultCount,
|
||||||
childCount,
|
childCount,
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
@@ -1459,7 +1462,7 @@ export class BookingsService {
|
|||||||
destinationStationId: dto.leg2DestinationStationId,
|
destinationStationId: dto.leg2DestinationStationId,
|
||||||
status: 'PENDING_PAYMENT',
|
status: 'PENDING_PAYMENT',
|
||||||
bookingType: 'ROUND_TRIP_TRANSIT',
|
bookingType: 'ROUND_TRIP_TRANSIT',
|
||||||
totalMinor, adultCount, childCount, displayCurrency, displayTotalMinor,
|
totalMinor, currency: displayCurrency, adultCount, childCount, displayCurrency, displayTotalMinor,
|
||||||
// Outbound transit leg-2
|
// Outbound transit leg-2
|
||||||
leg2ScheduleId: dto.leg2ScheduleId,
|
leg2ScheduleId: dto.leg2ScheduleId,
|
||||||
leg2OriginStationId: dto.transitStationId,
|
leg2OriginStationId: dto.transitStationId,
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ function generateRef(): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ethiopian mobile prefixes: Ethio Telecom (09xx) and Safaricom ET (07xx)
|
// Ethiopian mobile prefixes: Ethio Telecom (09xx) and Safaricom ET (07xx)
|
||||||
const ETH_MOBILE_PREFIXES = ['911','912','913','914','915','916','917','921','922','923','924','930','931','932','933','934','935','936','937','938','939','961','962','963','964'];
|
const ETH_MOBILE_PREFIXES = ['911', '912', '913', '914', '915', '916', '917', '921', '922', '923', '924', '930', '931', '932', '933', '934', '935', '936', '937', '938', '939', '961', '962', '963', '964'];
|
||||||
|
|
||||||
function generateEthiopianPhone(): string {
|
function generateEthiopianPhone(): string {
|
||||||
const prefix = ETH_MOBILE_PREFIXES[Math.floor(Math.random() * ETH_MOBILE_PREFIXES.length)];
|
const prefix = ETH_MOBILE_PREFIXES[Math.floor(Math.random() * ETH_MOBILE_PREFIXES.length)];
|
||||||
@@ -50,7 +50,7 @@ export class GuestBookingService {
|
|||||||
private passengerAuthService: PassengerAuthService,
|
private passengerAuthService: PassengerAuthService,
|
||||||
private fareEngine: FareEngineService,
|
private fareEngine: FareEngineService,
|
||||||
private eventEmitter: EventEmitter2,
|
private eventEmitter: EventEmitter2,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
async createGuestBooking(dto: CreateGuestBookingDto, req?: any) {
|
async createGuestBooking(dto: CreateGuestBookingDto, req?: any) {
|
||||||
// Enrich passengers with phone/email from SavedPassengerProfile when not supplied inline.
|
// Enrich passengers with phone/email from SavedPassengerProfile when not supplied inline.
|
||||||
@@ -70,8 +70,8 @@ export class GuestBookingService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dto.bookingType === 'ROUND_TRIP') return this.createGuestRoundTripBooking(dto, req);
|
if (dto.bookingType === 'ROUND_TRIP') return this.createGuestRoundTripBooking(dto, req);
|
||||||
if (dto.bookingType === 'TRANSIT') return this.createGuestTransitBooking(dto, req);
|
if (dto.bookingType === 'TRANSIT') return this.createGuestTransitBooking(dto, req);
|
||||||
if (dto.bookingType === 'ROUND_TRIP_TRANSIT') return this.createGuestRoundTripTransitBooking(dto, req);
|
if (dto.bookingType === 'ROUND_TRIP_TRANSIT') return this.createGuestRoundTripTransitBooking(dto, req);
|
||||||
return this.createGuestOneWayBooking(dto, req);
|
return this.createGuestOneWayBooking(dto, req);
|
||||||
}
|
}
|
||||||
@@ -123,8 +123,8 @@ export class GuestBookingService {
|
|||||||
let nationality = passenger.nationality;
|
let nationality = passenger.nationality;
|
||||||
|
|
||||||
const isEthiopian = passenger.nationality === 'Ethiopian' ||
|
const isEthiopian = passenger.nationality === 'Ethiopian' ||
|
||||||
passenger.nationality === 'ETHIOPIAN' ||
|
passenger.nationality === 'ETHIOPIAN' ||
|
||||||
passenger.idDocumentType === IdDocumentType.NATIONAL_ID;
|
passenger.idDocumentType === IdDocumentType.NATIONAL_ID;
|
||||||
|
|
||||||
if (isEthiopian && passenger.idDocumentType === IdDocumentType.NATIONAL_ID) {
|
if (isEthiopian && passenger.idDocumentType === IdDocumentType.NATIONAL_ID) {
|
||||||
if (passenger.idDocumentNumber) {
|
if (passenger.idDocumentNumber) {
|
||||||
@@ -278,13 +278,14 @@ export class GuestBookingService {
|
|||||||
destinationStationId: dto.destinationStationId,
|
destinationStationId: dto.destinationStationId,
|
||||||
status: 'PENDING_PAYMENT',
|
status: 'PENDING_PAYMENT',
|
||||||
totalMinor: resolvedTotalMinor,
|
totalMinor: resolvedTotalMinor,
|
||||||
|
currency: displayCurrency,
|
||||||
adultCount,
|
adultCount,
|
||||||
childCount,
|
childCount,
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
displayTotalMinor,
|
displayTotalMinor,
|
||||||
bookingType: 'ONE_WAY',
|
bookingType: 'ONE_WAY',
|
||||||
...(dto.packageId ? { packageId: dto.packageId, priceTierId: dto.priceTierId } : {}),
|
...(dto.packageId ? { packageId: dto.packageId, priceTierId: dto.priceTierId } : {}),
|
||||||
userAgent: dto.deviceId,
|
userAgent: dto.deviceId,
|
||||||
contactEmail: firstPassenger.email || null,
|
contactEmail: firstPassenger.email || null,
|
||||||
contactPhone: firstPassenger.phone || null,
|
contactPhone: firstPassenger.phone || null,
|
||||||
seats: {
|
seats: {
|
||||||
@@ -350,7 +351,7 @@ export class GuestBookingService {
|
|||||||
this.prisma.seatHold.findUnique({ where: { id: dto.returnHoldId } }),
|
this.prisma.seatHold.findUnique({ where: { id: dto.returnHoldId } }),
|
||||||
]);
|
]);
|
||||||
if (!outboundHold || outboundHold.expiresAt < new Date()) throw new BadRequestException('Outbound seat hold expired or not found');
|
if (!outboundHold || outboundHold.expiresAt < new Date()) throw new BadRequestException('Outbound seat hold expired or not found');
|
||||||
if (!returnHold || returnHold.expiresAt < new Date()) throw new BadRequestException('Return seat hold expired or not found');
|
if (!returnHold || returnHold.expiresAt < new Date()) throw new BadRequestException('Return seat hold expired or not found');
|
||||||
|
|
||||||
// Validate passengers have returnSeatId
|
// Validate passengers have returnSeatId
|
||||||
for (const p of dto.passengers) {
|
for (const p of dto.passengers) {
|
||||||
@@ -369,7 +370,7 @@ export class GuestBookingService {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
if (!outboundSchedule) throw new NotFoundException('Outbound schedule not found');
|
if (!outboundSchedule) throw new NotFoundException('Outbound schedule not found');
|
||||||
if (!returnSchedule) throw new NotFoundException('Return schedule not found');
|
if (!returnSchedule) throw new NotFoundException('Return schedule not found');
|
||||||
|
|
||||||
if (Date.now() >= outboundSchedule.departureAt.getTime() - BOOKING_CUTOFF_MS) {
|
if (Date.now() >= outboundSchedule.departureAt.getTime() - BOOKING_CUTOFF_MS) {
|
||||||
throw new BadRequestException('Bookings are not accepted within 30 minutes of departure');
|
throw new BadRequestException('Bookings are not accepted within 30 minutes of departure');
|
||||||
@@ -379,19 +380,19 @@ export class GuestBookingService {
|
|||||||
const station = sched.originStationId === stationId ? sched.originStation : sched.destinationStation;
|
const station = sched.originStationId === stationId ? sched.originStation : sched.destinationStation;
|
||||||
return { stationId, sequence: seq, station };
|
return { stationId, sequence: seq, station };
|
||||||
};
|
};
|
||||||
const obStops = outboundSchedule.stopTimes.length > 0 ? outboundSchedule.stopTimes : [synth(outboundSchedule, outboundSchedule.originStationId, 0), synth(outboundSchedule, outboundSchedule.destinationStationId, 1)];
|
const obStops = outboundSchedule.stopTimes.length > 0 ? outboundSchedule.stopTimes : [synth(outboundSchedule, outboundSchedule.originStationId, 0), synth(outboundSchedule, outboundSchedule.destinationStationId, 1)];
|
||||||
const retStops = returnSchedule.stopTimes.length > 0 ? returnSchedule.stopTimes : [synth(returnSchedule, returnSchedule.originStationId, 0), synth(returnSchedule, returnSchedule.destinationStationId, 1)];
|
const retStops = returnSchedule.stopTimes.length > 0 ? returnSchedule.stopTimes : [synth(returnSchedule, returnSchedule.originStationId, 0), synth(returnSchedule, returnSchedule.destinationStationId, 1)];
|
||||||
const outboundOriginStop = obStops.find((s: any) => s.stationId === dto.originStationId) ?? obStops[0];
|
const outboundOriginStop = obStops.find((s: any) => s.stationId === dto.originStationId) ?? obStops[0];
|
||||||
const outboundDestStop = obStops.find((s: any) => s.stationId === dto.destinationStationId) ?? obStops[obStops.length - 1];
|
const outboundDestStop = obStops.find((s: any) => s.stationId === dto.destinationStationId) ?? obStops[obStops.length - 1];
|
||||||
const returnOriginStop = retStops.find((s: any) => s.stationId === dto.returnOriginStationId) ?? retStops[0];
|
const returnOriginStop = retStops.find((s: any) => s.stationId === dto.returnOriginStationId) ?? retStops[0];
|
||||||
const returnDestStop = retStops.find((s: any) => s.stationId === dto.returnDestinationStationId) ?? retStops[retStops.length - 1];
|
const returnDestStop = retStops.find((s: any) => s.stationId === dto.returnDestinationStationId) ?? retStops[retStops.length - 1];
|
||||||
if (!outboundOriginStop || !outboundDestStop) throw new NotFoundException('Outbound origin or destination not found on schedule');
|
if (!outboundOriginStop || !outboundDestStop) throw new NotFoundException('Outbound origin or destination not found on schedule');
|
||||||
if (!returnOriginStop || !returnDestStop) throw new NotFoundException('Return origin or destination not found on schedule');
|
if (!returnOriginStop || !returnDestStop) throw new NotFoundException('Return origin or destination not found on schedule');
|
||||||
|
|
||||||
const outboundSegmentRoute = `${outboundOriginStop.station.code}-${outboundDestStop.station.code}`;
|
const outboundSegmentRoute = `${outboundOriginStop.station.code}-${outboundDestStop.station.code}`;
|
||||||
const outboundFullRoute = `${outboundSchedule.originStation.code}-${outboundSchedule.destinationStation.code}`;
|
const outboundFullRoute = `${outboundSchedule.originStation.code}-${outboundSchedule.destinationStation.code}`;
|
||||||
const returnSegmentRoute = `${returnOriginStop.station.code}-${returnDestStop.station.code}`;
|
const returnSegmentRoute = `${returnOriginStop.station.code}-${returnDestStop.station.code}`;
|
||||||
const returnFullRoute = `${returnSchedule.originStation.code}-${returnSchedule.destinationStation.code}`;
|
const returnFullRoute = `${returnSchedule.originStation.code}-${returnSchedule.destinationStation.code}`;
|
||||||
|
|
||||||
// Process passengers (verify identity once — same person travels both legs)
|
// Process passengers (verify identity once — same person travels both legs)
|
||||||
const passengersData: any[] = [];
|
const passengersData: any[] = [];
|
||||||
@@ -409,16 +410,16 @@ export class GuestBookingService {
|
|||||||
let nationality = passenger.nationality;
|
let nationality = passenger.nationality;
|
||||||
|
|
||||||
const isEthiopian = passenger.nationality === 'Ethiopian' ||
|
const isEthiopian = passenger.nationality === 'Ethiopian' ||
|
||||||
passenger.nationality === 'ETHIOPIAN' ||
|
passenger.nationality === 'ETHIOPIAN' ||
|
||||||
passenger.idDocumentType === IdDocumentType.NATIONAL_ID;
|
passenger.idDocumentType === IdDocumentType.NATIONAL_ID;
|
||||||
|
|
||||||
if (isEthiopian && passenger.idDocumentType === IdDocumentType.NATIONAL_ID) {
|
if (isEthiopian && passenger.idDocumentType === IdDocumentType.NATIONAL_ID) {
|
||||||
if (passenger.idDocumentNumber) {
|
if (passenger.idDocumentNumber) {
|
||||||
const verification = await this.verifaydaService.verifyNationalId(passenger.idDocumentNumber);
|
const verification = await this.verifaydaService.verifyNationalId(passenger.idDocumentNumber);
|
||||||
if (!verification.verified) throw new BadRequestException(`Verifayda verification failed for ${passenger.passengerName}: ${verification.failureReason}`);
|
if (!verification.verified) throw new BadRequestException(`Verifayda verification failed for ${passenger.passengerName}: ${verification.failureReason}`);
|
||||||
passengerName = verification.passengerData?.fullName || passengerName;
|
passengerName = verification.passengerData?.fullName || passengerName;
|
||||||
verifaydaVerified = true;
|
verifaydaVerified = true;
|
||||||
verifaydaData = verification.passengerData?.profileData;
|
verifaydaData = verification.passengerData?.profileData;
|
||||||
}
|
}
|
||||||
nationality = 'Ethiopian';
|
nationality = 'Ethiopian';
|
||||||
} else if (!isEthiopian && passenger.idDocumentType === IdDocumentType.PASSPORT) {
|
} else if (!isEthiopian && passenger.idDocumentType === IdDocumentType.PASSPORT) {
|
||||||
@@ -450,7 +451,7 @@ export class GuestBookingService {
|
|||||||
returnBaseFare = tier.priceMinor - halfMinor;
|
returnBaseFare = tier.priceMinor - halfMinor;
|
||||||
paidChildrenCount = childCount;
|
paidChildrenCount = childCount;
|
||||||
outboundChildUnitFare = Math.round(outboundBaseFare * 0.1);
|
outboundChildUnitFare = Math.round(outboundBaseFare * 0.1);
|
||||||
returnChildUnitFare = Math.round(returnBaseFare * 0.1);
|
returnChildUnitFare = Math.round(returnBaseFare * 0.1);
|
||||||
} else {
|
} else {
|
||||||
const primaryNationality = passengersData[0]?.nationality;
|
const primaryNationality = passengersData[0]?.nationality;
|
||||||
[outboundBaseFare, returnBaseFare] = await Promise.all([
|
[outboundBaseFare, returnBaseFare] = await Promise.all([
|
||||||
@@ -459,11 +460,11 @@ export class GuestBookingService {
|
|||||||
]);
|
]);
|
||||||
paidChildrenCount = Math.max(0, childCount - 1);
|
paidChildrenCount = Math.max(0, childCount - 1);
|
||||||
outboundChildUnitFare = outboundBaseFare;
|
outboundChildUnitFare = outboundBaseFare;
|
||||||
returnChildUnitFare = returnBaseFare;
|
returnChildUnitFare = returnBaseFare;
|
||||||
}
|
}
|
||||||
const outboundTotalBase = outboundBaseFare * adultCount + outboundChildUnitFare * paidChildrenCount;
|
const outboundTotalBase = outboundBaseFare * adultCount + outboundChildUnitFare * paidChildrenCount;
|
||||||
const returnTotalBase = returnBaseFare * adultCount + returnChildUnitFare * paidChildrenCount;
|
const returnTotalBase = returnBaseFare * adultCount + returnChildUnitFare * paidChildrenCount;
|
||||||
const combinedBaseFareMinor = outboundTotalBase + returnTotalBase;
|
const combinedBaseFareMinor = outboundTotalBase + returnTotalBase;
|
||||||
|
|
||||||
let discountMinor = 0;
|
let discountMinor = 0;
|
||||||
if (dto.promoCode) {
|
if (dto.promoCode) {
|
||||||
@@ -490,16 +491,16 @@ export class GuestBookingService {
|
|||||||
let outboundFareMinor: number;
|
let outboundFareMinor: number;
|
||||||
let returnFareMinor: number;
|
let returnFareMinor: number;
|
||||||
if (p.category === PassengerCategory.ADULT) {
|
if (p.category === PassengerCategory.ADULT) {
|
||||||
outboundFareMinor = p.seatFareMinor ?? outboundBaseFare;
|
outboundFareMinor = p.seatFareMinor ?? outboundBaseFare;
|
||||||
returnFareMinor = p.returnSeatFareMinor ?? returnBaseFare;
|
returnFareMinor = p.returnSeatFareMinor ?? returnBaseFare;
|
||||||
} else if (isPackageRoundTrip) {
|
} else if (isPackageRoundTrip) {
|
||||||
outboundFareMinor = 0;
|
outboundFareMinor = 0;
|
||||||
returnFareMinor = 0;
|
returnFareMinor = 0;
|
||||||
} else {
|
} else {
|
||||||
if (!outboundFreeChildUsed) { outboundFareMinor = 0; outboundFreeChildUsed = true; }
|
if (!outboundFreeChildUsed) { outboundFareMinor = 0; outboundFreeChildUsed = true; }
|
||||||
else outboundFareMinor = p.seatFareMinor ?? outboundChildUnitFare;
|
else outboundFareMinor = p.seatFareMinor ?? outboundChildUnitFare;
|
||||||
if (!returnFreeChildUsed) { returnFareMinor = 0; returnFreeChildUsed = true; }
|
if (!returnFreeChildUsed) { returnFareMinor = 0; returnFreeChildUsed = true; }
|
||||||
else returnFareMinor = p.returnSeatFareMinor ?? returnChildUnitFare;
|
else returnFareMinor = p.returnSeatFareMinor ?? returnChildUnitFare;
|
||||||
}
|
}
|
||||||
return { ...p, outboundFareMinor, returnFareMinor };
|
return { ...p, outboundFareMinor, returnFareMinor };
|
||||||
});
|
});
|
||||||
@@ -527,69 +528,70 @@ export class GuestBookingService {
|
|||||||
|
|
||||||
// Create booking with outbound seats; return seats confirmed separately
|
// Create booking with outbound seats; return seats confirmed separately
|
||||||
const outboundSeatIds = dto.passengers.map(p => p.seatId).filter((id): id is string => !!id);
|
const outboundSeatIds = dto.passengers.map(p => p.seatId).filter((id): id is string => !!id);
|
||||||
const returnSeatIds = dto.passengers.map(p => p.returnSeatId!);
|
const returnSeatIds = dto.passengers.map(p => p.returnSeatId!);
|
||||||
|
|
||||||
const booking = await this.prisma.booking.create({
|
const booking = await this.prisma.booking.create({
|
||||||
data: {
|
data: {
|
||||||
bookingRef: generateRef(),
|
bookingRef: generateRef(),
|
||||||
passengerId: guestPassengerId,
|
passengerId: guestPassengerId,
|
||||||
scheduleId: dto.scheduleId,
|
scheduleId: dto.scheduleId,
|
||||||
originStationId: dto.originStationId,
|
originStationId: dto.originStationId,
|
||||||
destinationStationId: dto.destinationStationId,
|
destinationStationId: dto.destinationStationId,
|
||||||
status: 'PENDING_PAYMENT',
|
status: 'PENDING_PAYMENT',
|
||||||
bookingType: 'ROUND_TRIP',
|
bookingType: 'ROUND_TRIP',
|
||||||
totalMinor,
|
totalMinor,
|
||||||
|
currency: displayCurrency,
|
||||||
adultCount,
|
adultCount,
|
||||||
childCount,
|
childCount,
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
displayTotalMinor,
|
displayTotalMinor,
|
||||||
returnScheduleId: dto.returnScheduleId,
|
returnScheduleId: dto.returnScheduleId,
|
||||||
returnOriginStationId: dto.returnOriginStationId,
|
returnOriginStationId: dto.returnOriginStationId,
|
||||||
returnDestinationStationId: dto.returnDestinationStationId,
|
returnDestinationStationId: dto.returnDestinationStationId,
|
||||||
returnHoldId: dto.returnHoldId,
|
returnHoldId: dto.returnHoldId,
|
||||||
returnSeatClassId,
|
returnSeatClassId,
|
||||||
returnLegStatus: 'NEITHER_USED',
|
returnLegStatus: 'NEITHER_USED',
|
||||||
...(dto.packageId ? { packageId: dto.packageId, priceTierId: dto.priceTierId } : {}),
|
...(dto.packageId ? { packageId: dto.packageId, priceTierId: dto.priceTierId } : {}),
|
||||||
userAgent: dto.deviceId,
|
userAgent: dto.deviceId,
|
||||||
contactEmail: passengersData[0]?.email || null,
|
contactEmail: passengersData[0]?.email || null,
|
||||||
contactPhone: passengersData[0]?.phone || null,
|
contactPhone: passengersData[0]?.phone || null,
|
||||||
seats: {
|
seats: {
|
||||||
create: [
|
create: [
|
||||||
...passengersWithFares.map((p) => ({
|
...passengersWithFares.map((p) => ({
|
||||||
seat: { connect: { id: p.seatId } },
|
seat: { connect: { id: p.seatId } },
|
||||||
leg: 1,
|
leg: 1,
|
||||||
scheduleId: dto.scheduleId,
|
scheduleId: dto.scheduleId,
|
||||||
passengerName: p.passengerName,
|
passengerName: p.passengerName,
|
||||||
dateOfBirth: p.dateOfBirth,
|
dateOfBirth: p.dateOfBirth,
|
||||||
passengerCategory: p.category,
|
passengerCategory: p.category,
|
||||||
idDocumentType: p.idDocumentType,
|
idDocumentType: p.idDocumentType,
|
||||||
passportNumber: p.passportNumber,
|
passportNumber: p.passportNumber,
|
||||||
passportCountry: p.passportCountry,
|
passportCountry: p.passportCountry,
|
||||||
verifaydaVerified: p.verifaydaVerified,
|
verifaydaVerified: p.verifaydaVerified,
|
||||||
verifaydaData: p.verifaydaData || undefined,
|
verifaydaData: p.verifaydaData || undefined,
|
||||||
fareMinor: p.outboundFareMinor,
|
fareMinor: p.outboundFareMinor,
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
})),
|
})),
|
||||||
...passengersWithFares.map((p) => ({
|
...passengersWithFares.map((p) => ({
|
||||||
seat: { connect: { id: p.returnSeatId } },
|
seat: { connect: { id: p.returnSeatId } },
|
||||||
leg: 2,
|
leg: 2,
|
||||||
scheduleId: dto.returnScheduleId,
|
scheduleId: dto.returnScheduleId,
|
||||||
passengerName: p.passengerName,
|
passengerName: p.passengerName,
|
||||||
dateOfBirth: p.dateOfBirth,
|
dateOfBirth: p.dateOfBirth,
|
||||||
passengerCategory: p.category,
|
passengerCategory: p.category,
|
||||||
idDocumentType: p.idDocumentType,
|
idDocumentType: p.idDocumentType,
|
||||||
passportNumber: p.passportNumber,
|
passportNumber: p.passportNumber,
|
||||||
passportCountry: p.passportCountry,
|
passportCountry: p.passportCountry,
|
||||||
verifaydaVerified: p.verifaydaVerified,
|
verifaydaVerified: p.verifaydaVerified,
|
||||||
verifaydaData: p.verifaydaData || undefined,
|
verifaydaData: p.verifaydaData || undefined,
|
||||||
fareMinor: p.returnFareMinor,
|
fareMinor: p.returnFareMinor,
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
} as any,
|
} as any,
|
||||||
include: {
|
include: {
|
||||||
seats: { include: { seat: { include: { coach: true } } } },
|
seats: { include: { seat: { include: { coach: true } } } },
|
||||||
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -608,16 +610,16 @@ export class GuestBookingService {
|
|||||||
iamUserId,
|
iamUserId,
|
||||||
fareBreakdown: {
|
fareBreakdown: {
|
||||||
outboundBaseFareMinor: outboundBaseFare,
|
outboundBaseFareMinor: outboundBaseFare,
|
||||||
returnBaseFareMinor: returnBaseFare,
|
returnBaseFareMinor: returnBaseFare,
|
||||||
adultCount,
|
adultCount,
|
||||||
childCount,
|
childCount,
|
||||||
freeChildrenCount: isPackageRoundTrip ? 0 : Math.min(childCount, 1),
|
freeChildrenCount: isPackageRoundTrip ? 0 : Math.min(childCount, 1),
|
||||||
paidChildrenCount,
|
paidChildrenCount,
|
||||||
combinedBaseFareMinor,
|
combinedBaseFareMinor,
|
||||||
discountMinor,
|
discountMinor,
|
||||||
taxesFeesMinor: taxesMinor,
|
taxesFeesMinor: taxesMinor,
|
||||||
totalMinor,
|
totalMinor,
|
||||||
currency: 'ETB',
|
currency: displayCurrency,
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
displayTotalMinor,
|
displayTotalMinor,
|
||||||
},
|
},
|
||||||
@@ -658,9 +660,9 @@ export class GuestBookingService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const leg1OriginStop = leg1Schedule.stopTimes.find(s => s.stationId === dto.originStationId);
|
const leg1OriginStop = leg1Schedule.stopTimes.find(s => s.stationId === dto.originStationId);
|
||||||
const leg1DestStop = leg1Schedule.stopTimes.find(s => s.stationId === dto.transitStationId);
|
const leg1DestStop = leg1Schedule.stopTimes.find(s => s.stationId === dto.transitStationId);
|
||||||
const leg2OriginStop = leg2Schedule.stopTimes.find(s => s.stationId === dto.transitStationId);
|
const leg2OriginStop = leg2Schedule.stopTimes.find(s => s.stationId === dto.transitStationId);
|
||||||
const leg2DestStop = leg2Schedule.stopTimes.find(s => s.stationId === dto.leg2DestinationStationId);
|
const leg2DestStop = leg2Schedule.stopTimes.find(s => s.stationId === dto.leg2DestinationStationId);
|
||||||
if (!leg1OriginStop || !leg1DestStop) throw new NotFoundException('Leg-1 origin or transit station not found on schedule');
|
if (!leg1OriginStop || !leg1DestStop) throw new NotFoundException('Leg-1 origin or transit station not found on schedule');
|
||||||
if (!leg2OriginStop || !leg2DestStop) throw new NotFoundException('Transit or leg-2 destination not found on leg-2 schedule');
|
if (!leg2OriginStop || !leg2DestStop) throw new NotFoundException('Transit or leg-2 destination not found on leg-2 schedule');
|
||||||
|
|
||||||
@@ -695,9 +697,9 @@ export class GuestBookingService {
|
|||||||
passengersData.push({ ...passenger, passengerName, dateOfBirth, category, verifaydaVerified, verifaydaData, nationality });
|
passengersData.push({ ...passenger, passengerName, dateOfBirth, category, verifaydaVerified, verifaydaData, nationality });
|
||||||
}
|
}
|
||||||
|
|
||||||
const leg2SeatClassId = dto.leg2SeatClassId || dto.seatClassId;
|
const leg2SeatClassId = dto.leg2SeatClassId || dto.seatClassId;
|
||||||
const primaryNationality = passengersData[0]?.nationality;
|
const primaryNationality = passengersData[0]?.nationality;
|
||||||
const paidChildrenCount = Math.max(0, childCount - 1);
|
const paidChildrenCount = Math.max(0, childCount - 1);
|
||||||
|
|
||||||
const [leg1BaseFare, leg2BaseFare] = await Promise.all([
|
const [leg1BaseFare, leg2BaseFare] = await Promise.all([
|
||||||
this.getBaseFare(dto.scheduleId, dto.seatClassId,
|
this.getBaseFare(dto.scheduleId, dto.seatClassId,
|
||||||
@@ -710,9 +712,9 @@ export class GuestBookingService {
|
|||||||
primaryNationality, dto.transitStationId, dto.leg2DestinationStationId),
|
primaryNationality, dto.transitStationId, dto.leg2DestinationStationId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const leg1Total = leg1BaseFare * adultCount + leg1BaseFare * paidChildrenCount;
|
const leg1Total = leg1BaseFare * adultCount + leg1BaseFare * paidChildrenCount;
|
||||||
const leg2Total = leg2BaseFare * adultCount + leg2BaseFare * paidChildrenCount;
|
const leg2Total = leg2BaseFare * adultCount + leg2BaseFare * paidChildrenCount;
|
||||||
const combinedBase = leg1Total + leg2Total;
|
const combinedBase = leg1Total + leg2Total;
|
||||||
|
|
||||||
let discountMinor = 0;
|
let discountMinor = 0;
|
||||||
if (dto.promoCode) {
|
if (dto.promoCode) {
|
||||||
@@ -734,62 +736,63 @@ export class GuestBookingService {
|
|||||||
// Single booking — leg-1 seats at leg=1, leg-2 seats at leg=2
|
// Single booking — leg-1 seats at leg=1, leg-2 seats at leg=2
|
||||||
const booking = await this.prisma.booking.create({
|
const booking = await this.prisma.booking.create({
|
||||||
data: {
|
data: {
|
||||||
bookingRef: generateRef(),
|
bookingRef: generateRef(),
|
||||||
passengerId: guestPassengerId,
|
passengerId: guestPassengerId,
|
||||||
scheduleId: dto.scheduleId,
|
scheduleId: dto.scheduleId,
|
||||||
originStationId: dto.originStationId,
|
originStationId: dto.originStationId,
|
||||||
destinationStationId: dto.leg2DestinationStationId,
|
destinationStationId: dto.leg2DestinationStationId,
|
||||||
status: 'PENDING_PAYMENT',
|
status: 'PENDING_PAYMENT',
|
||||||
bookingType: 'TRANSIT',
|
bookingType: 'TRANSIT',
|
||||||
totalMinor,
|
totalMinor,
|
||||||
|
currency: displayCurrency,
|
||||||
adultCount,
|
adultCount,
|
||||||
childCount,
|
childCount,
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
displayTotalMinor,
|
displayTotalMinor,
|
||||||
leg2ScheduleId: dto.leg2ScheduleId,
|
leg2ScheduleId: dto.leg2ScheduleId,
|
||||||
leg2OriginStationId: dto.transitStationId,
|
leg2OriginStationId: dto.transitStationId,
|
||||||
leg2DestinationStationId: dto.leg2DestinationStationId,
|
leg2DestinationStationId: dto.leg2DestinationStationId,
|
||||||
leg2SeatClassId: leg2SeatClassId,
|
leg2SeatClassId: leg2SeatClassId,
|
||||||
userAgent: dto.deviceId,
|
userAgent: dto.deviceId,
|
||||||
contactEmail: passengersData[0]?.email || null,
|
contactEmail: passengersData[0]?.email || null,
|
||||||
contactPhone: passengersData[0]?.phone || null,
|
contactPhone: passengersData[0]?.phone || null,
|
||||||
seats: {
|
seats: {
|
||||||
create: [
|
create: [
|
||||||
...passengersData.map(p => ({
|
...passengersData.map(p => ({
|
||||||
seat: { connect: { id: p.seatId } },
|
seat: { connect: { id: p.seatId } },
|
||||||
leg: 1,
|
leg: 1,
|
||||||
scheduleId: dto.scheduleId,
|
scheduleId: dto.scheduleId,
|
||||||
passengerName: p.passengerName,
|
passengerName: p.passengerName,
|
||||||
dateOfBirth: p.dateOfBirth,
|
dateOfBirth: p.dateOfBirth,
|
||||||
passengerCategory: p.category,
|
passengerCategory: p.category,
|
||||||
idDocumentType: p.idDocumentType,
|
idDocumentType: p.idDocumentType,
|
||||||
passportNumber: p.passportNumber,
|
passportNumber: p.passportNumber,
|
||||||
passportCountry: p.passportCountry,
|
passportCountry: p.passportCountry,
|
||||||
verifaydaVerified: p.verifaydaVerified,
|
verifaydaVerified: p.verifaydaVerified,
|
||||||
verifaydaData: p.verifaydaData || undefined,
|
verifaydaData: p.verifaydaData || undefined,
|
||||||
fareMinor: p.category === PassengerCategory.ADULT ? leg1BaseFare : (paidChildrenCount > 0 ? leg1BaseFare : 0),
|
fareMinor: p.category === PassengerCategory.ADULT ? leg1BaseFare : (paidChildrenCount > 0 ? leg1BaseFare : 0),
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
})),
|
})),
|
||||||
...passengersData.map(p => ({
|
...passengersData.map(p => ({
|
||||||
seat: { connect: { id: p.leg2SeatId! } },
|
seat: { connect: { id: p.leg2SeatId! } },
|
||||||
leg: 2,
|
leg: 2,
|
||||||
scheduleId: dto.leg2ScheduleId,
|
scheduleId: dto.leg2ScheduleId,
|
||||||
passengerName: p.passengerName,
|
passengerName: p.passengerName,
|
||||||
dateOfBirth: p.dateOfBirth,
|
dateOfBirth: p.dateOfBirth,
|
||||||
passengerCategory: p.category,
|
passengerCategory: p.category,
|
||||||
idDocumentType: p.idDocumentType,
|
idDocumentType: p.idDocumentType,
|
||||||
passportNumber: p.passportNumber,
|
passportNumber: p.passportNumber,
|
||||||
passportCountry: p.passportCountry,
|
passportCountry: p.passportCountry,
|
||||||
verifaydaVerified: p.verifaydaVerified,
|
verifaydaVerified: p.verifaydaVerified,
|
||||||
verifaydaData: p.verifaydaData || undefined,
|
verifaydaData: p.verifaydaData || undefined,
|
||||||
fareMinor: p.category === PassengerCategory.ADULT ? leg2BaseFare : (paidChildrenCount > 0 ? leg2BaseFare : 0),
|
fareMinor: p.category === PassengerCategory.ADULT ? leg2BaseFare : (paidChildrenCount > 0 ? leg2BaseFare : 0),
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
} as any,
|
} as any,
|
||||||
include: {
|
include: {
|
||||||
seats: { include: { seat: { include: { coach: true } } } },
|
seats: { include: { seat: { include: { coach: true } } } },
|
||||||
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -821,15 +824,15 @@ export class GuestBookingService {
|
|||||||
|
|
||||||
private async createGuestRoundTripTransitBooking(dto: CreateGuestBookingDto, req?: any) {
|
private async createGuestRoundTripTransitBooking(dto: CreateGuestBookingDto, req?: any) {
|
||||||
if (!dto.leg2ScheduleId || !dto.leg2HoldId || !dto.transitStationId || !dto.leg2DestinationStationId ||
|
if (!dto.leg2ScheduleId || !dto.leg2HoldId || !dto.transitStationId || !dto.leg2DestinationStationId ||
|
||||||
!dto.returnScheduleId || !dto.returnHoldId || !dto.returnOriginStationId || !dto.returnDestinationStationId ||
|
!dto.returnScheduleId || !dto.returnHoldId || !dto.returnOriginStationId || !dto.returnDestinationStationId ||
|
||||||
!dto.returnLeg2ScheduleId || !dto.returnLeg2HoldId || !dto.returnTransitStationId || !dto.returnLeg2DestinationStationId) {
|
!dto.returnLeg2ScheduleId || !dto.returnLeg2HoldId || !dto.returnTransitStationId || !dto.returnLeg2DestinationStationId) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
'ROUND_TRIP_TRANSIT requires all 4 holds and all transit/return station fields',
|
'ROUND_TRIP_TRANSIT requires all 4 holds and all transit/return station fields',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
for (const p of dto.passengers) {
|
for (const p of dto.passengers) {
|
||||||
if (!p.leg2SeatId) throw new BadRequestException(`leg2SeatId required for ${p.passengerName}`);
|
if (!p.leg2SeatId) throw new BadRequestException(`leg2SeatId required for ${p.passengerName}`);
|
||||||
if (!p.returnSeatId) throw new BadRequestException(`returnSeatId required for ${p.passengerName}`);
|
if (!p.returnSeatId) throw new BadRequestException(`returnSeatId required for ${p.passengerName}`);
|
||||||
if (!p.returnLeg2SeatId) throw new BadRequestException(`returnLeg2SeatId required for ${p.passengerName}`);
|
if (!p.returnLeg2SeatId) throw new BadRequestException(`returnLeg2SeatId required for ${p.passengerName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -840,19 +843,19 @@ export class GuestBookingService {
|
|||||||
this.prisma.seatHold.findUnique({ where: { id: dto.returnHoldId } }),
|
this.prisma.seatHold.findUnique({ where: { id: dto.returnHoldId } }),
|
||||||
this.prisma.seatHold.findUnique({ where: { id: dto.returnLeg2HoldId } }),
|
this.prisma.seatHold.findUnique({ where: { id: dto.returnLeg2HoldId } }),
|
||||||
]);
|
]);
|
||||||
if (!obL1Hold || obL1Hold.expiresAt < now) throw new BadRequestException('Outbound leg-1 hold expired');
|
if (!obL1Hold || obL1Hold.expiresAt < now) throw new BadRequestException('Outbound leg-1 hold expired');
|
||||||
if (!obL2Hold || obL2Hold.expiresAt < now) throw new BadRequestException('Outbound leg-2 hold expired');
|
if (!obL2Hold || obL2Hold.expiresAt < now) throw new BadRequestException('Outbound leg-2 hold expired');
|
||||||
if (!retL1Hold || retL1Hold.expiresAt < now) throw new BadRequestException('Return leg-1 hold expired');
|
if (!retL1Hold || retL1Hold.expiresAt < now) throw new BadRequestException('Return leg-1 hold expired');
|
||||||
if (!retL2Hold || retL2Hold.expiresAt < now) throw new BadRequestException('Return leg-2 hold expired');
|
if (!retL2Hold || retL2Hold.expiresAt < now) throw new BadRequestException('Return leg-2 hold expired');
|
||||||
|
|
||||||
const [obL1Sched, obL2Sched, retL1Sched, retL2Sched] = await Promise.all([
|
const [obL1Sched, obL2Sched, retL1Sched, retL2Sched] = await Promise.all([
|
||||||
this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
this.prisma.trainSchedule.findUnique({ where: { id: dto.scheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
||||||
this.prisma.trainSchedule.findUnique({ where: { id: dto.leg2ScheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
this.prisma.trainSchedule.findUnique({ where: { id: dto.leg2ScheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
||||||
this.prisma.trainSchedule.findUnique({ where: { id: dto.returnScheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
this.prisma.trainSchedule.findUnique({ where: { id: dto.returnScheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
||||||
this.prisma.trainSchedule.findUnique({ where: { id: dto.returnLeg2ScheduleId },include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
this.prisma.trainSchedule.findUnique({ where: { id: dto.returnLeg2ScheduleId }, include: { originStation: true, destinationStation: true, stopTimes: { include: { station: true }, orderBy: { sequence: 'asc' } } } }),
|
||||||
]);
|
]);
|
||||||
if (!obL1Sched) throw new NotFoundException('Outbound leg-1 schedule not found');
|
if (!obL1Sched) throw new NotFoundException('Outbound leg-1 schedule not found');
|
||||||
if (!obL2Sched) throw new NotFoundException('Outbound leg-2 schedule not found');
|
if (!obL2Sched) throw new NotFoundException('Outbound leg-2 schedule not found');
|
||||||
if (!retL1Sched) throw new NotFoundException('Return leg-1 schedule not found');
|
if (!retL1Sched) throw new NotFoundException('Return leg-1 schedule not found');
|
||||||
if (!retL2Sched) throw new NotFoundException('Return leg-2 schedule not found');
|
if (!retL2Sched) throw new NotFoundException('Return leg-2 schedule not found');
|
||||||
|
|
||||||
@@ -860,16 +863,16 @@ export class GuestBookingService {
|
|||||||
throw new BadRequestException('Bookings are not accepted within 30 minutes of departure');
|
throw new BadRequestException('Bookings are not accepted within 30 minutes of departure');
|
||||||
}
|
}
|
||||||
|
|
||||||
const obL1Origin = obL1Sched.stopTimes.find(s => s.stationId === dto.originStationId);
|
const obL1Origin = obL1Sched.stopTimes.find(s => s.stationId === dto.originStationId);
|
||||||
const obL1Dest = obL1Sched.stopTimes.find(s => s.stationId === dto.transitStationId);
|
const obL1Dest = obL1Sched.stopTimes.find(s => s.stationId === dto.transitStationId);
|
||||||
const obL2Origin = obL2Sched.stopTimes.find(s => s.stationId === dto.transitStationId);
|
const obL2Origin = obL2Sched.stopTimes.find(s => s.stationId === dto.transitStationId);
|
||||||
const obL2Dest = obL2Sched.stopTimes.find(s => s.stationId === dto.leg2DestinationStationId);
|
const obL2Dest = obL2Sched.stopTimes.find(s => s.stationId === dto.leg2DestinationStationId);
|
||||||
const retL1Origin = retL1Sched.stopTimes.find(s => s.stationId === dto.returnOriginStationId);
|
const retL1Origin = retL1Sched.stopTimes.find(s => s.stationId === dto.returnOriginStationId);
|
||||||
const retL1Dest = retL1Sched.stopTimes.find(s => s.stationId === dto.returnTransitStationId);
|
const retL1Dest = retL1Sched.stopTimes.find(s => s.stationId === dto.returnTransitStationId);
|
||||||
const retL2Origin = retL2Sched.stopTimes.find(s => s.stationId === dto.returnTransitStationId);
|
const retL2Origin = retL2Sched.stopTimes.find(s => s.stationId === dto.returnTransitStationId);
|
||||||
const retL2Dest = retL2Sched.stopTimes.find(s => s.stationId === dto.returnLeg2DestinationStationId);
|
const retL2Dest = retL2Sched.stopTimes.find(s => s.stationId === dto.returnLeg2DestinationStationId);
|
||||||
if (!obL1Origin || !obL1Dest) throw new NotFoundException('Outbound leg-1: origin or transit stop not found');
|
if (!obL1Origin || !obL1Dest) throw new NotFoundException('Outbound leg-1: origin or transit stop not found');
|
||||||
if (!obL2Origin || !obL2Dest) throw new NotFoundException('Outbound leg-2: transit or destination stop not found');
|
if (!obL2Origin || !obL2Dest) throw new NotFoundException('Outbound leg-2: transit or destination stop not found');
|
||||||
if (!retL1Origin || !retL1Dest) throw new NotFoundException('Return leg-1: origin or transit stop not found');
|
if (!retL1Origin || !retL1Dest) throw new NotFoundException('Return leg-1: origin or transit stop not found');
|
||||||
if (!retL2Origin || !retL2Dest) throw new NotFoundException('Return leg-2: transit or destination stop not found');
|
if (!retL2Origin || !retL2Dest) throw new NotFoundException('Return leg-2: transit or destination stop not found');
|
||||||
|
|
||||||
@@ -901,21 +904,21 @@ export class GuestBookingService {
|
|||||||
passengersData.push({ ...passenger, passengerName, dateOfBirth, category, verifaydaVerified, verifaydaData, nationality });
|
passengersData.push({ ...passenger, passengerName, dateOfBirth, category, verifaydaVerified, verifaydaData, nationality });
|
||||||
}
|
}
|
||||||
|
|
||||||
const nat = passengersData[0]?.nationality;
|
const nat = passengersData[0]?.nationality;
|
||||||
const paidChildren = Math.max(0, childCount - 1);
|
const paidChildren = Math.max(0, childCount - 1);
|
||||||
const obL2ClassId = dto.leg2SeatClassId ?? dto.seatClassId;
|
const obL2ClassId = dto.leg2SeatClassId ?? dto.seatClassId;
|
||||||
const retL1ClassId = dto.returnSeatClassId ?? dto.seatClassId;
|
const retL1ClassId = dto.returnSeatClassId ?? dto.seatClassId;
|
||||||
const retL2ClassId = dto.returnLeg2SeatClassId ?? dto.seatClassId;
|
const retL2ClassId = dto.returnLeg2SeatClassId ?? dto.seatClassId;
|
||||||
|
|
||||||
const [obL1Fare, obL2Fare, retL1Fare, retL2Fare] = await Promise.all([
|
const [obL1Fare, obL2Fare, retL1Fare, retL2Fare] = await Promise.all([
|
||||||
this.getBaseFare(dto.scheduleId, dto.seatClassId, `${obL1Origin.station.code}-${obL1Dest.station.code}`, `${obL1Sched.originStation.code}-${obL1Sched.destinationStation.code}`, nat, dto.originStationId, dto.transitStationId),
|
this.getBaseFare(dto.scheduleId, dto.seatClassId, `${obL1Origin.station.code}-${obL1Dest.station.code}`, `${obL1Sched.originStation.code}-${obL1Sched.destinationStation.code}`, nat, dto.originStationId, dto.transitStationId),
|
||||||
this.getBaseFare(dto.leg2ScheduleId!, obL2ClassId, `${obL2Origin.station.code}-${obL2Dest.station.code}`, `${obL2Sched.originStation.code}-${obL2Sched.destinationStation.code}`, nat, dto.transitStationId, dto.leg2DestinationStationId),
|
this.getBaseFare(dto.leg2ScheduleId!, obL2ClassId, `${obL2Origin.station.code}-${obL2Dest.station.code}`, `${obL2Sched.originStation.code}-${obL2Sched.destinationStation.code}`, nat, dto.transitStationId, dto.leg2DestinationStationId),
|
||||||
this.getBaseFare(dto.returnScheduleId!, retL1ClassId, `${retL1Origin.station.code}-${retL1Dest.station.code}`, `${retL1Sched.originStation.code}-${retL1Sched.destinationStation.code}`, nat, dto.returnOriginStationId, dto.returnTransitStationId),
|
this.getBaseFare(dto.returnScheduleId!, retL1ClassId, `${retL1Origin.station.code}-${retL1Dest.station.code}`, `${retL1Sched.originStation.code}-${retL1Sched.destinationStation.code}`, nat, dto.returnOriginStationId, dto.returnTransitStationId),
|
||||||
this.getBaseFare(dto.returnLeg2ScheduleId!,retL2ClassId, `${retL2Origin.station.code}-${retL2Dest.station.code}`, `${retL2Sched.originStation.code}-${retL2Sched.destinationStation.code}`, nat, dto.returnTransitStationId, dto.returnLeg2DestinationStationId),
|
this.getBaseFare(dto.returnLeg2ScheduleId!, retL2ClassId, `${retL2Origin.station.code}-${retL2Dest.station.code}`, `${retL2Sched.originStation.code}-${retL2Sched.destinationStation.code}`, nat, dto.returnTransitStationId, dto.returnLeg2DestinationStationId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const combinedBase = (obL1Fare + obL2Fare + retL1Fare + retL2Fare) * adultCount +
|
const combinedBase = (obL1Fare + obL2Fare + retL1Fare + retL2Fare) * adultCount +
|
||||||
(obL1Fare + obL2Fare + retL1Fare + retL2Fare) * paidChildren;
|
(obL1Fare + obL2Fare + retL1Fare + retL2Fare) * paidChildren;
|
||||||
let discountMinor = 0;
|
let discountMinor = 0;
|
||||||
if (dto.promoCode) {
|
if (dto.promoCode) {
|
||||||
const promo = await this.prisma.promotion.findUnique({ where: { code: dto.promoCode } });
|
const promo = await this.prisma.promotion.findUnique({ where: { code: dto.promoCode } });
|
||||||
@@ -923,8 +926,8 @@ export class GuestBookingService {
|
|||||||
discountMinor = promo.percentOff ? Math.round(combinedBase * promo.percentOff / 100) : (promo.amountOffMinor ?? 0);
|
discountMinor = promo.percentOff ? Math.round(combinedBase * promo.percentOff / 100) : (promo.amountOffMinor ?? 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const taxesMinor = Math.round(combinedBase * 0.05);
|
const taxesMinor = Math.round(combinedBase * 0.05);
|
||||||
const totalMinor = Math.max(0, combinedBase - discountMinor + taxesMinor);
|
const totalMinor = Math.max(0, combinedBase - discountMinor + taxesMinor);
|
||||||
const displayCurrency = dto.displayCurrency || Currency.ETB;
|
const displayCurrency = dto.displayCurrency || Currency.ETB;
|
||||||
const displayTotalMinor = displayCurrency !== Currency.ETB
|
const displayTotalMinor = displayCurrency !== Currency.ETB
|
||||||
? await this.currencyService.convertAmount(totalMinor, Currency.ETB, displayCurrency)
|
? await this.currencyService.convertAmount(totalMinor, Currency.ETB, displayCurrency)
|
||||||
@@ -933,58 +936,58 @@ export class GuestBookingService {
|
|||||||
const { guestPassengerId, iamUserId, createdAccount } = await this.resolveGuestPassenger(dto, passengersData[0], req);
|
const { guestPassengerId, iamUserId, createdAccount } = await this.resolveGuestPassenger(dto, passengersData[0], req);
|
||||||
|
|
||||||
const makeSeat = (p: any, seatId: string, leg: number, scheduleId: string, fare: number) => ({
|
const makeSeat = (p: any, seatId: string, leg: number, scheduleId: string, fare: number) => ({
|
||||||
seat: { connect: { id: seatId } },
|
seat: { connect: { id: seatId } },
|
||||||
leg,
|
leg,
|
||||||
scheduleId,
|
scheduleId,
|
||||||
passengerName: p.passengerName,
|
passengerName: p.passengerName,
|
||||||
dateOfBirth: p.dateOfBirth,
|
dateOfBirth: p.dateOfBirth,
|
||||||
passengerCategory: p.category,
|
passengerCategory: p.category,
|
||||||
idDocumentType: p.idDocumentType,
|
idDocumentType: p.idDocumentType,
|
||||||
passportNumber: p.passportNumber,
|
passportNumber: p.passportNumber,
|
||||||
passportCountry: p.passportCountry,
|
passportCountry: p.passportCountry,
|
||||||
verifaydaVerified: p.verifaydaVerified,
|
verifaydaVerified: p.verifaydaVerified,
|
||||||
verifaydaData: p.verifaydaData || undefined,
|
verifaydaData: p.verifaydaData || undefined,
|
||||||
fareMinor: p.category === PassengerCategory.ADULT ? fare : (paidChildren > 0 ? fare : 0),
|
fareMinor: p.category === PassengerCategory.ADULT ? fare : (paidChildren > 0 ? fare : 0),
|
||||||
displayCurrency,
|
displayCurrency,
|
||||||
});
|
});
|
||||||
|
|
||||||
const booking = await this.prisma.booking.create({
|
const booking = await this.prisma.booking.create({
|
||||||
data: {
|
data: {
|
||||||
bookingRef: generateRef(),
|
bookingRef: generateRef(),
|
||||||
passengerId: guestPassengerId,
|
passengerId: guestPassengerId,
|
||||||
scheduleId: dto.scheduleId,
|
scheduleId: dto.scheduleId,
|
||||||
originStationId: dto.originStationId,
|
originStationId: dto.originStationId,
|
||||||
destinationStationId: dto.returnLeg2DestinationStationId,
|
destinationStationId: dto.returnLeg2DestinationStationId,
|
||||||
status: 'PENDING_PAYMENT',
|
status: 'PENDING_PAYMENT',
|
||||||
bookingType: 'ROUND_TRIP_TRANSIT',
|
bookingType: 'ROUND_TRIP_TRANSIT',
|
||||||
totalMinor, adultCount, childCount, displayCurrency, displayTotalMinor,
|
totalMinor, currency: displayCurrency, adultCount, childCount, displayCurrency, displayTotalMinor,
|
||||||
leg2ScheduleId: dto.leg2ScheduleId,
|
leg2ScheduleId: dto.leg2ScheduleId,
|
||||||
leg2OriginStationId: dto.transitStationId,
|
leg2OriginStationId: dto.transitStationId,
|
||||||
leg2DestinationStationId: dto.leg2DestinationStationId,
|
leg2DestinationStationId: dto.leg2DestinationStationId,
|
||||||
leg2SeatClassId: obL2ClassId,
|
leg2SeatClassId: obL2ClassId,
|
||||||
returnScheduleId: dto.returnScheduleId,
|
returnScheduleId: dto.returnScheduleId,
|
||||||
returnOriginStationId: dto.returnOriginStationId,
|
returnOriginStationId: dto.returnOriginStationId,
|
||||||
returnDestinationStationId: dto.returnDestinationStationId,
|
returnDestinationStationId: dto.returnDestinationStationId,
|
||||||
returnSeatClassId: retL1ClassId,
|
returnSeatClassId: retL1ClassId,
|
||||||
returnLeg2ScheduleId: dto.returnLeg2ScheduleId,
|
returnLeg2ScheduleId: dto.returnLeg2ScheduleId,
|
||||||
returnLeg2OriginStationId: dto.returnTransitStationId,
|
returnLeg2OriginStationId: dto.returnTransitStationId,
|
||||||
returnLeg2DestStationId: dto.returnLeg2DestinationStationId,
|
returnLeg2DestStationId: dto.returnLeg2DestinationStationId,
|
||||||
returnLeg2SeatClassId: retL2ClassId,
|
returnLeg2SeatClassId: retL2ClassId,
|
||||||
returnLegStatus: 'NEITHER_USED',
|
returnLegStatus: 'NEITHER_USED',
|
||||||
userAgent: dto.deviceId,
|
userAgent: dto.deviceId,
|
||||||
contactEmail: passengersData[0]?.email || null,
|
contactEmail: passengersData[0]?.email || null,
|
||||||
contactPhone: passengersData[0]?.phone || null,
|
contactPhone: passengersData[0]?.phone || null,
|
||||||
seats: {
|
seats: {
|
||||||
create: [
|
create: [
|
||||||
...passengersData.map(p => makeSeat(p, p.seatId, 1, dto.scheduleId, obL1Fare)),
|
...passengersData.map(p => makeSeat(p, p.seatId, 1, dto.scheduleId, obL1Fare)),
|
||||||
...passengersData.map(p => makeSeat(p, p.leg2SeatId!, 2, dto.leg2ScheduleId!, obL2Fare)),
|
...passengersData.map(p => makeSeat(p, p.leg2SeatId!, 2, dto.leg2ScheduleId!, obL2Fare)),
|
||||||
...passengersData.map(p => makeSeat(p, p.returnSeatId!, 3, dto.returnScheduleId!, retL1Fare)),
|
...passengersData.map(p => makeSeat(p, p.returnSeatId!, 3, dto.returnScheduleId!, retL1Fare)),
|
||||||
...passengersData.map(p => makeSeat(p, p.returnLeg2SeatId!,4, dto.returnLeg2ScheduleId!,retL2Fare)),
|
...passengersData.map(p => makeSeat(p, p.returnLeg2SeatId!, 4, dto.returnLeg2ScheduleId!, retL2Fare)),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
} as any,
|
} as any,
|
||||||
include: {
|
include: {
|
||||||
seats: { include: { seat: { include: { coach: true } } } },
|
seats: { include: { seat: { include: { coach: true } } } },
|
||||||
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -1006,14 +1009,14 @@ export class GuestBookingService {
|
|||||||
fareBreakdown: {
|
fareBreakdown: {
|
||||||
outboundLeg1FareMinor: obL1Fare,
|
outboundLeg1FareMinor: obL1Fare,
|
||||||
outboundLeg2FareMinor: obL2Fare,
|
outboundLeg2FareMinor: obL2Fare,
|
||||||
returnLeg1FareMinor: retL1Fare,
|
returnLeg1FareMinor: retL1Fare,
|
||||||
returnLeg2FareMinor: retL2Fare,
|
returnLeg2FareMinor: retL2Fare,
|
||||||
adultCount, childCount,
|
adultCount, childCount,
|
||||||
freeChildrenCount: Math.min(childCount, 1),
|
freeChildrenCount: Math.min(childCount, 1),
|
||||||
paidChildrenCount: paidChildren,
|
paidChildrenCount: paidChildren,
|
||||||
combinedBaseFareMinor: combinedBase,
|
combinedBaseFareMinor: combinedBase,
|
||||||
discountMinor, taxesFeesMinor: taxesMinor, totalMinor,
|
discountMinor, taxesFeesMinor: taxesMinor, totalMinor,
|
||||||
currency: 'ETB', displayCurrency, displayTotalMinor,
|
currency: displayCurrency, displayCurrency, displayTotalMinor,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1042,7 +1045,7 @@ export class GuestBookingService {
|
|||||||
const guestPassenger = await this.prisma.passenger.create({ data: {} });
|
const guestPassenger = await this.prisma.passenger.create({ data: {} });
|
||||||
await this.prisma.loyaltyAccount.create({ data: { passengerId: guestPassenger.id, pointsBalance: 0, tier: 'BRONZE' } });
|
await this.prisma.loyaltyAccount.create({ data: { passengerId: guestPassenger.id, pointsBalance: 0, tier: 'BRONZE' } });
|
||||||
await this.prisma.walletAccount.create({ data: { passengerId: guestPassenger.id, balanceMinor: 0 } });
|
await this.prisma.walletAccount.create({ data: { passengerId: guestPassenger.id, balanceMinor: 0 } });
|
||||||
|
|
||||||
return { guestPassengerId: guestPassenger.id, iamUserId: null, createdAccount: false };
|
return { guestPassengerId: guestPassenger.id, iamUserId: null, createdAccount: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1052,7 +1055,7 @@ export class GuestBookingService {
|
|||||||
if (passenger.verifaydaData && typeof passenger.verifaydaData === 'object') {
|
if (passenger.verifaydaData && typeof passenger.verifaydaData === 'object') {
|
||||||
gender = passenger.verifaydaData.gender || passenger.verifaydaData.Gender || null;
|
gender = passenger.verifaydaData.gender || passenger.verifaydaData.Gender || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.prisma.travelerProfile.create({
|
await this.prisma.travelerProfile.create({
|
||||||
data: {
|
data: {
|
||||||
passengerId,
|
passengerId,
|
||||||
@@ -1130,8 +1133,8 @@ export class GuestBookingService {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const premiumMinor = seatClass?.premiumMinor ?? 0;
|
const premiumMinor = seatClass?.premiumMinor ?? 0;
|
||||||
const insuranceMinor = seatClass?.insuranceFeeMinor ?? 0;
|
const insuranceMinor = seatClass?.insuranceFeeMinor ?? 0;
|
||||||
|
|
||||||
const priorities = [
|
const priorities = [
|
||||||
{ tripId: scheduleId, route: segmentRoute, nationality },
|
{ tripId: scheduleId, route: segmentRoute, nationality },
|
||||||
|
|||||||
Reference in New Issue
Block a user