diff --git a/apps/edr-passenger-api/src/modules/bookings/bookings.controller.ts b/apps/edr-passenger-api/src/modules/bookings/bookings.controller.ts index d21777a1b..d7f87550f 100644 --- a/apps/edr-passenger-api/src/modules/bookings/bookings.controller.ts +++ b/apps/edr-passenger-api/src/modules/bookings/bookings.controller.ts @@ -1,4 +1,4 @@ -import { Body, Controller, Delete, Get, Param, Post, Patch, UseGuards, Query, Req } from '@nestjs/common'; +import { Body, Controller, Delete, Get, Param, Post, Patch, UseGuards, Query, Req, BadRequestException } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiResponse, ApiQuery } from '@nestjs/swagger'; import { BookingsService } from './bookings.service'; import { GuestBookingService } from './guest-booking.service'; @@ -15,7 +15,7 @@ export class BookingsController { private guestService: GuestBookingService, ) {} - @Get('my/bookings') + @Get('my') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ @@ -43,6 +43,34 @@ export class BookingsController { pageSize: pageSize ? parseInt(pageSize) : 20 }); } + + @Get('by-device') + @ApiOperation({ + summary: 'Get bookings by device ID', + description: 'Returns all bookings associated with a device ID (for guest users). Includes saved passenger details and booking history.' + }) + @ApiQuery({ name: 'deviceId', required: true, description: 'Device identifier' }) + @ApiQuery({ name: 'search', required: false, description: 'Search by booking reference or station names' }) + @ApiQuery({ name: 'status', required: false, description: 'Filter by booking status' }) + @ApiQuery({ name: 'page', required: false, description: 'Page number' }) + @ApiQuery({ name: 'pageSize', required: false, description: 'Items per page' }) + @ApiResponse({ status: 200, description: 'List of guest bookings and saved passengers for device' }) + @ApiResponse({ status: 400, description: 'Device ID is required' }) + getByDevice( + @Query('deviceId') deviceId?: string, + @Query('search') search?: string, + @Query('status') status?: string, + @Query('page') page?: string, + @Query('pageSize') pageSize?: string, + ) { + if (!deviceId) throw new BadRequestException('Device ID is required'); + return this.service.findByDeviceId(deviceId, { + search, + status, + page: page ? parseInt(page) : 1, + pageSize: pageSize ? parseInt(pageSize) : 20 + }); + } @Get() @ApiOperation({ 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 256e974a7..7a12efc8c 100644 --- a/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts +++ b/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts @@ -102,6 +102,86 @@ export class BookingsService { }; } + async findByDeviceId(deviceId: string, filters: BookingFilters = {}) { + const { search, status, page = 1, pageSize = 20 } = filters; + const skip = (page - 1) * pageSize; + + const where: any = { passenger: { user: { devices: { some: { id: deviceId } } } } }; + + if (search) { + where.OR = [ + { bookingRef: { contains: search, mode: 'insensitive' } }, + { schedule: { originStation: { name: { contains: search, mode: 'insensitive' } } } }, + { schedule: { destinationStation: { name: { contains: search, mode: 'insensitive' } } } }, + ]; + } + + if (status) { + where.status = status; + } + + const [items, total] = await Promise.all([ + this.prisma.booking.findMany({ + where, + skip, + take: pageSize, + orderBy: { createdAt: 'desc' }, + include: { + schedule: { include: { originStation: true, destinationStation: true, train: true } }, + paymentIntent: true, + seats: { include: { seat: true } }, + }, + }), + this.prisma.booking.count({ where }), + ]); + + const savedPassengers = await this.prisma.savedPassengerProfile.findMany({ + where: { deviceId }, + orderBy: { createdAt: 'desc' }, + }); + + return { + bookings: items.map(booking => ({ + id: booking.id, + bookingRef: booking.bookingRef, + status: booking.status, + totalMinor: booking.totalMinor, + currency: 'ETB', + displayCurrency: booking.displayCurrency, + displayTotalMinor: booking.displayTotalMinor, + adultCount: booking.adultCount, + childCount: booking.childCount, + createdAt: booking.createdAt, + schedule: { + train: booking.schedule.train, + originStation: booking.schedule.originStation, + destinationStation: booking.schedule.destinationStation, + departureAt: booking.schedule.departureAt, + arrivalAt: booking.schedule.arrivalAt, + }, + paymentIntent: booking.paymentIntent, + seatCount: booking.seats.length, + })), + savedPassengers: savedPassengers.map(p => ({ + id: p.id, + passengerName: p.passengerName, + dateOfBirth: p.dateOfBirth.toISOString().split('T')[0], + idDocumentType: p.idDocumentType, + passportNumber: p.passportNumber || undefined, + passportCountry: p.passportCountry || undefined, + nationality: p.nationality || undefined, + phone: p.phone || undefined, + email: p.email || undefined, + })), + meta: { + page, + pageSize, + total, + totalPages: Math.ceil(total / pageSize), + }, + }; + } + async findAll(filters: BookingFilters = {}) { const { search, status, page = 1, pageSize = 20 } = filters; const skip = (page - 1) * pageSize; diff --git a/apps/edr-passenger-web/backoffice/src/lib/api/index.ts b/apps/edr-passenger-web/backoffice/src/lib/api/index.ts index 55851e8bb..4f1f0322b 100644 --- a/apps/edr-passenger-web/backoffice/src/lib/api/index.ts +++ b/apps/edr-passenger-web/backoffice/src/lib/api/index.ts @@ -18,6 +18,29 @@ export const bookingsApi = { return Array.isArray(response) ? { items: response } : response; }, getById: (id: string) => apiClient.get(`/bookings/${id}`), + getMy: async (params?: any) => { + const cleanParams = Object.fromEntries( + Object.entries(params || {}).filter(([_, value]) => value !== '' && value !== undefined && value !== null) + ) as Record; + const query = new URLSearchParams(cleanParams).toString(); + const response = await apiClient.get(`/bookings/my${query ? `?${query}` : ''}`); + if (response?.data) { + return Array.isArray(response.data) ? { items: response.data } : response; + } + return Array.isArray(response) ? { items: response } : response; + }, + getByDevice: async (deviceId: string, params?: any) => { + const cleanParams = Object.fromEntries( + Object.entries({ ...params }).filter(([_, value]) => value !== '' && value !== undefined && value !== null) + ) as Record; + cleanParams['deviceId'] = deviceId; + const query = new URLSearchParams(cleanParams).toString(); + const response = await apiClient.get(`/bookings/by-device${query ? `?${query}` : ''}`); + if (response?.data) { + return Array.isArray(response.data) ? { items: response.data } : response; + } + return Array.isArray(response) ? { items: response } : response; + }, cancel: (id: string, data?: any) => apiClient.post(`/bookings/${id}/cancel`, data), modify: (id: string, data: any) => apiClient.patch(`/bookings/${id}`, data), checkUsage: (id: string) => apiClient.get(`/bookings/${id}/usage`),