Separate ticket generation for portal and back office

This commit is contained in:
Stephanos A
2026-07-22 11:06:16 +03:00
parent e6e2c88054
commit de17365802
2 changed files with 31 additions and 20 deletions

View File

@@ -1,7 +1,6 @@
import { Body, Controller, Get, Param, Post, Query, UseGuards, Delete, Patch, SetMetadata } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiBody, ApiQuery } from '@nestjs/swagger';
import { TicketsService } from './tickets.service';
import { JwtGuard } from '../../common/jwt.guard';
import { PassengerStaff, PassengerAdmin } from '../../common/passenger-guards';
import { PASSENGER_PERMS } from '../../seed/passenger-permissions.registry';
@@ -24,13 +23,23 @@ export class TicketsController {
}
@Post('generate/:bookingId')
@SetMetadata('isPublic', true)
@ApiOperation({
summary: 'Generate ticket for booking (confirmation page)',
description: 'Creates a ticket when confirmation page is reached and permanently holds all associated seats with SeatBlock records. Requires payment to be SUCCEEDED and booking to be CONFIRMED.'
})
generateTicket(@Param('bookingId') bookingId: string) {
return this.service.generate(bookingId);
}
@Post('force-generate/:bookingId')
@PassengerStaff(PASSENGER_PERMS.tickets.generate)
@ApiBearerAuth('IAM-auth')
@ApiOperation({
summary: 'Generate ticket for booking (confirmation page)',
description: 'Creates a ticket when confirmation page is reached and permanently holds all associated seats with SeatBlock records.'
summary: 'Force-generate ticket for booking (staff only)',
description: 'Staff override: regenerates tickets for a confirmed booking regardless of prior state.'
})
generateTicket(@Param('bookingId') bookingId: string) {
forceGenerateTicket(@Param('bookingId') bookingId: string) {
return this.service.generate(bookingId);
}
@@ -45,8 +54,8 @@ export class TicketsController {
}
@Get()
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@PassengerStaff(PASSENGER_PERMS.tickets.generate)
@ApiBearerAuth('IAM-auth')
@ApiOperation({ summary: 'List all tickets with optional filters' })
@ApiQuery({ name: 'search', required: false })
@ApiQuery({ name: 'status', required: false })
@@ -88,8 +97,8 @@ export class TicketsController {
}
@Get('by-order/:merchantOrderId')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@PassengerStaff(PASSENGER_PERMS.tickets.generate)
@ApiBearerAuth('IAM-auth')
@ApiOperation({
summary: 'Get ticket by merchant order ID',
description: 'Looks up the booking ID from the PaymentIntent using merchantOrderId, then returns the full ticket information.'
@@ -106,8 +115,8 @@ export class TicketsController {
}
@Post('scan-board/:qrCodeOrRef')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@PassengerStaff(PASSENGER_PERMS.tickets.generate)
@ApiBearerAuth('IAM-auth')
@ApiOperation({
summary: 'Scan QR code or booking ref and automatically board ticket',
description: 'Scans ticket QR code or booking reference and automatically boards the passenger. Handles errors like expired tickets, already used tickets, etc. Designed for mobile boarding interface.'
@@ -131,8 +140,8 @@ export class TicketsController {
}
@Post(':bookingRef/validate')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@PassengerStaff(PASSENGER_PERMS.tickets.generate)
@ApiBearerAuth('IAM-auth')
@ApiOperation({
summary: 'Validate ticket at gate with audit logging',
description: 'Validates ticket QR/barcode at station gate. For round-trip bookings, supply `leg` (OUTBOUND or RETURN) to record which leg is being used. Defaults to OUTBOUND if omitted. Records validation in audit log with timestamp, gate, and validator.'
@@ -162,24 +171,24 @@ export class TicketsController {
}
@Get(':ticketId/validation-logs')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@PassengerStaff(PASSENGER_PERMS.tickets.generate)
@ApiBearerAuth('IAM-auth')
@ApiOperation({ summary: 'Get validation logs for ticket' })
getValidationLogs(@Param('ticketId') ticketId: string) {
return this.service.getValidationLogs(ticketId);
}
@Get('offline/export')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@PassengerStaff(PASSENGER_PERMS.tickets.generate)
@ApiBearerAuth('IAM-auth')
@ApiOperation({ summary: 'Export tickets for offline validation' })
exportOfflineData(@Query('scheduleId') scheduleId: string) {
return this.service.exportOfflineData(scheduleId);
}
@Post('validate/offline')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@PassengerStaff(PASSENGER_PERMS.tickets.generate)
@ApiBearerAuth('IAM-auth')
@ApiOperation({
summary: 'Batch import offline validations',
description: 'Processes validations collected offline. Each entry may include an optional `leg` field (OUTBOUND | RETURN) for round-trip tickets. Deduplication is per bookingRef+leg combination so both legs of the same booking can be submitted in one batch.'
@@ -221,8 +230,8 @@ export class TicketsController {
}
@Patch(':id/restore')
@UseGuards(JwtGuard)
@ApiBearerAuth('JWT-auth')
@PassengerStaff(PASSENGER_PERMS.tickets.generate)
@ApiBearerAuth('IAM-auth')
@ApiOperation({ summary: 'Restore a cancelled ticket by resetting its status to ACTIVE' })
restore(@Param('id') id: string) {
return this.service.restore(id);

View File

@@ -48,6 +48,8 @@ export const bookingsApi = {
apiClient.post<any>(`/payments/${bookingId}/force-confirm`, data),
smartAssign: (bookingId: string) =>
apiClient.post<any>(`/tickets/smart-assign/${bookingId}`, {}),
forceGenerate: (bookingId: string) =>
apiClient.post<any>(`/tickets/force-generate/${bookingId}`, {}),
};
// Passengers API