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 2a9de63d2..a6703f55e 100644
--- a/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts
+++ b/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts
@@ -1879,6 +1879,8 @@ export class BookingsService {
adultCount: booking.adultCount, childCount: booking.childCount,
displayCurrency: booking.displayCurrency, displayTotalMinor: booking.displayTotalMinor ?? undefined,
bookingType: booking.bookingType,
+ packageId: (booking as any).packageId ?? null,
+ isPackageBooking: !!(booking as any).packageId,
returnLegStatus: (booking as any).returnLegStatus ?? null,
outboundBoardedAt: (booking as any).outboundBoardedAt ?? null,
returnBoardedAt: (booking as any).returnBoardedAt ?? null,
diff --git a/apps/edr-passenger-web/portal/src/app/booking/detail/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/detail/page.tsx
index 025c212bf..c0c9f56bb 100644
--- a/apps/edr-passenger-web/portal/src/app/booking/detail/page.tsx
+++ b/apps/edr-passenger-web/portal/src/app/booking/detail/page.tsx
@@ -427,6 +427,7 @@ function BookingDetailContent() {
+ {!booking.isPackageBooking && booking.bookingType !== "PACKAGE" && (
Fare breakdown
@@ -481,6 +482,7 @@ function BookingDetailContent() {
);
})}
+ )}
diff --git a/apps/edr-passenger-web/portal/src/app/booking/review/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/review/page.tsx
index 10051f0d3..95d390182 100644
--- a/apps/edr-passenger-web/portal/src/app/booking/review/page.tsx
+++ b/apps/edr-passenger-web/portal/src/app/booking/review/page.tsx
@@ -52,6 +52,7 @@ export default function ReviewPage() {
const [timeLeft, setTimeLeft] = useState
('');
const [seatDetails, setSeatDetails] = useState>({});
const [fareBreakdown, setFareBreakdown] = useState(null);
+ const [returnFareBreakdown, setReturnFareBreakdown] = useState(null);
const [computedTotal, setComputedTotal] = useState(0);
const isRoundTrip = searchCriteria?.tripType === 'ROUND_TRIP';
@@ -173,16 +174,27 @@ const adultPassengerCount = searchCriteria?.adultCount ?? passengers.filter(p =>
// Prefers displayFareMinor (converted) from the fare breakdown API when available.
// Falls back to raw ETB seat fares (which are always in minor units).
const getPassengerSeatFare = (p: any, index?: number): number | null => {
+ if (isRoundTrip) {
+ // Seat-specific fares (set during seat selection) cover each leg separately — use them first.
+ if (p.outboundSeatFareMinor != null || p.inboundSeatFareMinor != null) {
+ if (isPackageBooking) return (p.outboundSeatFareMinor ?? 0) * 2;
+ return (p.outboundSeatFareMinor ?? 0) + (p.inboundSeatFareMinor ?? 0);
+ }
+ // No seat-specific fares: fall back to the per-leg fare-breakdown totals for both legs.
+ if (!isPackageBooking && fareBreakdown?.passengers && returnFareBreakdown?.passengers && index != null) {
+ const obLine = fareBreakdown.passengers[index];
+ const retLine = returnFareBreakdown.passengers[index];
+ const obFare = obLine?.displayFareMinor ?? obLine?.fareMinor;
+ const retFare = retLine?.displayFareMinor ?? retLine?.fareMinor;
+ if (obFare != null && retFare != null) return obFare + retFare;
+ }
+ return null;
+ }
if (!isPackageBooking && fareBreakdown?.passengers && index != null) {
const line = fareBreakdown.passengers[index];
const displayFare = line?.displayFareMinor ?? line?.fareMinor;
if (displayFare != null) return displayFare;
}
- if (isRoundTrip) {
- if (p.outboundSeatFareMinor == null && p.inboundSeatFareMinor == null) return null;
- if (isPackageBooking) return (p.outboundSeatFareMinor ?? 0) * 2;
- return (p.outboundSeatFareMinor ?? 0) + (p.inboundSeatFareMinor ?? 0);
- }
if (p.seatFareMinor == null) return null;
return isPackageBooking ? p.seatFareMinor * 2 : p.seatFareMinor;
};
@@ -541,6 +553,22 @@ const adultPassengerCount = searchCriteria?.adultCount ?? passengers.filter(p =>
const result: any = await apiClient.get(`/search/fare-breakdown?${params}`);
setFareBreakdown(result);
+
+ // For round-trips, also fetch the return leg's fare breakdown so the review page
+ // can display and send the correct combined total (outbound + return per passenger).
+ if (isRoundTrip && inboundSchedule) {
+ const returnScheduleId = (inboundSchedule as any).id;
+ const returnParams = new URLSearchParams({
+ scheduleId: returnScheduleId,
+ originStationId: searchCriteria.destinationStationId,
+ destinationStationId: searchCriteria.originStationId,
+ passengers: passengersParam,
+ displayCurrency: displayCurrencyCode,
+ ...(searchCriteria.promoCode ? { promoCode: searchCriteria.promoCode } : {}),
+ });
+ const returnResult: any = await apiClient.get(`/search/fare-breakdown?${returnParams}`);
+ setReturnFareBreakdown(returnResult);
+ }
} catch (err) {
}
})();
@@ -566,7 +594,11 @@ const adultPassengerCount = searchCriteria?.adultCount ?? passengers.filter(p =>
const isFreeChild = line?.isFree ?? (isChildPassenger && isFirstChild(passengers, i));
if (isFreeChild) return sum;
const seatFare = getPassengerSeatFare(p, i);
- const displayFare = line?.displayFareMinor ?? line?.fareMinor;
+ // For round-trips the fallback must combine both legs; for one-way it's the single-leg fare.
+ const obFare = line?.displayFareMinor ?? line?.fareMinor;
+ const retLine = returnFareBreakdown?.passengers?.[i];
+ const retFare = retLine?.displayFareMinor ?? retLine?.fareMinor;
+ const displayFare = isRoundTrip && retFare != null ? (obFare ?? 0) + retFare : obFare;
return sum + (seatFare ?? displayFare ?? 0);
}, 0);
@@ -586,26 +618,27 @@ const adultPassengerCount = searchCriteria?.adultCount ?? passengers.filter(p =>
? isPkgFreeChild(i)
: (line?.isFree ?? (isChild(p) && isFirstChild(passengers, i)));
- // Per-leg fares for round trips — use converted amounts from fareBreakdown when available
- const displayFare = line?.displayFareMinor ?? line?.fareMinor;
+ // Per-leg fares for round trips — prefer seat-specific fares, then per-leg breakdowns.
+ const retLine = returnFareBreakdown?.passengers?.[i];
+ const obBreakdownFare = line?.displayFareMinor ?? line?.fareMinor;
+ const retBreakdownFare = retLine?.displayFareMinor ?? retLine?.fareMinor;
const outboundFare: number | null = isRoundTrip
? (isPackageBooking
? (packageTierPriceMinor ?? null)
- : (displayFare != null
- ? Math.round(displayFare / 2)
- : ((p as any).outboundSeatFareMinor ?? null)))
+ : ((p as any).outboundSeatFareMinor ?? obBreakdownFare ?? null))
: null;
const inboundFare: number | null = isRoundTrip
? (isPackageBooking
? (packageTierPriceMinor ?? null)
- : (displayFare != null
- ? Math.round(displayFare / 2)
- : ((p as any).inboundSeatFareMinor ?? null)))
+ : ((p as any).inboundSeatFareMinor ?? retBreakdownFare ?? null))
: null;
const seatFare = getPassengerSeatFare(p, i);
+ const combinedDisplayFare = isRoundTrip && retBreakdownFare != null
+ ? (obBreakdownFare ?? 0) + retBreakdownFare
+ : obBreakdownFare;
const passengerTotal = isPackageBooking
? (isFreeChild ? 0 : (seatFare ?? (isChildPassenger ? pkgChildFare : pkgAdultFare)))
- : (isFreeChild ? 0 : (seatFare ?? displayFare ?? 0));
+ : (isFreeChild ? 0 : (seatFare ?? combinedDisplayFare ?? 0));
return (
diff --git a/apps/edr-passenger-web/portal/src/app/booking/seats/page.tsx b/apps/edr-passenger-web/portal/src/app/booking/seats/page.tsx
index a143f7a1e..b38e3aee9 100644
--- a/apps/edr-passenger-web/portal/src/app/booking/seats/page.tsx
+++ b/apps/edr-passenger-web/portal/src/app/booking/seats/page.tsx
@@ -52,7 +52,7 @@ const BedCard = memo(({ bed, isSelected, isAssignedToOther, onToggle }: any) =>
? "bg-purple-50 border-purple-300 cursor-not-allowed dark:bg-purple-900/20 dark:border-purple-700"
: bed.status === "AVAILABLE"
? "bg-green-50 border-green-300 hover:bg-green-100 hover:border-green-400 dark:bg-green-900/20 dark:border-green-700"
- : bed.status === "BOOKED"
+ : bed.status === "BOOKED" || bed.status === "BLOCKED"
? "bg-red-50 border-red-300 cursor-not-allowed dark:bg-red-900/20 dark:border-red-700"
: "bg-gray-100 border-gray-300 cursor-not-allowed dark:bg-gray-800 dark:border-gray-700"
}`}