import { BadRequestException, Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, ParseUUIDPipe, Patch, Post, Query, } from '@nestjs/common'; import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger'; import { BookingStaff } from '../../common/booking-guards'; import { FREIGHT_PERMS } from '../../seed/freight-permissions.registry'; import { CreateFirstMileDto } from './dto/create-first-mile.dto'; import { UpdateFirstMileDto } from './dto/update-first-mile.dto'; import { SetVehiclesDto } from './dto/set-vehicles.dto'; import { SetDistancesDto } from './dto/set-distances.dto'; import { FirstMileStatus } from './entities/first-mile.entity'; import { FirstMileService } from './first-mile.service'; import { FirstMileInvoiceService } from './first-mile-invoice.service'; @ApiTags('first-mile') @ApiBearerAuth() @Controller('first-mile') @BookingStaff(FREIGHT_PERMS.firstMile.view) export class FirstMileController { constructor( private readonly firstMileService: FirstMileService, private readonly firstMileInvoiceService: FirstMileInvoiceService, ) { } @Get() @ApiOperation({ summary: 'List first-mile legs' }) findAll( @Query('status') status?: string, @Query('bookingId') bookingId?: string, @Query('vehicleId') vehicleId?: string, @Query('page') page?: string, @Query('pageSize') pageSize?: string, @Query('sortBy') sortBy?: string, @Query('sortOrder') sortOrder?: 'ASC' | 'DESC', ) { return this.firstMileService.findAll({ status: status as FirstMileStatus | undefined, bookingId, vehicleId, page: page ? parseInt(page, 10) : undefined, pageSize: pageSize ? parseInt(pageSize, 10) : undefined, sortBy, sortOrder, }); } @Get(':id') @ApiOperation({ summary: 'Get a first-mile leg by ID' }) findOne(@Param('id', ParseUUIDPipe) id: string) { return this.firstMileService.findById(id); } @Get('acceptitem/:id') @BookingStaff(FREIGHT_PERMS.firstMile.accept) @ApiOperation({ summary: 'Get a first-mile accep by ID' }) acceptItem(@Param('id', ParseUUIDPipe) id: string) { return this.firstMileService.acceptBooking(id); } @Post('accept/:reference') @BookingStaff(FREIGHT_PERMS.firstMile.accept) @ApiOperation({ summary: 'Accept a paid booking and create a first-mile leg' }) acceptBooking(@Param('reference') reference: string) { return this.firstMileService.acceptBookingByReference(reference); } @Post() @BookingStaff(FREIGHT_PERMS.firstMile.create) @ApiOperation({ summary: 'Create a first-mile leg' }) create(@Body() dto: CreateFirstMileDto) { return this.firstMileService.create(dto); } @Patch(':id') @BookingStaff(FREIGHT_PERMS.firstMile.update) @ApiOperation({ summary: 'Update a first-mile leg' }) async update(@Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateFirstMileDto) { // No invoice side-effects — invoices are generated only via the explicit // POST :id/invoice endpoint (the "Generate Invoice" action). return this.firstMileService.update(id, dto); } @Post(':id/invoice') @BookingStaff(FREIGHT_PERMS.firstMile.generateInvoice) @ApiOperation({ summary: 'Generate the first-mile delivery-fee invoice' }) async generateInvoice(@Param('id', ParseUUIDPipe) id: string) { const record = await this.firstMileService.findById(id); const invoice = await this.firstMileInvoiceService.ensureInvoiceFor(record); if (!invoice) { throw new BadRequestException( 'Cannot generate invoice: the leg has no billable amount. Add distance and ensure a FIRST_MILE rate is configured, and the booking has a company.', ); } return invoice; } @Post(':id/vehicles') @BookingStaff(FREIGHT_PERMS.firstMile.assignVehicles) @ApiOperation({ summary: 'Set the vehicles assigned to a first-mile pickup (multi-truck)' }) async setVehicles( @Param('id', ParseUUIDPipe) id: string, @Body() dto: SetVehiclesDto, ) { return this.firstMileService.setVehicles(id, dto.vehicles); } @Post(':id/distances') @BookingStaff(FREIGHT_PERMS.firstMile.setDistances) @ApiOperation({ summary: 'Set per-vehicle actual distances (does not generate an invoice)' }) async setDistances( @Param('id', ParseUUIDPipe) id: string, @Body() dto: SetDistancesDto, ) { return this.firstMileService.setDistances(id, dto.distances, dto.remainingPayment); } @Delete(':id') @BookingStaff(FREIGHT_PERMS.firstMile.delete) @HttpCode(HttpStatus.NO_CONTENT) @ApiOperation({ summary: 'Soft-delete a first-mile leg' }) remove(@Param('id', ParseUUIDPipe) id: string) { return this.firstMileService.remove(id); } }