From 222687f4ac80fc81c9dc7a154cf072a4b75d287e Mon Sep 17 00:00:00 2001 From: Stephanos A Date: Tue, 7 Jul 2026 15:46:46 +0300 Subject: [PATCH] Build issues resolution --- apps/edr-passenger-api/src/app.module.ts | 11 ------ .../src/modules/packages/packages.service.ts | 39 ++++++++++++------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/apps/edr-passenger-api/src/app.module.ts b/apps/edr-passenger-api/src/app.module.ts index e552fad8c..1e41076e7 100644 --- a/apps/edr-passenger-api/src/app.module.ts +++ b/apps/edr-passenger-api/src/app.module.ts @@ -64,7 +64,6 @@ import { TasksModule } from './modules/tasks/tasks.module'; import { AppReleasesModule } from './modules/app-releases/app-releases.module'; import { ConfigurableFareModule } from './modules/configurable-fare/configurable-fare.module'; import { SegmentFareSeeder } from './seed/segment-fare.seeder'; -import { EOtpType } from "@tria-plc/iamapi-common"; @Module({ imports: [ @@ -98,16 +97,6 @@ import { EOtpType } from "@tria-plc/iamapi-common"; TriaIamModule.forRoot({ applications: [EDR_PASSENGER_APPLICATION], permissions: EDR_PASSENGER_PERMISSIONS, - otpMessages: { - [EOtpType.MFA_LOGIN]: ({ otp }) => - `Your EDR Passenger login code is ${otp}. It will expire in 5 minutes.`, - [EOtpType.VERIFY_PHONE_NUMBER]: ({ otp }) => - `Your EDR Passenger phone verification code is ${otp}. It will expire in 5 minutes.`, - [EOtpType.RESET_PASSWORD]: ({ route }) => - `Reset your EDR Passenger password using this link: ${route}`, - [EOtpType.SET_PASSWORD]: ({ route }) => - `Set your EDR Passenger password using this link: ${route}`, - }, }), SharedAuthModule, PrismaModule, diff --git a/apps/edr-passenger-api/src/modules/packages/packages.service.ts b/apps/edr-passenger-api/src/modules/packages/packages.service.ts index a474b1edf..916a0425b 100644 --- a/apps/edr-passenger-api/src/modules/packages/packages.service.ts +++ b/apps/edr-passenger-api/src/modules/packages/packages.service.ts @@ -75,7 +75,6 @@ export class PackagesService { const maxChildren = adultCount * PKG_CHILDREN_PER_ADULT; if (childCount > maxChildren) throw new BadRequestException(`Maximum ${PKG_CHILDREN_PER_ADULT} children per adult (${maxChildren} for ${adultCount} adult${adultCount !== 1 ? 's' : ''}) allowed per package booking`); - const remaining = tier.availableSeats - tier.bookedSeats; const isRoundTrip = !!pkg.returnScheduleId; const { adultFareMinor, freeChildren, paidChildren, totalMinor } = calculatePackageFareBreakdown( tier.priceMinor, isRoundTrip, adultCount, childCount, @@ -84,6 +83,10 @@ export class PackagesService { // Only adults and paid children need seats; free children travel without a seat const seatsNeeded = adultCount + paidChildren; const passengerCount = adultCount + childCount; + // Re-fetch tier from DB to get accurate live counts + const liveTier = await this.prisma.packagePriceTier.findUnique({ where: { id: tierId } }); + if (!liveTier) throw new NotFoundException('Price tier not found'); + const remaining = liveTier.availableSeats - liveTier.bookedSeats; if (seatsNeeded > remaining) throw new BadRequestException(`Only ${remaining} seat(s) remaining in the ${tier.label} tier`); @@ -392,25 +395,29 @@ export class PackagesService { if (childCount > maxChildrenBook) throw new BadRequestException(`Maximum ${PKG_CHILDREN_PER_ADULT} children per adult (${maxChildrenBook} for ${adultCount} adult${adultCount !== 1 ? 's' : ''}) allowed per package booking`); const passengerCount = adultCount + childCount; - const remaining = tier.availableSeats - tier.bookedSeats; - const isRoundTrip = !!pkg.returnScheduleId; const { adultFareMinor, freeChildren, paidChildren, totalMinor } = calculatePackageFareBreakdown( tier.priceMinor, isRoundTrip, adultCount, childCount, ); // Only adults and paid children need seats; free children travel without a seat const seatsNeeded = adultCount + paidChildren; - if (seatsNeeded > remaining) { - throw new BadRequestException(`Only ${remaining} seats remaining in the ${tier.label} tier`); - } const displayCurrency = (dto.displayCurrency as Currency) ?? Currency.ETB; const displayTotalMinor = displayCurrency !== Currency.ETB ? await this.currencyService.convertAmount(totalMinor, Currency.ETB, displayCurrency) : totalMinor; - const [booking] = await this.prisma.$transaction([ - this.prisma.packageBooking.create({ + const [booking] = await this.prisma.$transaction(async (tx) => { + // Re-fetch tier inside transaction for race-condition-safe availability check + const freshTier = await tx.packagePriceTier.findUnique({ where: { id: dto.priceTierId } }); + if (!freshTier) throw new NotFoundException('Price tier not found'); + const remaining = freshTier.availableSeats - freshTier.bookedSeats; + if (seatsNeeded > remaining) { + throw new BadRequestException(`Only ${remaining} seats remaining in the ${tier.label} tier`); + } + + return Promise.all([ + tx.packageBooking.create({ data: { bookingRef: generateRef(), packageId: dto.packageId, @@ -448,12 +455,16 @@ export class PackagesService { }, }, }, - }), - this.prisma.packagePriceTier.update({ - where: { id: dto.priceTierId }, - data: { bookedSeats: { increment: seatsNeeded } }, - }), - ]); + }), + tx.packagePriceTier.update({ + where: { id: dto.priceTierId }, + data: { + bookedSeats: { increment: seatsNeeded }, + availableSeats: { decrement: seatsNeeded }, + }, + }), + ]); + }); return { ...booking,