import { Body, Controller, Delete, Get, Param, Post, Patch, UseGuards, Query, Req } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiBearerAuth, ApiResponse, ApiQuery } from '@nestjs/swagger'; import { BookingsService } from './bookings.service'; import { GuestBookingService } from './guest-booking.service'; import { CreateBookingDto, ModifyBookingDto, CancelBookingDto } from './bookings.dto'; import { CreateGuestBookingDto, GetSavedPassengersDto } from './guest-booking.dto'; import { JwtGuard } from '../../common/jwt.guard'; import { IamGuard } from '../../common/iam-adapter'; @ApiTags('Booking') @Controller('bookings') export class BookingsController { constructor( private service: BookingsService, private guestService: GuestBookingService, ) {} @Get('my/bookings') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Get logged-in user\'s booking history', description: 'Returns all bookings for the authenticated user with schedule and payment details' }) @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 user bookings with schedule and passenger details' }) getMyBookings( @Req() req: any, @Query('search') search?: string, @Query('status') status?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { const passengerId = req.user?.passengerId; if (!passengerId) throw new Error('Passenger ID not found in token'); return this.service.findByPassengerId(passengerId, { search, status, page: page ? parseInt(page) : 1, pageSize: pageSize ? parseInt(pageSize) : 20 }); } @Get() @ApiOperation({ summary: 'List all bookings with filters (Admin/Agent)', description: 'Returns paginated list of bookings with search and status filters' }) @ApiQuery({ name: 'search', required: false, description: 'Search by booking reference, email, or phone' }) @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' }) findAll( @Query('search') search?: string, @Query('status') status?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, ) { return this.service.findAll({ search, status, page: page ? parseInt(page) : 1, pageSize: pageSize ? parseInt(pageSize) : 20 }); } @Post('guest') @ApiOperation({ summary: 'Create guest booking without login (optional account creation)', description: `Creates a booking without requiring login. Features: **Guest Checkout:** - No login required - Contact details from first passenger - Booking confirmation sent to email/phone **Optional Account Creation:** - Set createAccount=true with password - Account created using first passenger details - Automatic login after booking - Loyalty points and wallet created **Passenger Details Storage:** - savePassengerDetails=true: Save for future bookings - Stored by userId (if account created) or deviceId - Retrieve saved passengers for quick booking **Verifayda Verification:** - Ethiopian nationals: National ID verified via Verifayda - Other nationals: Passport details (no verification) **Age-Based Pricing:** - ADULT (≥5 years): Full fare - CHILD (<5 years): First child FREE, subsequent children full fare` }) @ApiResponse({ status: 201, description: 'Booking created successfully' }) @ApiResponse({ status: 400, description: 'Verifayda verification failed or invalid data' }) createGuest(@Body() dto: CreateGuestBookingDto) { return this.guestService.createGuestBooking(dto); } @Get('saved-passengers') @ApiOperation({ summary: 'Get saved passenger profiles', description: 'Retrieve saved passenger details by userId (if logged in) or deviceId (for guest users)' }) @ApiResponse({ status: 200, description: 'List of saved passenger profiles' }) getSavedPassengers(@Query() query: GetSavedPassengersDto) { return this.guestService.getSavedPassengers(undefined, query.deviceId); } @Post() @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Create booking (requires login)', description: `Creates a booking for logged-in users with saved passenger profiles. Use POST /bookings/guest for guest checkout without login.` }) @ApiResponse({ status: 201, description: 'Booking created with fare breakdown' }) @ApiResponse({ status: 400, description: 'Verifayda verification failed or invalid passenger data' }) @ApiResponse({ status: 404, description: 'Trip or seat hold not found' }) create(@Body() dto: CreateBookingDto) { return this.service.create(dto); } @Get(':bookingRef') @ApiOperation({ summary: 'Get booking details by reference (no auth required)', description: 'Returns booking with passenger categories, Verifayda verification status, and multi-currency amounts. Works for both guest and authenticated bookings.' }) @ApiResponse({ status: 200, description: 'Booking details with adult/child counts and currency conversion' }) @ApiResponse({ status: 404, description: 'Booking not found' }) getByRef(@Param('bookingRef') ref: string) { return this.service.getByRef(ref); } @Patch(':bookingRef/modify') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Modify booking seats or trip', description: 'Allows modification of confirmed bookings before departure' }) @ApiResponse({ status: 200, description: 'Booking modified successfully' }) @ApiResponse({ status: 400, description: 'Cannot modify cancelled or past bookings' }) modify(@Body() dto: ModifyBookingDto) { return this.service.modify(dto); } @Delete(':bookingRef') @UseGuards(JwtGuard) @ApiBearerAuth('JWT-auth') @ApiOperation({ summary: 'Cancel booking with refund', description: 'Cancels booking and processes refund (80% for confirmed bookings)' }) @ApiResponse({ status: 200, description: 'Booking cancelled with refund amount' }) @ApiResponse({ status: 400, description: 'Booking already cancelled' }) cancel(@Param('bookingRef') ref: string, @Body() dto: CancelBookingDto) { return this.service.cancel(ref, dto.reason); } }