diff --git a/apps/backend/src/application/controllers/csv-bookings.controller.ts b/apps/backend/src/application/controllers/csv-bookings.controller.ts index aba25fc..fed40dc 100644 --- a/apps/backend/src/application/controllers/csv-bookings.controller.ts +++ b/apps/backend/src/application/controllers/csv-bookings.controller.ts @@ -14,6 +14,7 @@ import { BadRequestException, ForbiddenException, ParseIntPipe, + ParseUUIDPipe, DefaultValuePipe, Inject, } from '@nestjs/common'; @@ -591,6 +592,31 @@ export class CsvBookingsController { return await this.csvBookingService.cancelBooking(id, userId); } + /** + * Delete an unpaid booking + * + * DELETE /api/v1/csv-bookings/:id + */ + @Delete(':id') + @UseGuards(JwtAuthGuard) + @ApiBearerAuth() + @ApiOperation({ + summary: 'Delete an unpaid booking', + description: + 'Permanently deletes a booking whose commission has not been paid. Only accessible by the booking owner. A paid booking has been sent to the carrier and can only be cancelled.', + }) + @ApiParam({ name: 'id', description: 'Booking ID (UUID)' }) + @ApiResponse({ status: 200, description: 'Booking deleted successfully' }) + @ApiResponse({ status: 400, description: 'Booking has been paid and cannot be deleted' }) + @ApiResponse({ status: 404, description: 'Booking not found' }) + @ApiResponse({ status: 401, description: 'Unauthorized' }) + async deleteBooking( + @Param('id', ParseUUIDPipe) id: string, + @Request() req: any + ): Promise<{ success: boolean; message: string }> { + return await this.csvBookingService.deleteBooking(id, req.user.id); + } + /** * Update booking cargo details before payment * diff --git a/apps/backend/src/application/services/csv-booking.service.ts b/apps/backend/src/application/services/csv-booking.service.ts index 3bb52a8..e4ac8a0 100644 --- a/apps/backend/src/application/services/csv-booking.service.ts +++ b/apps/backend/src/application/services/csv-booking.service.ts @@ -1024,6 +1024,44 @@ export class CsvBookingService { return this.toResponseDto(updatedBooking); } + /** + * Delete an unpaid booking (user action). + * + * Seul le proprietaire peut supprimer, et seulement tant qu'aucun paiement + * n'a ete encaisse — voir `CsvBooking.isDeletable()`. Une reservation payee + * est partie chez le transporteur : elle s'annule, elle ne s'efface pas. + * + * Les documents deja televerses restent dans le stockage objet, comme lors de + * la suppression d'un document isole : la politique du projet est de les + * conserver pour l'audit. + */ + async deleteBooking(id: string, userId: string): Promise<{ success: boolean; message: string }> { + this.logger.log(`Deleting booking ${id} by user ${userId}`); + + const booking = await this.csvBookingRepository.findById(id); + + if (!booking) { + throw new NotFoundException('Booking not found'); + } + + // Meme reponse qu'une reservation inexistante : appartenir a quelqu'un + // d'autre ne doit pas etre distinguable de ne pas exister. + if (booking.userId !== userId) { + throw new NotFoundException('Booking not found'); + } + + if (!booking.isDeletable()) { + throw new BadRequestException( + `Cannot delete a booking with status ${booking.status}. Only unpaid bookings can be deleted.` + ); + } + + await this.csvBookingRepository.delete(id); + this.logger.log(`Booking ${id} deleted`); + + return { success: true, message: 'Booking deleted successfully' }; + } + /** * Update the cargo details of a booking before payment (user action). *