mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 07:38:10 +00:00
Merge branch 'dev' into freight/nati-2
This commit is contained in:
@@ -72,6 +72,12 @@ import { GenerateGrnDto } from './dto/generate-grn.dto';
|
||||
import { ContainerReceiptService } from './container-receipt.service';
|
||||
import { SignContractDto } from './dto/sign-contract.dto';
|
||||
import { UpdateBookingDto } from './dto/update-booking.dto';
|
||||
import { BookingWagonCancellationService } from './booking-wagon-cancellation.service';
|
||||
import {
|
||||
FilterWagonCancellationsDto,
|
||||
RebookCancelledWagonsDto,
|
||||
RequestWagonCancellationDto,
|
||||
} from './dto/wagon-cancellation.dto';
|
||||
import {
|
||||
type AuthUserPayload,
|
||||
resolveAuthUserId,
|
||||
@@ -151,6 +157,7 @@ export class BookingsController {
|
||||
private readonly firstMileService: FirstMileService,
|
||||
private readonly lastMileService: LastMileService,
|
||||
private readonly userTradeAccessService: UserTradeAccessService,
|
||||
private readonly wagonCancellationService: BookingWagonCancellationService,
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
@@ -507,6 +514,150 @@ export class BookingsController {
|
||||
res.send(buffer);
|
||||
}
|
||||
|
||||
@Get(':id/wagons')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Allocated wagons for a booking (JSON) — empty until the paid booking is placed on a train',
|
||||
})
|
||||
async wagonAllocations(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
const booking = await this.bookingsService.findById(id);
|
||||
if (!hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) {
|
||||
await this.bookingsService.assertCustomerCanAccessBooking(user?.id, booking);
|
||||
}
|
||||
return this.bookingsService.wagonAllocations(id);
|
||||
}
|
||||
|
||||
// ── Partial wagon cancellation (paid bookings) ────────────────────────────
|
||||
// Customer endpoints are ownership-scoped (no portal permission keys); the
|
||||
// staff history/void/rebook variants are permission-gated below.
|
||||
|
||||
@Post(':id/wagon-cancellations/preview')
|
||||
@ApiOperation({ summary: 'Preview the fee/credit of a partial wagon cancellation (no writes)' })
|
||||
async previewWagonCancellation(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: RequestWagonCancellationDto,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
const booking = await this.bookingsService.findById(id);
|
||||
if (!hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) {
|
||||
await this.bookingsService.assertCustomerCanAccessBooking(user?.id, booking);
|
||||
}
|
||||
return this.wagonCancellationService.previewCancellation(id, dto);
|
||||
}
|
||||
|
||||
@Post(':id/wagon-cancellations')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Request a partial wagon cancellation on a PAID booking — opens the cancellation-fee invoice; wagons are released only once the fee settles',
|
||||
})
|
||||
async requestWagonCancellation(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@Body() dto: RequestWagonCancellationDto,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
const booking = await this.bookingsService.findById(id);
|
||||
if (!hasFreightPermission(user, FREIGHT_PERMS.bookings.view)) {
|
||||
await this.bookingsService.assertCustomerCanAccessBooking(user?.id, booking);
|
||||
}
|
||||
return this.wagonCancellationService.requestCancellation(id, dto, user?.id);
|
||||
}
|
||||
|
||||
@Get(':id/wagon-cancellations')
|
||||
@ApiOperation({ summary: 'Wagon-cancellation history of one booking (owner or staff)' })
|
||||
async listBookingWagonCancellations(
|
||||
@Param('id', ParseUUIDPipe) id: string,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
const booking = await this.bookingsService.findById(id);
|
||||
const staff =
|
||||
hasFreightPermission(user, FREIGHT_PERMS.bookings.view) ||
|
||||
hasFreightPermission(user, FREIGHT_PERMS.bookings.wagonCancellationView);
|
||||
if (!staff) {
|
||||
await this.bookingsService.assertCustomerCanAccessBooking(user?.id, booking);
|
||||
}
|
||||
return this.wagonCancellationService.list({ bookingId: id, pageSize: 100 });
|
||||
}
|
||||
|
||||
@Get('wagon-cancellations/my')
|
||||
@ApiOperation({ summary: 'Wagon-cancellation history of the calling customer (paginated, filterable)' })
|
||||
async listMyWagonCancellations(
|
||||
@Query() filter: FilterWagonCancellationsDto,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
const companyId = await this.bookingsService.resolveCustomerCompanyId(user?.id ?? '');
|
||||
if (!companyId) throw new ForbiddenException('No customer company for this user.');
|
||||
return this.wagonCancellationService.list({
|
||||
companyId,
|
||||
status: filter.statuses,
|
||||
search: filter.search,
|
||||
from: filter.from ? new Date(filter.from) : undefined,
|
||||
to: filter.to ? new Date(filter.to) : undefined,
|
||||
page: filter.page,
|
||||
pageSize: filter.pageSize,
|
||||
});
|
||||
}
|
||||
|
||||
@Get('wagon-cancellations/history')
|
||||
@WagonCancellationView()
|
||||
@ApiOperation({ summary: 'All wagon cancellations (staff, paginated, filterable)' })
|
||||
async listAllWagonCancellations(@Query() filter: FilterWagonCancellationsDto) {
|
||||
return this.wagonCancellationService.list({
|
||||
status: filter.statuses,
|
||||
search: filter.search,
|
||||
from: filter.from ? new Date(filter.from) : undefined,
|
||||
to: filter.to ? new Date(filter.to) : undefined,
|
||||
page: filter.page,
|
||||
pageSize: filter.pageSize,
|
||||
});
|
||||
}
|
||||
|
||||
@Post('wagon-cancellations/:cancellationId/withdraw')
|
||||
@ApiOperation({ summary: 'Withdraw a fee-pending wagon cancellation (owner, or staff with the void permission)' })
|
||||
async withdrawWagonCancellation(
|
||||
@Param('cancellationId', ParseUUIDPipe) cancellationId: string,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
await this.assertWagonCancellationActor(
|
||||
cancellationId,
|
||||
user,
|
||||
FREIGHT_PERMS.bookings.wagonCancellationVoid,
|
||||
);
|
||||
return this.wagonCancellationService.withdraw(cancellationId);
|
||||
}
|
||||
|
||||
@Post('wagon-cancellations/:cancellationId/rebook')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'Rebook a wagon-cancellation credit: pick a shipment day only — the new booking is created under the contract and marked PAID (freight already paid; contract must still be valid)',
|
||||
})
|
||||
async rebookWagonCancellation(
|
||||
@Param('cancellationId', ParseUUIDPipe) cancellationId: string,
|
||||
@Body() dto: RebookCancelledWagonsDto,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
await this.assertWagonCancellationActor(
|
||||
cancellationId,
|
||||
user,
|
||||
FREIGHT_PERMS.bookings.wagonCancellationRebook,
|
||||
);
|
||||
return this.wagonCancellationService.rebook(cancellationId, dto, user?.id);
|
||||
}
|
||||
|
||||
/** Owner-or-staff gate shared by the per-cancellation actions. */
|
||||
private async assertWagonCancellationActor(
|
||||
cancellationId: string,
|
||||
user: TCurrentUser,
|
||||
staffPermission: string,
|
||||
): Promise<void> {
|
||||
if (hasFreightPermission(user, staffPermission)) return;
|
||||
const row = await this.wagonCancellationService.findById(cancellationId);
|
||||
const booking = await this.bookingsService.findById(row.bookingId);
|
||||
await this.bookingsService.assertCustomerCanAccessBooking(user?.id, booking);
|
||||
}
|
||||
|
||||
@Get(':id/customer-trucks')
|
||||
@MixedAudience([
|
||||
FREIGHT_PERMS.bookings.view,
|
||||
@@ -1382,6 +1533,19 @@ export class BookingsController {
|
||||
return this.transitionService.enrichBookingResponse(booking);
|
||||
}
|
||||
|
||||
@Post(":id/customer-cancel")
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"Customer cancels their own booking before payment — no cancellation fee",
|
||||
})
|
||||
async customerCancel(
|
||||
@Param("id", ParseUUIDPipe) id: string,
|
||||
@Body() dto: RejectBookingDto,
|
||||
) {
|
||||
const booking = await this.transitionService.customerCancel(id, dto.reason);
|
||||
return this.transitionService.enrichBookingResponse(booking);
|
||||
}
|
||||
|
||||
@Post(":id/cancel-hold")
|
||||
@PortalCustomer()
|
||||
@ApiOperation({
|
||||
|
||||
Reference in New Issue
Block a user