diff --git a/apps/edr-passenger-api/src/modules/bookings/bookings.dto.ts b/apps/edr-passenger-api/src/modules/bookings/bookings.dto.ts index d50f48592..4b355c1fe 100644 --- a/apps/edr-passenger-api/src/modules/bookings/bookings.dto.ts +++ b/apps/edr-passenger-api/src/modules/bookings/bookings.dto.ts @@ -145,7 +145,7 @@ export class CreateBookingDto { @ApiPropertyOptional({ description: 'Package price tier ID — required when packageId is provided' }) @IsOptional() @IsString() priceTierId?: string; - @ApiPropertyOptional({ description: 'Total amount in minor units (ETB) as computed and displayed on the review page. When provided, this overrides the fare engine total — use to pass the exact berth-specific amount the user saw.' }) + @ApiPropertyOptional({ description: 'Total amount in display-currency minor units as computed and displayed on the review page. When displayCurrency is ETB this equals ETB minor units; for DJF/USD it is the converted display amount. The backend uses this directly as displayTotalMinor and back-converts to ETB for storage.' }) @IsOptional() @IsInt() reviewedTotalMinor?: number; @ApiPropertyOptional({ description: 'Promo code for discount (applies to combined fare for round-trip)' }) diff --git a/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts b/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts index 7f09ffa49..4e4bb706f 100644 --- a/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts +++ b/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts @@ -837,16 +837,30 @@ export class BookingsService { // Free children have no seatId and no seatFareMinor — exclude them from the check. const seatedPassengers = passengersData.filter(p => p.seatId); const allFaresProvided = seatedPassengers.length > 0 && seatedPassengers.every(p => p.seatFareMinor != null); - const resolvedTotalMinor = dto.reviewedTotalMinor ?? - (allFaresProvided - ? passengersWithFares.reduce((sum, p) => sum + p.fareMinor, 0) - : fareCalculation.totalMinor); - this.logger.log(`createOneWayBooking: resolvedTotalMinor=${resolvedTotalMinor} (reviewedTotalMinor=${dto.reviewedTotalMinor} allFaresProvided=${allFaresProvided} fareEngine=${fareCalculation.totalMinor})`); - - let displayTotalMinor = resolvedTotalMinor; - if (displayCurrency !== Currency.ETB) { - displayTotalMinor = await this.currencyService.convertAmount(resolvedTotalMinor, Currency.ETB, displayCurrency); + // reviewedTotalMinor is now sent in display-currency minor units from the review page. + // When displayCurrency != ETB, use it directly as displayTotalMinor and back-convert to ETB. + let resolvedTotalMinor: number; + let displayTotalMinor: number; + if (dto.reviewedTotalMinor != null) { + if (displayCurrency !== Currency.ETB) { + displayTotalMinor = dto.reviewedTotalMinor; + resolvedTotalMinor = await this.currencyService.convertAmount(dto.reviewedTotalMinor, displayCurrency, Currency.ETB); + } else { + resolvedTotalMinor = dto.reviewedTotalMinor; + displayTotalMinor = dto.reviewedTotalMinor; + } + } else if (allFaresProvided) { + resolvedTotalMinor = passengersWithFares.reduce((sum, p) => sum + p.fareMinor, 0); + displayTotalMinor = displayCurrency !== Currency.ETB + ? await this.currencyService.convertAmount(resolvedTotalMinor, Currency.ETB, displayCurrency) + : resolvedTotalMinor; + } else { + resolvedTotalMinor = fareCalculation.totalMinor; + displayTotalMinor = displayCurrency !== Currency.ETB + ? await this.currencyService.convertAmount(resolvedTotalMinor, Currency.ETB, displayCurrency) + : resolvedTotalMinor; } + this.logger.log(`createOneWayBooking: resolvedTotalMinor=${resolvedTotalMinor} displayTotalMinor=${displayTotalMinor} displayCurrency=${displayCurrency} (reviewedTotalMinor=${dto.reviewedTotalMinor} allFaresProvided=${allFaresProvided} fareEngine=${fareCalculation.totalMinor})`); const booking = await this.prisma.booking.create({ data: { @@ -857,7 +871,7 @@ export class BookingsService { destinationStationId: dto.destinationStationId, status: 'PENDING_PAYMENT', bookingType: 'ONE_WAY', - totalMinor: resolvedTotalMinor, + totalMinor: resolvedTotalMinor / 100, adultCount, childCount, displayCurrency, @@ -1011,11 +1025,14 @@ export class BookingsService { const rtSeatedPassengers = passengersData.filter(p => p.outboundSeatId); const allRTFaresProvided = rtSeatedPassengers.length > 0 && rtSeatedPassengers.every(p => p.seatFareMinor != null && p.returnSeatFareMinor != null); - if (dto.reviewedTotalMinor) { - totalMinor = dto.reviewedTotalMinor; - displayTotalMinor = displayCurrency !== Currency.ETB - ? await this.currencyService.convertAmount(totalMinor, Currency.ETB, displayCurrency) - : totalMinor; + if (dto.reviewedTotalMinor != null) { + if (displayCurrency !== Currency.ETB) { + displayTotalMinor = dto.reviewedTotalMinor; + totalMinor = await this.currencyService.convertAmount(dto.reviewedTotalMinor, displayCurrency, Currency.ETB); + } else { + totalMinor = dto.reviewedTotalMinor; + displayTotalMinor = dto.reviewedTotalMinor; + } } else if (allRTFaresProvided && !dto.packageId) { totalMinor = passengersWithFares.reduce((sum, p) => sum + p.outboundFareMinor + p.returnFareMinor, 0); if (displayCurrency !== Currency.ETB) { diff --git a/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx b/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx index 4ab5cb9b7..1a80a2aef 100644 --- a/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx +++ b/apps/edr-passenger-web/backoffice/src/app/bookings/page.tsx @@ -270,7 +270,7 @@ function BookingsPageContent() { render: (booking: any) => (