Merge branch 'dev' into quick-fix

This commit is contained in:
Stephanos A.
2026-07-19 08:33:16 +03:00
committed by GitHub
337 changed files with 16245 additions and 2090 deletions

View File

@@ -17,9 +17,11 @@ import {
ApiParam,
ApiQuery,
ApiResponse,
ApiBody,
} from "@nestjs/swagger";
import { SeatsService } from "./seats.service";
import { HoldSeatsDto, ReleaseHoldDto } from "./seats.dto";
import { GetDuplicateSeatsQuery, ResolveDuplicatesDto } from "./duplicate-seats.dto";
import { JwtGuard } from "../../common/jwt.guard";
import { PassengerStaff } from "../../common/passenger-guards";
import { PASSENGER_PERMS } from "../../seed/passenger-permissions.registry";
@@ -316,4 +318,90 @@ This makes it clear which segment of the route each seat is held for, enabling s
) {
return this.service.importSeatsCSV(body.scheduleId, body.csv, body.commit);
}
// ── Duplicate seat management (backoffice) ────────────────────────────────
@Get("duplicates")
@UseGuards(JwtGuard)
@ApiBearerAuth("JWT-auth")
@ApiOperation({
summary: "List duplicate seat assignments by schedule date",
description:
"Returns all schedules on the given date that have bookings sharing " +
"the same seat, grouped by coach. Each coach entry includes the duplicate " +
"groups (with full booking info) and the list of currently available seats " +
"that can be used for reassignment.",
})
@ApiQuery({ name: "date", example: "2026-07-17", description: "Schedule date (YYYY-MM-DD)" })
@ApiQuery({ name: "scheduleId", required: false, description: "Filter to a specific schedule" })
@ApiResponse({
status: 200,
description: "Duplicate seat report grouped by schedule → coach",
schema: {
example: {
date: "2026-07-17",
totalDuplicates: 1,
schedules: [{
scheduleId: "uuid",
departureAt: "2026-07-17T06:00:00.000Z",
origin: "Addis Ababa",
destination: "Dire Dawa",
coaches: [{
coachId: "uuid",
coachNumber: "C1",
coachTypeName: "SBC",
duplicates: [{
seatId: "uuid",
seatNumber: "12A",
leg: 1,
bookings: [
{ bookingSeatId: "uuid", bookingId: "uuid", bookingRef: "ATPC9F", passengerName: "Abebe", contactPhone: "+251911000000", createdAt: "2026-07-16T10:00:00.000Z" },
{ bookingSeatId: "uuid", bookingId: "uuid", bookingRef: "XYZ123", passengerName: "Kebede", contactPhone: "+251922000000", createdAt: "2026-07-16T11:00:00.000Z" },
],
}],
availableSeats: [
{ seatId: "uuid", seatNumber: "14B" },
{ seatId: "uuid", seatNumber: "15A" },
],
}],
}],
},
},
})
getDuplicateSeats(@Query() query: GetDuplicateSeatsQuery) {
return this.service.getDuplicateSeats(query.date, query.scheduleId);
}
@Post("duplicates/resolve")
@UseGuards(JwtGuard)
@ApiBearerAuth("JWT-auth")
@ApiOperation({
summary: "Auto-assign duplicate bookings to seats in selected coaches",
description:
"Staff selects which duplicate BookingSeat IDs to fix and which coaches to pull replacement seats from. " +
"The system automatically picks the first available (non-blocked, non-occupied) seat in the given coaches " +
"for each booking, updates BookingSeat + Ticket + JourneySegment atomically so the seatmap reflects the " +
"change immediately, then sends an SMS notification to the passenger. " +
"Coaches are searched in the order provided; seats within each coach are assigned by row then column.",
})
@ApiBody({ type: ResolveDuplicatesDto })
@ApiResponse({
status: 200,
description: "Resolution summary — resolved count, unresolved count, per-booking results",
schema: {
example: {
resolved: 2,
unresolved: 0,
results: [
{ bookingRef: "XYZ123", oldSeatNumber: "1A", newSeatNumber: "14B", contactPhone: "+251922000000" },
{ bookingRef: "ABC456", oldSeatNumber: "1A", newSeatNumber: "15A", contactPhone: "+251933000000" },
],
},
},
})
@ApiResponse({ status: 400, description: "Booking not in CONFIRMED/BOARDED status" })
@ApiResponse({ status: 404, description: "BookingSeat ID not found" })
resolveDuplicateSeats(@Body() dto: ResolveDuplicatesDto) {
return this.service.resolveDuplicateSeats(dto.bookingSeatIds, dto.coachIds);
}
}