mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 10:58:14 +00:00
feat(group-booking): draft schedules, coach-scoped seating, mixed classes
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { SearchService } from './search.service';
|
||||
import { SearchTripsDto } from './search.dto';
|
||||
import { PassengerWrite } from '../../common/passenger-guards';
|
||||
import { PASSENGER_PERMS } from '../../seed/passenger-permissions.registry';
|
||||
|
||||
/**
|
||||
* The staff group-booking search channel.
|
||||
*
|
||||
* Deliberately a SEPARATE controller from `SearchController` rather than another route on it.
|
||||
* `SearchController` carries a class-level `@IsPublic()`, and `JwtGuard` resolves that key with
|
||||
* `getAllAndOverride([handler, class])` — a handler with no key of its own inherits the class's
|
||||
* `true` and stays public. Worse, `JwtGuard` returns early for a public route and never
|
||||
* populates `request.user`, so an in-handler permission check there is not merely awkward, it is
|
||||
* impossible: there is no user to check.
|
||||
*
|
||||
* Splitting the controller is what makes the guard real. The channel is therefore decided by
|
||||
* which route the caller can reach, not by a field they send — which matters because this
|
||||
* channel exposes unpublished DRAFT schedules.
|
||||
*/
|
||||
@ApiTags('Search')
|
||||
@Controller('search')
|
||||
export class GroupSearchController {
|
||||
constructor(private service: SearchService) {}
|
||||
|
||||
@Post('group')
|
||||
@PassengerWrite(PASSENGER_PERMS.bookings.create, PASSENGER_PERMS.bookings.manage)
|
||||
@ApiBearerAuth('IAM-auth')
|
||||
@ApiOperation({
|
||||
summary: 'Search trips as staff for a group booking — includes unpublished DRAFT trips',
|
||||
description: `Same request body and response shape as the public \`POST /search\`, but run on the staff channel.
|
||||
|
||||
The staff channel is a strict **superset** of the public one:
|
||||
|
||||
| Schedule state | Public \`POST /search\` | This route |
|
||||
| --- | --- | --- |
|
||||
| \`DRAFT\` (not yet published) | hidden | **visible** |
|
||||
| \`SCHEDULED\`/\`BOARDING\`/\`EN_ROUTE\`, ordinary | visible | visible |
|
||||
| \`SCHEDULED\`/\`BOARDING\`/\`EN_ROUTE\`, \`isGroupBookingOnly\` | hidden | **visible** |
|
||||
| \`CANCELLED\`/\`ARRIVED\`/\`DELAYED\` | hidden | hidden |
|
||||
| \`isPackageOnly\` | hidden | hidden |
|
||||
|
||||
So a group can be booked onto an ordinary scheduled service, onto a trip reserved for groups, or onto a draft trip that has not gone on sale yet — all through the same seat inventory as normal booking.
|
||||
|
||||
Replaces the old \`channel: 'GROUP_BOOKING'\` body field on \`POST /search\`, which was unguarded.`,
|
||||
})
|
||||
@ApiResponse({ status: 200, description: 'Matching schedules, including DRAFT and group-reserved ones' })
|
||||
@ApiResponse({ status: 403, description: 'Caller lacks bookings:create / bookings:manage' })
|
||||
searchTripsForGroup(@Body() dto: SearchTripsDto) {
|
||||
return this.service.searchTrips(dto, true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user