diff --git a/apps/edr-passenger-api/prisma/migrations/20260716202849_add_journey_segment_unique_seat_per_schedule/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260716202849_add_journey_segment_unique_seat_per_schedule/migration.sql index 2608b4cbf..83a49ef67 100644 --- a/apps/edr-passenger-api/prisma/migrations/20260716202849_add_journey_segment_unique_seat_per_schedule/migration.sql +++ b/apps/edr-passenger-api/prisma/migrations/20260716202849_add_journey_segment_unique_seat_per_schedule/migration.sql @@ -1,3 +1,4 @@ +<<<<<<< Updated upstream -- Remove duplicate JourneySegment rows, keeping the one with the lowest id -- (earliest created) per (scheduleId, seatId, departureStationId) group. -- This cleans up any existing double-bookings before the unique index is applied. @@ -16,3 +17,6 @@ AND "seatId" IS NOT NULL; CREATE UNIQUE INDEX "JourneySegment_scheduleId_seatId_departureStationId_key" ON passenger."JourneySegment" ("scheduleId", "seatId", "departureStationId") WHERE "seatId" IS NOT NULL; +======= +-- Migration already applied directly to the database. +>>>>>>> Stashed changes diff --git a/apps/edr-passenger-api/prisma/migrations/20260717000001_add_route_checkin_minutes_rename_stop_status/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260717000001_add_route_checkin_minutes_rename_stop_status/migration.sql index 375f40f7e..e04c02621 100644 --- a/apps/edr-passenger-api/prisma/migrations/20260717000001_add_route_checkin_minutes_rename_stop_status/migration.sql +++ b/apps/edr-passenger-api/prisma/migrations/20260717000001_add_route_checkin_minutes_rename_stop_status/migration.sql @@ -1,3 +1,4 @@ +<<<<<<< Updated upstream -- Rename StopStatus enum values to reflect segment-level booking lifecycle. -- UPCOMING → OPEN (segment is bookable) -- APPROACHING → CHECKIN_CLOSED (within check-in cutoff, no new bookings) @@ -10,3 +11,6 @@ ALTER TYPE "passenger"."StopStatus" RENAME VALUE 'CURRENT' TO 'BOARDED'; -- Add per-route check-in window. Each route can define how many minutes before -- a stop's planned departure check-in is closed. Defaults to 30 minutes. ALTER TABLE "passenger"."Route" ADD COLUMN "checkinMinutesBefore" INTEGER NOT NULL DEFAULT 30; +======= +-- Migration already applied directly to the database. +>>>>>>> Stashed changes diff --git a/apps/edr-passenger-api/prisma/migrations/20260717000002_add_route_stop_checkin_minutes/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260717000002_add_route_stop_checkin_minutes/migration.sql index 0b0995292..5fd9ae0f5 100644 --- a/apps/edr-passenger-api/prisma/migrations/20260717000002_add_route_stop_checkin_minutes/migration.sql +++ b/apps/edr-passenger-api/prisma/migrations/20260717000002_add_route_stop_checkin_minutes/migration.sql @@ -1 +1,5 @@ +<<<<<<< Updated upstream ALTER TABLE "passenger"."RouteStop" ADD COLUMN "checkinMinutesBefore" INTEGER; +======= +-- Migration already applied directly to the database. +>>>>>>> Stashed changes diff --git a/apps/edr-passenger-api/prisma/migrations/20260717000003_booking_seat_schedule_unique/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260717000003_booking_seat_schedule_unique/migration.sql new file mode 100644 index 000000000..8553fc81b --- /dev/null +++ b/apps/edr-passenger-api/prisma/migrations/20260717000003_booking_seat_schedule_unique/migration.sql @@ -0,0 +1,66 @@ +-- Make scheduleId non-nullable: backfill from the parent booking, then add NOT NULL. +UPDATE passenger."BookingSeat" bs +<<<<<<< Updated upstream +SET schedule_id = b.schedule_id +FROM passenger."Booking" b +WHERE bs.booking_id = b.id + AND bs.schedule_id IS NULL +======= +SET "scheduleId" = b."scheduleId" +FROM passenger."Booking" b +WHERE bs."bookingId" = b.id + AND bs."scheduleId" IS NULL +>>>>>>> Stashed changes + AND bs.leg = 1; + +-- For leg=2 rows (return/transit), pull from the booking's returnScheduleId / leg2ScheduleId. +UPDATE passenger."BookingSeat" bs +<<<<<<< Updated upstream +SET schedule_id = COALESCE( + (b.return_schedule_id), + (b.leg2_schedule_id), + b.schedule_id +) +FROM passenger."Booking" b +WHERE bs.booking_id = b.id + AND bs.schedule_id IS NULL + AND bs.leg = 2; + +-- Catch any remaining NULLs (leg 3/4 from ROUND_TRIP_TRANSIT) using the booking's schedule. +UPDATE passenger."BookingSeat" bs +SET schedule_id = b.schedule_id +FROM passenger."Booking" b +WHERE bs.booking_id = b.id + AND bs.schedule_id IS NULL; + +-- Now enforce NOT NULL. +ALTER TABLE passenger."BookingSeat" ALTER COLUMN schedule_id SET NOT NULL; + +-- Add the unique constraint that is the actual double-booking guard. +CREATE UNIQUE INDEX "BookingSeat_scheduleId_seatId_key" + ON passenger."BookingSeat"(schedule_id, seat_id); +======= +SET "scheduleId" = COALESCE( + b."returnScheduleId", + b."leg2ScheduleId", + b."scheduleId" +) +FROM passenger."Booking" b +WHERE bs."bookingId" = b.id + AND bs."scheduleId" IS NULL + AND bs.leg = 2; + +-- Catch any remaining NULLs using the booking's schedule. +UPDATE passenger."BookingSeat" bs +SET "scheduleId" = b."scheduleId" +FROM passenger."Booking" b +WHERE bs."bookingId" = b.id + AND bs."scheduleId" IS NULL; + +-- Now enforce NOT NULL. +ALTER TABLE passenger."BookingSeat" ALTER COLUMN "scheduleId" SET NOT NULL; + +-- Add the unique constraint that is the actual double-booking guard. +CREATE UNIQUE INDEX "BookingSeat_scheduleId_seatId_key" + ON passenger."BookingSeat"("scheduleId", "seatId"); +>>>>>>> Stashed changes diff --git a/apps/edr-passenger-api/prisma/schema.prisma b/apps/edr-passenger-api/prisma/schema.prisma index fb9db346f..601410fae 100644 --- a/apps/edr-passenger-api/prisma/schema.prisma +++ b/apps/edr-passenger-api/prisma/schema.prisma @@ -580,7 +580,7 @@ model BookingSeat { bookingId String seatId String leg Int @default(1) // 1=outbound/leg-1, 2=return/leg-2 - scheduleId String? // which schedule this seat belongs to + scheduleId String // which schedule this seat belongs to passengerName String dateOfBirth DateTime? passengerCategory PassengerCategory @default(ADULT) @@ -599,6 +599,7 @@ model BookingSeat { displayFareMinor Int? booking Booking @relation(fields: [bookingId], references: [id]) seat Seat @relation(fields: [seatId], references: [id]) + @@unique([scheduleId, seatId]) @@schema("passenger") } diff --git a/apps/edr-passenger-api/src/modules/agents/agents.service.ts b/apps/edr-passenger-api/src/modules/agents/agents.service.ts index 4ba7afcf1..57f33c5aa 100644 --- a/apps/edr-passenger-api/src/modules/agents/agents.service.ts +++ b/apps/edr-passenger-api/src/modules/agents/agents.service.ts @@ -85,6 +85,7 @@ export class AgentsService { seats: { create: dto.passengers.map(p => ({ seat: { connect: { id: p.seatId } }, + scheduleId: dto.scheduleId, passengerName: p.fullName, idDocumentType: p.idDocumentType as IdDocumentType | undefined, idDocumentNumber: p.idDocumentNumber 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 a0994d1be..ada6e2a78 100644 --- a/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts +++ b/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts @@ -910,6 +910,7 @@ export class BookingsService { seats: { create: passengersWithFares.map(p => ({ seat: { connect: { id: p.seatId } }, + scheduleId: dto.scheduleId, passengerName: p.passengerName, dateOfBirth: p.dateOfBirth, passengerCategory: p.category, diff --git a/apps/edr-passenger-api/src/modules/bookings/guest-booking.service.ts b/apps/edr-passenger-api/src/modules/bookings/guest-booking.service.ts index 43470c160..17e3a585e 100644 --- a/apps/edr-passenger-api/src/modules/bookings/guest-booking.service.ts +++ b/apps/edr-passenger-api/src/modules/bookings/guest-booking.service.ts @@ -299,6 +299,7 @@ export class GuestBookingService { seats: { create: passengersWithFares.map((p) => ({ seat: { connect: { id: p.seatId } }, + scheduleId: dto.scheduleId, passengerName: p.passengerName, dateOfBirth: p.dateOfBirth, passengerCategory: p.category, 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 9ddc1fb0a..1528b2967 100644 --- a/apps/edr-passenger-api/src/modules/packages/packages.service.ts +++ b/apps/edr-passenger-api/src/modules/packages/packages.service.ts @@ -440,8 +440,8 @@ export class PackagesService { passengerCount, adultCount, childCount, - totalMinor, - currency: 'ETB', + totalMinor: displayTotalMinor, + currency: displayCurrency, displayCurrency, displayTotalMinor, status: 'PENDING_PAYMENT', diff --git a/apps/edr-passenger-api/src/modules/payments/payments.e2e-spec.ts b/apps/edr-passenger-api/src/modules/payments/payments.e2e-spec.ts index 5393696bf..058b2f9f6 100644 --- a/apps/edr-passenger-api/src/modules/payments/payments.e2e-spec.ts +++ b/apps/edr-passenger-api/src/modules/payments/payments.e2e-spec.ts @@ -118,6 +118,7 @@ describe("Payments E2E", () => { data: { bookingId: booking.id, seatId: seat.id, + scheduleId: schedule.id, passengerName: "Test Passenger", }, }); diff --git a/apps/edr-passenger-api/src/modules/search/search.service.ts b/apps/edr-passenger-api/src/modules/search/search.service.ts index a51dc0142..29be84adc 100644 --- a/apps/edr-passenger-api/src/modules/search/search.service.ts +++ b/apps/edr-passenger-api/src/modules/search/search.service.ts @@ -535,8 +535,8 @@ export class SearchService { discountMinor: fare.discountMinor, taxesFeesMinor: 0, loyaltyRedemptionMinor: loyaltyMinor, - totalMinor, - currency: 'ETB', + totalMinor: displayTotalMinor, + currency: displayCurrency, displayCurrency, displayTotalMinor, }; @@ -653,8 +653,8 @@ export class SearchService { passengers: passengerLines, subtotalMinor, discountMinor, - totalMinor, - currency: 'ETB', + totalMinor: displayTotalMinor, + currency: displayCurrency, displayCurrency, displayTotalMinor, }; diff --git a/apps/edr-passenger-api/src/modules/seats/seats.service.ts b/apps/edr-passenger-api/src/modules/seats/seats.service.ts index 963cc571d..3d87c8331 100644 --- a/apps/edr-passenger-api/src/modules/seats/seats.service.ts +++ b/apps/edr-passenger-api/src/modules/seats/seats.service.ts @@ -1020,7 +1020,7 @@ export class SeatsService { where: { OR: [ { scheduleId: schedule.id }, - { scheduleId: null, booking: { scheduleId: schedule.id } }, + { booking: { scheduleId: schedule.id } }, ], booking: { status: { in: ['CONFIRMED', 'BOARDED'] } }, }, diff --git a/booking-checker.html b/booking-checker.html new file mode 100644 index 000000000..9d2b7ded4 --- /dev/null +++ b/booking-checker.html @@ -0,0 +1,530 @@ + + +
+ + +No trailing slash. e.g. https://api.edrsc.com
+Supports any format: one per line, comma-separated, or {REF1,REF2} groups.
| Journey | +Duplicate Bookings | +
|---|
bookings.json here or click to browse
+ Accepts a JSON array of bookings or an object with a bookings key.
| # | +Booking Ref | +Status | +Booking Type | +Phone | +Departure | +Origin | +Destination | +Passenger(s) | +Coach - Seat | +Payment Method | +Payment Status | +Total (DJF) | +Created At | +
|---|
tickets.json here or click to browse
+ Accepts a JSON array of tickets or an object with a tickets key.
| # | +Ticket No. | +Booking Ref | +Passenger | +Phone | +Journey Type | +Origin | +Destination | +Seat Class | +Coach | +Seat | +
|---|