mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 23:28:11 +00:00
remove throttler
This commit is contained in:
@@ -1,41 +1,85 @@
|
||||
import { Body, Controller, Delete, Get, Param, Post, Patch, UseGuards, Query, Req, SetMetadata, BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth, ApiResponse, ApiQuery, ApiBody } from '@nestjs/swagger';
|
||||
import { IsPublic } from '@tria-plc/api-common/modules/auth/decorators/public.decorator';
|
||||
import { Throttle } from '@nestjs/throttler';
|
||||
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 { PassengerAdmin } from '../../common/passenger-guards';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Patch,
|
||||
UseGuards,
|
||||
Query,
|
||||
Req,
|
||||
SetMetadata,
|
||||
BadRequestException,
|
||||
UnauthorizedException,
|
||||
} from "@nestjs/common";
|
||||
import {
|
||||
ApiTags,
|
||||
ApiOperation,
|
||||
ApiBearerAuth,
|
||||
ApiResponse,
|
||||
ApiQuery,
|
||||
ApiBody,
|
||||
} from "@nestjs/swagger";
|
||||
import { IsPublic } from "@tria-plc/api-common/modules/auth/decorators/public.decorator";
|
||||
import { Throttle } from "@nestjs/throttler";
|
||||
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 { PassengerAdmin } from "../../common/passenger-guards";
|
||||
|
||||
@ApiTags('Booking')
|
||||
@Controller('bookings')
|
||||
@Throttle({ strict: { limit: 20, ttl: 60_000 } })
|
||||
@ApiTags("Booking")
|
||||
@Controller("bookings")
|
||||
// @Throttle({ strict: { limit: 20, ttl: 60_000 } })
|
||||
export class BookingsController {
|
||||
constructor(
|
||||
private service: BookingsService,
|
||||
private guestService: GuestBookingService,
|
||||
) {}
|
||||
|
||||
@Get('my')
|
||||
|
||||
@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'
|
||||
@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",
|
||||
})
|
||||
@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,
|
||||
@Query("search") search?: string,
|
||||
@Query("status") status?: string,
|
||||
@Query("page") page?: string,
|
||||
@Query("pageSize") pageSize?: string,
|
||||
) {
|
||||
const iamUserId = req.user?.id;
|
||||
if (!iamUserId) throw new UnauthorizedException();
|
||||
@@ -43,81 +87,103 @@ export class BookingsController {
|
||||
search,
|
||||
status,
|
||||
page: page ? parseInt(page) : 1,
|
||||
pageSize: pageSize ? parseInt(pageSize) : 20
|
||||
pageSize: pageSize ? parseInt(pageSize) : 20,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('by-device')
|
||||
@SetMetadata('isPublic', true)
|
||||
@Get("by-device")
|
||||
@SetMetadata("isPublic", true)
|
||||
@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.'
|
||||
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' })
|
||||
@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,
|
||||
@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
|
||||
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()
|
||||
@SetMetadata('isPublic', true)
|
||||
@SetMetadata("isPublic", true)
|
||||
@ApiOperation({
|
||||
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.'
|
||||
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 })
|
||||
@ApiQuery({ name: 'status', required: false })
|
||||
@ApiQuery({ name: 'returnLegStatus', required: false })
|
||||
@ApiQuery({ name: 'bookingType', required: false })
|
||||
@ApiQuery({ name: 'paymentStatus', required: false })
|
||||
@ApiQuery({ name: 'dateFrom', required: false })
|
||||
@ApiQuery({ name: 'dateTo', required: false })
|
||||
@ApiQuery({ name: 'page', required: false })
|
||||
@ApiQuery({ name: 'pageSize', required: false })
|
||||
@ApiQuery({ name: "search", required: false })
|
||||
@ApiQuery({ name: "status", required: false })
|
||||
@ApiQuery({ name: "returnLegStatus", required: false })
|
||||
@ApiQuery({ name: "bookingType", required: false })
|
||||
@ApiQuery({ name: "paymentStatus", required: false })
|
||||
@ApiQuery({ name: "dateFrom", required: false })
|
||||
@ApiQuery({ name: "dateTo", required: false })
|
||||
@ApiQuery({ name: "page", required: false })
|
||||
@ApiQuery({ name: "pageSize", required: false })
|
||||
findAll(
|
||||
@Query('search') search?: string,
|
||||
@Query('status') status?: string,
|
||||
@Query('returnLegStatus') returnLegStatus?: string,
|
||||
@Query('bookingType') bookingType?: string,
|
||||
@Query('paymentStatus') paymentStatus?: string,
|
||||
@Query('dateFrom') dateFrom?: string,
|
||||
@Query('dateTo') dateTo?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
@Query("search") search?: string,
|
||||
@Query("status") status?: string,
|
||||
@Query("returnLegStatus") returnLegStatus?: string,
|
||||
@Query("bookingType") bookingType?: string,
|
||||
@Query("paymentStatus") paymentStatus?: string,
|
||||
@Query("dateFrom") dateFrom?: string,
|
||||
@Query("dateTo") dateTo?: string,
|
||||
@Query("page") page?: string,
|
||||
@Query("pageSize") pageSize?: string,
|
||||
) {
|
||||
return this.service.findAll({
|
||||
search,
|
||||
return this.service.findAll({
|
||||
search,
|
||||
status,
|
||||
returnLegStatus,
|
||||
bookingType,
|
||||
paymentStatus,
|
||||
dateFrom,
|
||||
dateTo,
|
||||
page: page ? parseInt(page) : 1,
|
||||
pageSize: pageSize ? parseInt(pageSize) : 20
|
||||
page: page ? parseInt(page) : 1,
|
||||
pageSize: pageSize ? parseInt(pageSize) : 20,
|
||||
});
|
||||
}
|
||||
|
||||
@Post('guest')
|
||||
@SetMetadata('isPublic', true)
|
||||
|
||||
@Post("guest")
|
||||
@SetMetadata("isPublic", true)
|
||||
@ApiOperation({
|
||||
summary: 'Create guest booking — ONE_WAY | ROUND_TRIP | TRANSIT | ROUND_TRIP_TRANSIT (no login required)',
|
||||
summary:
|
||||
"Create guest booking — ONE_WAY | ROUND_TRIP | TRANSIT | ROUND_TRIP_TRANSIT (no login required)",
|
||||
description: `Creates a booking without requiring login. Supports all four booking types.
|
||||
|
||||
**bookingType: ONE_WAY (default)**
|
||||
@@ -138,152 +204,170 @@ export class BookingsController {
|
||||
|
||||
**Optional account creation:** set createAccount=true with password — creates account from first passenger details, loyalty + wallet initialised.
|
||||
|
||||
**Verifayda:** Ethiopian nationals verified; international passengers require passportNumber + passportCountry.`
|
||||
**Verifayda:** Ethiopian nationals verified; international passengers require passportNumber + passportCountry.`,
|
||||
})
|
||||
@ApiBody({
|
||||
type: CreateGuestBookingDto,
|
||||
examples: {
|
||||
ONE_WAY: {
|
||||
summary: 'ONE_WAY — single direct journey (guest)',
|
||||
summary: "ONE_WAY — single direct journey (guest)",
|
||||
value: {
|
||||
scheduleId: 'schedule-uuid',
|
||||
holdId: 'hold-uuid',
|
||||
originStationId: 'station-uuid',
|
||||
destinationStationId: 'station-uuid',
|
||||
seatClassId: 'seat-class-uuid',
|
||||
bookingType: 'ONE_WAY',
|
||||
displayCurrency: 'ETB',
|
||||
passengers: [{
|
||||
seatId: 'seat-uuid',
|
||||
passengerName: 'Abebe Kebede',
|
||||
dateOfBirth: '1990-05-15',
|
||||
idDocumentType: 'NATIONAL_ID',
|
||||
idDocumentNumber: 'ET123456789',
|
||||
nationality: 'Ethiopian',
|
||||
phone: '+251911234567',
|
||||
email: 'abebe@email.com',
|
||||
}],
|
||||
scheduleId: "schedule-uuid",
|
||||
holdId: "hold-uuid",
|
||||
originStationId: "station-uuid",
|
||||
destinationStationId: "station-uuid",
|
||||
seatClassId: "seat-class-uuid",
|
||||
bookingType: "ONE_WAY",
|
||||
displayCurrency: "ETB",
|
||||
passengers: [
|
||||
{
|
||||
seatId: "seat-uuid",
|
||||
passengerName: "Abebe Kebede",
|
||||
dateOfBirth: "1990-05-15",
|
||||
idDocumentType: "NATIONAL_ID",
|
||||
idDocumentNumber: "ET123456789",
|
||||
nationality: "Ethiopian",
|
||||
phone: "+251911234567",
|
||||
email: "abebe@email.com",
|
||||
},
|
||||
],
|
||||
savePassengerDetails: true,
|
||||
deviceId: 'device-uuid-123',
|
||||
deviceId: "device-uuid-123",
|
||||
},
|
||||
},
|
||||
ROUND_TRIP: {
|
||||
summary: 'ROUND_TRIP — outbound + return, single PNR (guest)',
|
||||
summary: "ROUND_TRIP — outbound + return, single PNR (guest)",
|
||||
value: {
|
||||
scheduleId: 'outbound-schedule-uuid',
|
||||
holdId: 'outbound-hold-uuid',
|
||||
originStationId: 'addis-station-uuid',
|
||||
destinationStationId: 'djibouti-station-uuid',
|
||||
seatClassId: 'seat-class-uuid',
|
||||
bookingType: 'ROUND_TRIP',
|
||||
returnScheduleId: 'return-schedule-uuid',
|
||||
returnHoldId: 'return-hold-uuid',
|
||||
returnOriginStationId: 'djibouti-station-uuid',
|
||||
returnDestinationStationId: 'addis-station-uuid',
|
||||
returnSeatClassId: 'seat-class-uuid',
|
||||
displayCurrency: 'ETB',
|
||||
passengers: [{
|
||||
seatId: 'outbound-seat-uuid',
|
||||
returnSeatId: 'return-seat-uuid',
|
||||
passengerName: 'Abebe Kebede',
|
||||
dateOfBirth: '1990-05-15',
|
||||
idDocumentType: 'NATIONAL_ID',
|
||||
idDocumentNumber: 'ET123456789',
|
||||
nationality: 'Ethiopian',
|
||||
phone: '+251911234567',
|
||||
}],
|
||||
scheduleId: "outbound-schedule-uuid",
|
||||
holdId: "outbound-hold-uuid",
|
||||
originStationId: "addis-station-uuid",
|
||||
destinationStationId: "djibouti-station-uuid",
|
||||
seatClassId: "seat-class-uuid",
|
||||
bookingType: "ROUND_TRIP",
|
||||
returnScheduleId: "return-schedule-uuid",
|
||||
returnHoldId: "return-hold-uuid",
|
||||
returnOriginStationId: "djibouti-station-uuid",
|
||||
returnDestinationStationId: "addis-station-uuid",
|
||||
returnSeatClassId: "seat-class-uuid",
|
||||
displayCurrency: "ETB",
|
||||
passengers: [
|
||||
{
|
||||
seatId: "outbound-seat-uuid",
|
||||
returnSeatId: "return-seat-uuid",
|
||||
passengerName: "Abebe Kebede",
|
||||
dateOfBirth: "1990-05-15",
|
||||
idDocumentType: "NATIONAL_ID",
|
||||
idDocumentNumber: "ET123456789",
|
||||
nationality: "Ethiopian",
|
||||
phone: "+251911234567",
|
||||
},
|
||||
],
|
||||
savePassengerDetails: true,
|
||||
deviceId: 'device-uuid-123',
|
||||
deviceId: "device-uuid-123",
|
||||
},
|
||||
},
|
||||
TRANSIT: {
|
||||
summary: 'TRANSIT — connecting train, single PNR (guest)',
|
||||
summary: "TRANSIT — connecting train, single PNR (guest)",
|
||||
value: {
|
||||
scheduleId: 'leg1-schedule-uuid',
|
||||
holdId: 'leg1-hold-uuid',
|
||||
originStationId: 'addis-station-uuid',
|
||||
destinationStationId: 'diredawa-station-uuid',
|
||||
seatClassId: 'seat-class-uuid',
|
||||
bookingType: 'TRANSIT',
|
||||
leg2ScheduleId: 'leg2-schedule-uuid',
|
||||
leg2HoldId: 'leg2-hold-uuid',
|
||||
transitStationId: 'diredawa-station-uuid',
|
||||
leg2DestinationStationId: 'djibouti-station-uuid',
|
||||
displayCurrency: 'ETB',
|
||||
passengers: [{
|
||||
seatId: 'leg1-seat-uuid',
|
||||
leg2SeatId: 'leg2-seat-uuid',
|
||||
passengerName: 'Abebe Kebede',
|
||||
dateOfBirth: '1990-05-15',
|
||||
idDocumentType: 'NATIONAL_ID',
|
||||
idDocumentNumber: 'ET123456789',
|
||||
nationality: 'Ethiopian',
|
||||
phone: '+251911234567',
|
||||
}],
|
||||
deviceId: 'device-uuid-123',
|
||||
scheduleId: "leg1-schedule-uuid",
|
||||
holdId: "leg1-hold-uuid",
|
||||
originStationId: "addis-station-uuid",
|
||||
destinationStationId: "diredawa-station-uuid",
|
||||
seatClassId: "seat-class-uuid",
|
||||
bookingType: "TRANSIT",
|
||||
leg2ScheduleId: "leg2-schedule-uuid",
|
||||
leg2HoldId: "leg2-hold-uuid",
|
||||
transitStationId: "diredawa-station-uuid",
|
||||
leg2DestinationStationId: "djibouti-station-uuid",
|
||||
displayCurrency: "ETB",
|
||||
passengers: [
|
||||
{
|
||||
seatId: "leg1-seat-uuid",
|
||||
leg2SeatId: "leg2-seat-uuid",
|
||||
passengerName: "Abebe Kebede",
|
||||
dateOfBirth: "1990-05-15",
|
||||
idDocumentType: "NATIONAL_ID",
|
||||
idDocumentNumber: "ET123456789",
|
||||
nationality: "Ethiopian",
|
||||
phone: "+251911234567",
|
||||
},
|
||||
],
|
||||
deviceId: "device-uuid-123",
|
||||
},
|
||||
},
|
||||
ROUND_TRIP_TRANSIT: {
|
||||
summary: 'ROUND_TRIP_TRANSIT — round trip via connecting trains, 4 holds (guest)',
|
||||
summary:
|
||||
"ROUND_TRIP_TRANSIT — round trip via connecting trains, 4 holds (guest)",
|
||||
value: {
|
||||
scheduleId: 'ob-leg1-schedule-uuid',
|
||||
holdId: 'ob-leg1-hold-uuid',
|
||||
originStationId: 'addis-station-uuid',
|
||||
destinationStationId: 'diredawa-station-uuid',
|
||||
seatClassId: 'seat-class-uuid',
|
||||
bookingType: 'ROUND_TRIP_TRANSIT',
|
||||
leg2ScheduleId: 'ob-leg2-schedule-uuid',
|
||||
leg2HoldId: 'ob-leg2-hold-uuid',
|
||||
transitStationId: 'diredawa-station-uuid',
|
||||
leg2DestinationStationId: 'djibouti-station-uuid',
|
||||
returnScheduleId: 'ret-leg1-schedule-uuid',
|
||||
returnHoldId: 'ret-leg1-hold-uuid',
|
||||
returnOriginStationId: 'djibouti-station-uuid',
|
||||
returnDestinationStationId: 'diredawa-station-uuid',
|
||||
returnLeg2ScheduleId: 'ret-leg2-schedule-uuid',
|
||||
returnLeg2HoldId: 'ret-leg2-hold-uuid',
|
||||
returnTransitStationId: 'diredawa-station-uuid',
|
||||
returnLeg2DestinationStationId: 'addis-station-uuid',
|
||||
displayCurrency: 'ETB',
|
||||
passengers: [{
|
||||
seatId: 'ob-leg1-seat-uuid',
|
||||
leg2SeatId: 'ob-leg2-seat-uuid',
|
||||
returnSeatId: 'ret-leg1-seat-uuid',
|
||||
returnLeg2SeatId: 'ret-leg2-seat-uuid',
|
||||
passengerName: 'Abebe Kebede',
|
||||
dateOfBirth: '1990-05-15',
|
||||
idDocumentType: 'NATIONAL_ID',
|
||||
idDocumentNumber: 'ET123456789',
|
||||
nationality: 'Ethiopian',
|
||||
phone: '+251911234567',
|
||||
}],
|
||||
deviceId: 'device-uuid-123',
|
||||
scheduleId: "ob-leg1-schedule-uuid",
|
||||
holdId: "ob-leg1-hold-uuid",
|
||||
originStationId: "addis-station-uuid",
|
||||
destinationStationId: "diredawa-station-uuid",
|
||||
seatClassId: "seat-class-uuid",
|
||||
bookingType: "ROUND_TRIP_TRANSIT",
|
||||
leg2ScheduleId: "ob-leg2-schedule-uuid",
|
||||
leg2HoldId: "ob-leg2-hold-uuid",
|
||||
transitStationId: "diredawa-station-uuid",
|
||||
leg2DestinationStationId: "djibouti-station-uuid",
|
||||
returnScheduleId: "ret-leg1-schedule-uuid",
|
||||
returnHoldId: "ret-leg1-hold-uuid",
|
||||
returnOriginStationId: "djibouti-station-uuid",
|
||||
returnDestinationStationId: "diredawa-station-uuid",
|
||||
returnLeg2ScheduleId: "ret-leg2-schedule-uuid",
|
||||
returnLeg2HoldId: "ret-leg2-hold-uuid",
|
||||
returnTransitStationId: "diredawa-station-uuid",
|
||||
returnLeg2DestinationStationId: "addis-station-uuid",
|
||||
displayCurrency: "ETB",
|
||||
passengers: [
|
||||
{
|
||||
seatId: "ob-leg1-seat-uuid",
|
||||
leg2SeatId: "ob-leg2-seat-uuid",
|
||||
returnSeatId: "ret-leg1-seat-uuid",
|
||||
returnLeg2SeatId: "ret-leg2-seat-uuid",
|
||||
passengerName: "Abebe Kebede",
|
||||
dateOfBirth: "1990-05-15",
|
||||
idDocumentType: "NATIONAL_ID",
|
||||
idDocumentNumber: "ET123456789",
|
||||
nationality: "Ethiopian",
|
||||
phone: "+251911234567",
|
||||
},
|
||||
],
|
||||
deviceId: "device-uuid-123",
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 201, description: 'Booking created successfully with fareBreakdown' })
|
||||
@ApiResponse({ status: 400, description: 'Missing required seat IDs for bookingType, or Verifayda verification failed' })
|
||||
@ApiResponse({
|
||||
status: 201,
|
||||
description: "Booking created successfully with fareBreakdown",
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description:
|
||||
"Missing required seat IDs for bookingType, or Verifayda verification failed",
|
||||
})
|
||||
createGuest(@Req() req: any, @Body() dto: CreateGuestBookingDto) {
|
||||
return this.guestService.createGuestBooking(dto, req);
|
||||
}
|
||||
|
||||
@Get('saved-passengers')
|
||||
@SetMetadata('isPublic', true)
|
||||
@Get("saved-passengers")
|
||||
@SetMetadata("isPublic", true)
|
||||
@ApiOperation({
|
||||
summary: 'Get saved passenger profiles',
|
||||
description: 'Retrieve saved passenger details by userId (if logged in) or deviceId (for guest users)'
|
||||
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' })
|
||||
@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 | ROUND_TRIP | TRANSIT | ROUND_TRIP_TRANSIT',
|
||||
@ApiBearerAuth("JWT-auth")
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Create booking — ONE_WAY | ROUND_TRIP | TRANSIT | ROUND_TRIP_TRANSIT",
|
||||
description: `Creates a booking for a logged-in passenger. bookingType controls which fields are required.
|
||||
|
||||
**ONE_WAY**
|
||||
@@ -306,164 +390,193 @@ export class BookingsController {
|
||||
|
||||
**Age-Based Pricing (all types)**
|
||||
- ADULT (≥5 years): full fare per leg
|
||||
- CHILD (<5 years): first child FREE per booking, subsequent children full fare`
|
||||
- CHILD (<5 years): first child FREE per booking, subsequent children full fare`,
|
||||
})
|
||||
@ApiBody({
|
||||
type: CreateBookingDto,
|
||||
examples: {
|
||||
ONE_WAY: {
|
||||
summary: 'ONE_WAY — single direct journey',
|
||||
summary: "ONE_WAY — single direct journey",
|
||||
value: {
|
||||
passengerId: 'passenger-uuid',
|
||||
scheduleId: 'schedule-uuid',
|
||||
holdId: 'hold-uuid',
|
||||
originStationId: 'station-uuid',
|
||||
destinationStationId: 'station-uuid',
|
||||
seatClassId: 'seat-class-uuid',
|
||||
bookingType: 'ONE_WAY',
|
||||
displayCurrency: 'ETB',
|
||||
passengers: [{
|
||||
seatId: 'seat-uuid',
|
||||
passengerName: 'Abebe Kebede',
|
||||
dateOfBirth: '1990-05-15',
|
||||
idDocumentType: 'NATIONAL_ID',
|
||||
idDocumentNumber: 'ET123456789',
|
||||
nationality: 'Ethiopian',
|
||||
}],
|
||||
passengerId: "passenger-uuid",
|
||||
scheduleId: "schedule-uuid",
|
||||
holdId: "hold-uuid",
|
||||
originStationId: "station-uuid",
|
||||
destinationStationId: "station-uuid",
|
||||
seatClassId: "seat-class-uuid",
|
||||
bookingType: "ONE_WAY",
|
||||
displayCurrency: "ETB",
|
||||
passengers: [
|
||||
{
|
||||
seatId: "seat-uuid",
|
||||
passengerName: "Abebe Kebede",
|
||||
dateOfBirth: "1990-05-15",
|
||||
idDocumentType: "NATIONAL_ID",
|
||||
idDocumentNumber: "ET123456789",
|
||||
nationality: "Ethiopian",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
ROUND_TRIP: {
|
||||
summary: 'ROUND_TRIP — outbound + return, single PNR',
|
||||
summary: "ROUND_TRIP — outbound + return, single PNR",
|
||||
value: {
|
||||
passengerId: 'passenger-uuid',
|
||||
scheduleId: 'outbound-schedule-uuid',
|
||||
holdId: 'outbound-hold-uuid',
|
||||
originStationId: 'addis-station-uuid',
|
||||
destinationStationId: 'djibouti-station-uuid',
|
||||
seatClassId: 'seat-class-uuid',
|
||||
bookingType: 'ROUND_TRIP',
|
||||
returnScheduleId: 'return-schedule-uuid',
|
||||
returnHoldId: 'return-hold-uuid',
|
||||
returnOriginStationId: 'djibouti-station-uuid',
|
||||
returnDestinationStationId: 'addis-station-uuid',
|
||||
returnSeatClassId: 'seat-class-uuid',
|
||||
displayCurrency: 'ETB',
|
||||
passengers: [{
|
||||
seatId: 'outbound-seat-uuid',
|
||||
returnSeatId: 'return-seat-uuid',
|
||||
passengerName: 'Abebe Kebede',
|
||||
dateOfBirth: '1990-05-15',
|
||||
idDocumentType: 'NATIONAL_ID',
|
||||
idDocumentNumber: 'ET123456789',
|
||||
nationality: 'Ethiopian',
|
||||
}],
|
||||
passengerId: "passenger-uuid",
|
||||
scheduleId: "outbound-schedule-uuid",
|
||||
holdId: "outbound-hold-uuid",
|
||||
originStationId: "addis-station-uuid",
|
||||
destinationStationId: "djibouti-station-uuid",
|
||||
seatClassId: "seat-class-uuid",
|
||||
bookingType: "ROUND_TRIP",
|
||||
returnScheduleId: "return-schedule-uuid",
|
||||
returnHoldId: "return-hold-uuid",
|
||||
returnOriginStationId: "djibouti-station-uuid",
|
||||
returnDestinationStationId: "addis-station-uuid",
|
||||
returnSeatClassId: "seat-class-uuid",
|
||||
displayCurrency: "ETB",
|
||||
passengers: [
|
||||
{
|
||||
seatId: "outbound-seat-uuid",
|
||||
returnSeatId: "return-seat-uuid",
|
||||
passengerName: "Abebe Kebede",
|
||||
dateOfBirth: "1990-05-15",
|
||||
idDocumentType: "NATIONAL_ID",
|
||||
idDocumentNumber: "ET123456789",
|
||||
nationality: "Ethiopian",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
TRANSIT: {
|
||||
summary: 'TRANSIT — connecting train, single PNR',
|
||||
summary: "TRANSIT — connecting train, single PNR",
|
||||
value: {
|
||||
passengerId: 'passenger-uuid',
|
||||
scheduleId: 'leg1-schedule-uuid',
|
||||
holdId: 'leg1-hold-uuid',
|
||||
originStationId: 'addis-station-uuid',
|
||||
destinationStationId: 'diredawa-station-uuid',
|
||||
seatClassId: 'seat-class-uuid',
|
||||
bookingType: 'TRANSIT',
|
||||
leg2ScheduleId: 'leg2-schedule-uuid',
|
||||
leg2HoldId: 'leg2-hold-uuid',
|
||||
transitStationId: 'diredawa-station-uuid',
|
||||
leg2DestinationStationId: 'djibouti-station-uuid',
|
||||
displayCurrency: 'ETB',
|
||||
passengers: [{
|
||||
seatId: 'leg1-seat-uuid',
|
||||
leg2SeatId: 'leg2-seat-uuid',
|
||||
passengerName: 'Abebe Kebede',
|
||||
dateOfBirth: '1990-05-15',
|
||||
idDocumentType: 'NATIONAL_ID',
|
||||
idDocumentNumber: 'ET123456789',
|
||||
nationality: 'Ethiopian',
|
||||
}],
|
||||
passengerId: "passenger-uuid",
|
||||
scheduleId: "leg1-schedule-uuid",
|
||||
holdId: "leg1-hold-uuid",
|
||||
originStationId: "addis-station-uuid",
|
||||
destinationStationId: "diredawa-station-uuid",
|
||||
seatClassId: "seat-class-uuid",
|
||||
bookingType: "TRANSIT",
|
||||
leg2ScheduleId: "leg2-schedule-uuid",
|
||||
leg2HoldId: "leg2-hold-uuid",
|
||||
transitStationId: "diredawa-station-uuid",
|
||||
leg2DestinationStationId: "djibouti-station-uuid",
|
||||
displayCurrency: "ETB",
|
||||
passengers: [
|
||||
{
|
||||
seatId: "leg1-seat-uuid",
|
||||
leg2SeatId: "leg2-seat-uuid",
|
||||
passengerName: "Abebe Kebede",
|
||||
dateOfBirth: "1990-05-15",
|
||||
idDocumentType: "NATIONAL_ID",
|
||||
idDocumentNumber: "ET123456789",
|
||||
nationality: "Ethiopian",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
ROUND_TRIP_TRANSIT: {
|
||||
summary: 'ROUND_TRIP_TRANSIT — round trip via connecting trains, 4 holds',
|
||||
summary:
|
||||
"ROUND_TRIP_TRANSIT — round trip via connecting trains, 4 holds",
|
||||
value: {
|
||||
passengerId: 'passenger-uuid',
|
||||
scheduleId: 'ob-leg1-schedule-uuid',
|
||||
holdId: 'ob-leg1-hold-uuid',
|
||||
originStationId: 'addis-station-uuid',
|
||||
destinationStationId: 'diredawa-station-uuid',
|
||||
seatClassId: 'seat-class-uuid',
|
||||
bookingType: 'ROUND_TRIP_TRANSIT',
|
||||
leg2ScheduleId: 'ob-leg2-schedule-uuid',
|
||||
leg2HoldId: 'ob-leg2-hold-uuid',
|
||||
transitStationId: 'diredawa-station-uuid',
|
||||
leg2DestinationStationId: 'djibouti-station-uuid',
|
||||
returnScheduleId: 'ret-leg1-schedule-uuid',
|
||||
returnHoldId: 'ret-leg1-hold-uuid',
|
||||
returnOriginStationId: 'djibouti-station-uuid',
|
||||
returnDestinationStationId: 'diredawa-station-uuid',
|
||||
returnLeg2ScheduleId: 'ret-leg2-schedule-uuid',
|
||||
returnLeg2HoldId: 'ret-leg2-hold-uuid',
|
||||
returnTransitStationId: 'diredawa-station-uuid',
|
||||
returnLeg2DestinationStationId: 'addis-station-uuid',
|
||||
displayCurrency: 'ETB',
|
||||
passengers: [{
|
||||
seatId: 'ob-leg1-seat-uuid',
|
||||
leg2SeatId: 'ob-leg2-seat-uuid',
|
||||
returnSeatId: 'ret-leg1-seat-uuid',
|
||||
returnLeg2SeatId: 'ret-leg2-seat-uuid',
|
||||
passengerName: 'Abebe Kebede',
|
||||
dateOfBirth: '1990-05-15',
|
||||
idDocumentType: 'NATIONAL_ID',
|
||||
idDocumentNumber: 'ET123456789',
|
||||
nationality: 'Ethiopian',
|
||||
}],
|
||||
passengerId: "passenger-uuid",
|
||||
scheduleId: "ob-leg1-schedule-uuid",
|
||||
holdId: "ob-leg1-hold-uuid",
|
||||
originStationId: "addis-station-uuid",
|
||||
destinationStationId: "diredawa-station-uuid",
|
||||
seatClassId: "seat-class-uuid",
|
||||
bookingType: "ROUND_TRIP_TRANSIT",
|
||||
leg2ScheduleId: "ob-leg2-schedule-uuid",
|
||||
leg2HoldId: "ob-leg2-hold-uuid",
|
||||
transitStationId: "diredawa-station-uuid",
|
||||
leg2DestinationStationId: "djibouti-station-uuid",
|
||||
returnScheduleId: "ret-leg1-schedule-uuid",
|
||||
returnHoldId: "ret-leg1-hold-uuid",
|
||||
returnOriginStationId: "djibouti-station-uuid",
|
||||
returnDestinationStationId: "diredawa-station-uuid",
|
||||
returnLeg2ScheduleId: "ret-leg2-schedule-uuid",
|
||||
returnLeg2HoldId: "ret-leg2-hold-uuid",
|
||||
returnTransitStationId: "diredawa-station-uuid",
|
||||
returnLeg2DestinationStationId: "addis-station-uuid",
|
||||
displayCurrency: "ETB",
|
||||
passengers: [
|
||||
{
|
||||
seatId: "ob-leg1-seat-uuid",
|
||||
leg2SeatId: "ob-leg2-seat-uuid",
|
||||
returnSeatId: "ret-leg1-seat-uuid",
|
||||
returnLeg2SeatId: "ret-leg2-seat-uuid",
|
||||
passengerName: "Abebe Kebede",
|
||||
dateOfBirth: "1990-05-15",
|
||||
idDocumentType: "NATIONAL_ID",
|
||||
idDocumentNumber: "ET123456789",
|
||||
nationality: "Ethiopian",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 201, description: 'Booking created with fare breakdown' })
|
||||
@ApiResponse({ status: 400, description: 'Missing required fields for bookingType, or Verifayda verification failed' })
|
||||
@ApiResponse({ status: 404, description: 'Schedule or seat hold not found' })
|
||||
create(@Body() dto: CreateBookingDto) {
|
||||
return this.service.create(dto);
|
||||
}
|
||||
|
||||
@Get(':id/usage')
|
||||
@SetMetadata('isPublic', true)
|
||||
@ApiOperation({
|
||||
description: 'Returns list of modules/data that reference this booking'
|
||||
@ApiResponse({
|
||||
status: 201,
|
||||
description: "Booking created with fare breakdown",
|
||||
})
|
||||
@ApiResponse({ status: 200, description: 'Usage information retrieved' })
|
||||
@ApiResponse({ status: 404, description: 'Booking not found' })
|
||||
checkUsage(@Param('id') id: string) {
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description:
|
||||
"Missing required fields for bookingType, or Verifayda verification failed",
|
||||
})
|
||||
@ApiResponse({ status: 404, description: "Schedule or seat hold not found" })
|
||||
create(@Body() dto: CreateBookingDto) {
|
||||
return this.service.create(dto);
|
||||
}
|
||||
|
||||
@Get(":id/usage")
|
||||
@SetMetadata("isPublic", true)
|
||||
@ApiOperation({
|
||||
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);
|
||||
}
|
||||
|
||||
@Get('by-phone')
|
||||
@SetMetadata('isPublic', true)
|
||||
@Get("by-phone")
|
||||
@SetMetadata("isPublic", true)
|
||||
@ApiOperation({
|
||||
summary: 'Find bookings by phone number (no auth required)',
|
||||
summary: "Find bookings by phone number (no auth required)",
|
||||
description: `Returns all bookings where the contact phone matches the provided number.
|
||||
Accepts Ethiopian local format (09XXXXXXXX) and international format (+251XXXXXXXXX).
|
||||
Results are ordered most-recent first. Use the returned \`bookingRef\` to open booking detail.`
|
||||
Results are ordered most-recent first. Use the returned \`bookingRef\` to open booking detail.`,
|
||||
})
|
||||
@ApiQuery({ name: 'phone', required: true, description: 'Phone number in local (09…) or international (+251…) format' })
|
||||
@ApiQuery({ name: 'status', required: false, description: 'Filter by booking status' })
|
||||
@ApiQuery({ name: 'page', required: false })
|
||||
@ApiQuery({ name: 'pageSize', required: false })
|
||||
@ApiResponse({ status: 200, description: 'Paginated list of bookings for this phone number' })
|
||||
@ApiResponse({ status: 400, description: 'Phone number missing or invalid' })
|
||||
@ApiQuery({
|
||||
name: "phone",
|
||||
required: true,
|
||||
description: "Phone number in local (09…) or international (+251…) format",
|
||||
})
|
||||
@ApiQuery({
|
||||
name: "status",
|
||||
required: false,
|
||||
description: "Filter by booking status",
|
||||
})
|
||||
@ApiQuery({ name: "page", required: false })
|
||||
@ApiQuery({ name: "pageSize", required: false })
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: "Paginated list of bookings for this phone number",
|
||||
})
|
||||
@ApiResponse({ status: 400, description: "Phone number missing or invalid" })
|
||||
findByPhone(
|
||||
@Query('phone') phone?: string,
|
||||
@Query('status') status?: string,
|
||||
@Query('page') page?: string,
|
||||
@Query('pageSize') pageSize?: string,
|
||||
@Query("phone") phone?: string,
|
||||
@Query("status") status?: string,
|
||||
@Query("page") page?: string,
|
||||
@Query("pageSize") pageSize?: string,
|
||||
) {
|
||||
if (!phone?.trim()) throw new BadRequestException('Phone number is required');
|
||||
const digits = phone.replace(/[^\d]/g, '');
|
||||
if (digits.length < 7) throw new BadRequestException('Phone number is too short');
|
||||
if (!phone?.trim())
|
||||
throw new BadRequestException("Phone number is required");
|
||||
const digits = phone.replace(/[^\d]/g, "");
|
||||
if (digits.length < 7)
|
||||
throw new BadRequestException("Phone number is too short");
|
||||
return this.service.findByPhone(phone.trim(), {
|
||||
status,
|
||||
page: page ? parseInt(page) : 1,
|
||||
@@ -471,65 +584,82 @@ Results are ordered most-recent first. Use the returned \`bookingRef\` to open b
|
||||
});
|
||||
}
|
||||
|
||||
@Get(':bookingRef')
|
||||
@SetMetadata('isPublic', true)
|
||||
@Get(":bookingRef")
|
||||
@SetMetadata("isPublic", true)
|
||||
@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.'
|
||||
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);
|
||||
@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')
|
||||
|
||||
@Patch(":bookingRef/modify")
|
||||
@UseGuards(JwtGuard)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@ApiOperation({
|
||||
summary: 'Modify booking seats or trip',
|
||||
description: 'Allows modification of confirmed bookings before departure'
|
||||
@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",
|
||||
})
|
||||
@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')
|
||||
|
||||
@Delete(":id")
|
||||
@PassengerAdmin()
|
||||
@ApiBearerAuth('IAM-auth')
|
||||
@ApiBearerAuth("IAM-auth")
|
||||
@ApiOperation({
|
||||
description: 'Permanently deletes a booking record'
|
||||
description: "Permanently deletes a booking record",
|
||||
})
|
||||
@ApiQuery({ name: 'cascade', required: false, type: Boolean, description: 'Force delete with all related data' })
|
||||
@ApiResponse({ status: 200, description: 'Booking deleted successfully' })
|
||||
@ApiResponse({ status: 404, description: 'Booking not found' })
|
||||
delete(@Param('id') id: string, @Query('cascade') cascade?: string) {
|
||||
return this.service.delete(id, cascade === 'true');
|
||||
@ApiQuery({
|
||||
name: "cascade",
|
||||
required: false,
|
||||
type: Boolean,
|
||||
description: "Force delete with all related data",
|
||||
})
|
||||
@ApiResponse({ status: 200, description: "Booking deleted successfully" })
|
||||
@ApiResponse({ status: 404, description: "Booking not found" })
|
||||
delete(@Param("id") id: string, @Query("cascade") cascade?: string) {
|
||||
return this.service.delete(id, cascade === "true");
|
||||
}
|
||||
|
||||
@Patch(':id')
|
||||
@SetMetadata('isPublic', true)
|
||||
@Patch(":id")
|
||||
@SetMetadata("isPublic", true)
|
||||
@ApiOperation({
|
||||
description: 'Updates booking information for admin/agent operations'
|
||||
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);
|
||||
@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);
|
||||
}
|
||||
|
||||
@Delete(':bookingRef')
|
||||
@Delete(":bookingRef")
|
||||
@UseGuards(JwtGuard)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
@ApiOperation({
|
||||
summary: 'Cancel booking with refund',
|
||||
description: 'Cancels booking and processes refund (80% for confirmed bookings)'
|
||||
@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);
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user