mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
242 lines
10 KiB
TypeScript
242 lines
10 KiB
TypeScript
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';
|
|
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')
|
|
@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('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({
|
|
summary: 'List all bookings with filters (Admin/Agent)',
|
|
description: 'Returns paginated list of bookings. Use `returnLegStatus=OUTBOUND_ONLY` to find round-trip no-shows on the return leg, `INBOUND_ONLY` for passengers who only used the return leg, `BOTH_USED` for fully completed round-trips, and `NEITHER_USED` for confirmed but not yet boarded.'
|
|
})
|
|
@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: 'returnLegStatus', required: false, description: 'Filter round-trip leg usage: NEITHER_USED | OUTBOUND_ONLY | INBOUND_ONLY | BOTH_USED | NOT_APPLICABLE' })
|
|
@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('returnLegStatus') returnLegStatus?: string,
|
|
@Query('page') page?: string,
|
|
@Query('pageSize') pageSize?: string,
|
|
) {
|
|
return this.service.findAll({
|
|
search,
|
|
status,
|
|
returnLegStatus,
|
|
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 (one-way or round-trip)',
|
|
description: `Creates a one-way or round-trip booking for logged-in users.
|
|
|
|
ONE_WAY booking:
|
|
- scheduleId, holdId, originStationId, destinationStationId
|
|
- passengers: array of PassengerInputDto with seatId
|
|
- Single PNR, single payment
|
|
|
|
ROUND_TRIP booking:
|
|
- Outbound: scheduleId, holdId, originStationId, destinationStationId, seatClassId
|
|
- Return: returnScheduleId, returnHoldId, returnOriginStationId, returnDestinationStationId, returnSeatClassId
|
|
- passengers: array of RoundTripPassengerDto with outboundSeatId and returnSeatId
|
|
- Combined PNR, single payment for both legs
|
|
- Fare = outbound_fare + return_fare, single total, single promo, single loyalty deduction`
|
|
})
|
|
@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(':id')
|
|
@ApiOperation({
|
|
summary: 'Update booking details',
|
|
description: 'Updates booking information for admin/agent operations'
|
|
})
|
|
@ApiResponse({ status: 200, description: 'Booking updated successfully' })
|
|
@ApiResponse({ status: 404, description: 'Booking not found' })
|
|
update(@Param('id') id: string, @Body() dto: any) {
|
|
return this.service.update(id, dto);
|
|
}
|
|
|
|
@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(':id')
|
|
@ApiOperation({
|
|
summary: 'Delete booking (admin only)',
|
|
description: 'Permanently deletes a booking record'
|
|
})
|
|
@ApiResponse({ status: 200, description: 'Booking deleted successfully' })
|
|
@ApiResponse({ status: 404, description: 'Booking not found' })
|
|
delete(@Param('id') id: string) {
|
|
return this.service.delete(id);
|
|
}
|
|
|
|
@Get(':id/usage')
|
|
@ApiOperation({
|
|
summary: 'Check if booking is in use',
|
|
description: 'Returns list of modules/data that reference this booking'
|
|
})
|
|
@ApiResponse({ status: 200, description: 'Usage information retrieved' })
|
|
@ApiResponse({ status: 404, description: 'Booking not found' })
|
|
checkUsage(@Param('id') id: string) {
|
|
return this.service.checkBookingUsage(id);
|
|
}
|
|
|
|
@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);
|
|
}
|
|
}
|