Ticket generation updates

This commit is contained in:
Stephanos A
2026-07-22 13:43:38 +03:00
parent 44b4779ca6
commit 698dbd47e6
3 changed files with 42 additions and 10 deletions

View File

@@ -3,6 +3,7 @@ import {
Logger,
NotFoundException,
BadRequestException,
ConflictException,
} from "@nestjs/common";
import { PrismaService } from "../../common/prisma.service";
import { SeatsService } from "../seats/seats.service";
@@ -897,12 +898,25 @@ export class PaymentsService {
try {
await this.ticketsService.generate(booking.id);
} catch (err) {
this.logger.error(
`Error generating ticket for booking ${booking.id}: ${err instanceof Error ? err.message : String(err)}`,
);
// Re-throw so callers (e.g. force-confirm) know tickets weren't issued.
// Webhook handlers catch this themselves and still return 200 to avoid redelivery.
throw err;
const msg = err instanceof Error ? err.message : String(err);
// Only reassign seats when a *different* booking genuinely holds the seat
// (ConflictException). Any other error (transient DB issue, etc.) is logged
// and swallowed — the passenger keeps their original seat and the ticket can
// be retried via "Generate Missing" in the backoffice.
if (err instanceof ConflictException) {
this.logger.warn(
`Seat conflict for booking ${booking.id}: ${msg}. Attempting smart seat reassignment.`,
);
try {
await this.ticketsService.smartAssignAndGenerate(booking.id);
} catch (retryErr) {
this.logger.error(
`Smart assign also failed for booking ${booking.id}: ${retryErr instanceof Error ? retryErr.message : String(retryErr)}`,
);
}
} else {
this.logger.error(`Error generating ticket for booking ${booking.id}: ${msg}`);
}
}
try {

View File

@@ -352,8 +352,20 @@ export class TicketsService {
}
}
// Check for seat conflicts before deleting existing tickets or issuing new ones
await this.prisma.ticket.deleteMany({ where: { bookingId } });
// Remove any SeatBlock rows left over from a previous generate() run for this
// booking — they reference the old ticket IDs which are now deleted, and would
// otherwise cause the conflict check below to see this booking's own seats as
// blocked by another booking.
const seatIds = (booking as any).seats.map((bs: any) => bs.seatId);
await this.prisma.seatBlock.deleteMany({
where: { seatId: { in: seatIds }, blockedBy: 'SYSTEM', approvedBy: 'SYSTEM' },
});
// Check for seat conflicts — only seats confirmed/boarded by a *different* booking
// on the same schedule are a real conflict. SeatBlock rows created by a previous
// generate() run for this booking are NOT a conflict; they are cleaned up above.
const conflictingSeats = await this.prisma.bookingSeat.findMany({
where: {
seatId: { in: seatIds },
@@ -371,8 +383,6 @@ export class TicketsService {
);
}
await this.prisma.ticket.deleteMany({ where: { bookingId } });
// Generate one ticket per passenger per leg.
// Round-trip / transit bookings have seats on multiple legs — each leg needs its own
// ticket so the voucher can match by (passengerName, leg) and gate scanners can