From 52a9462228f3eb1dca14bc14baa90bfad28e3c7d Mon Sep 17 00:00:00 2001 From: Marshal Date: Tue, 16 Jun 2026 12:20:49 +0000 Subject: [PATCH] add booking endpoint for featch payable bookings --- .../modules/bookings/bookings.controller.ts | 14 +++++++++ .../modules/bookings/bookings.repository.ts | 12 +++++++ .../src/modules/bookings/bookings.service.ts | 31 +++++++++++++++++++ .../bookings/dto/filter-booking.dto.ts | 6 ++++ 4 files changed, 63 insertions(+) diff --git a/apps/edr-freight-api/src/modules/bookings/bookings.controller.ts b/apps/edr-freight-api/src/modules/bookings/bookings.controller.ts index 8631aa9e4..77d60b439 100644 --- a/apps/edr-freight-api/src/modules/bookings/bookings.controller.ts +++ b/apps/edr-freight-api/src/modules/bookings/bookings.controller.ts @@ -128,6 +128,20 @@ export class BookingsController { return this.bookingsService.getListSummary(filter); } + @Get('my') + @ApiOperation({ + summary: "List the current customer's bookings ready for payment", + description: + 'Bookings owned by the authenticated user\'s company that are payable ' + + '(FULLY_EXECUTED, SELECTED_FOR_BATCH, AWAITING_PAYMENT) and not yet PAID.', + }) + findMyPayable( + @CurrentUser() user: AuthUserPayload, + @Query() filter: FilterBookingDto, + ) { + return this.bookingsService.findMyPayable(resolveAuthUserId(user), filter); + } + @Get('queues/:queue') @ApiOperation({ summary: 'List bookings for a dashboard queue', diff --git a/apps/edr-freight-api/src/modules/bookings/bookings.repository.ts b/apps/edr-freight-api/src/modules/bookings/bookings.repository.ts index d1084fbab..3e95b917d 100644 --- a/apps/edr-freight-api/src/modules/bookings/bookings.repository.ts +++ b/apps/edr-freight-api/src/modules/bookings/bookings.repository.ts @@ -31,6 +31,8 @@ export interface BookingListFilterOptions { freightType?: string; tradeDirection?: string; paymentCurrency?: string; + paymentStatus?: string; + excludePaymentStatus?: string; allowConsolidation?: boolean; consolidationPaired?: string; } @@ -570,6 +572,16 @@ export class BookingsRepository extends BaseRepository { paymentCurrency: options.paymentCurrency, }); } + if (options.paymentStatus) { + qb.andWhere('booking.payment_status = :paymentStatus', { + paymentStatus: options.paymentStatus, + }); + } + if (options.excludePaymentStatus) { + qb.andWhere('booking.payment_status != :excludePaymentStatus', { + excludePaymentStatus: options.excludePaymentStatus, + }); + } if (options.allowConsolidation !== undefined) { qb.andWhere('booking.allow_consolidation = :allowConsolidation', { allowConsolidation: options.allowConsolidation, diff --git a/apps/edr-freight-api/src/modules/bookings/bookings.service.ts b/apps/edr-freight-api/src/modules/bookings/bookings.service.ts index c07a658da..d1c135152 100644 --- a/apps/edr-freight-api/src/modules/bookings/bookings.service.ts +++ b/apps/edr-freight-api/src/modules/bookings/bookings.service.ts @@ -552,6 +552,7 @@ export class BookingsService { freightType: filter.freightType, tradeDirection: filter.tradeDirection, paymentCurrency: filter.paymentCurrency, + paymentStatus: filter.paymentStatus, allowConsolidation: filter.allowConsolidation, consolidationPaired: filter.consolidationPaired, sortBy: filter.sortBy, @@ -559,6 +560,35 @@ export class BookingsService { }); } + /** Booking statuses at which a customer can pay (mirrors booking-payment.service). */ + private static readonly PAYABLE_STATUSES = [ + 'FULLY_EXECUTED', + 'SELECTED_FOR_BATCH', + 'AWAITING_PAYMENT', + ]; + + /** + * List the current customer's bookings that are ready for payment: + * payable status AND not yet PAID. Company scope is derived from the + * authenticated user and cannot be widened by the caller. + */ + async findMyPayable( + userId: string, + filter: FilterBookingDto, + ): Promise<{ items: Booking[]; total: number }> { + const { company } = await this.companiesService.getCompanyInfoByUserId(userId); + + return this.bookingsRepository.findAllPaginated({ + page: filter.page ?? 1, + pageSize: filter.pageSize ?? 20, + statuses: BookingsService.PAYABLE_STATUSES, + excludePaymentStatus: 'PAID', + companyId: company.id, + sortBy: filter.sortBy, + sortOrder: filter.sortOrder, + }); + } + /** Aggregate metrics and tab counts for the backoffice booking list. */ async getListSummary(filter: FilterBookingDto): Promise { const page = filter.page ?? 1; @@ -573,6 +603,7 @@ export class BookingsService { freightType: filter.freightType, tradeDirection: filter.tradeDirection, paymentCurrency: filter.paymentCurrency, + paymentStatus: filter.paymentStatus, allowConsolidation: filter.allowConsolidation, consolidationPaired: filter.consolidationPaired, }; diff --git a/apps/edr-freight-api/src/modules/bookings/dto/filter-booking.dto.ts b/apps/edr-freight-api/src/modules/bookings/dto/filter-booking.dto.ts index b88c381ae..9ce90d2b9 100644 --- a/apps/edr-freight-api/src/modules/bookings/dto/filter-booking.dto.ts +++ b/apps/edr-freight-api/src/modules/bookings/dto/filter-booking.dto.ts @@ -7,6 +7,7 @@ import { PAYMENT_CURRENCIES, TRADE_DIRECTIONS, } from './create-booking.dto'; +import { PAYMENT_STATUSES } from '../entities/booking.entity'; export class FilterBookingDto { @ApiPropertyOptional({ enum: BOOKING_STATUSES }) @@ -65,6 +66,11 @@ export class FilterBookingDto { @IsIn([...PAYMENT_CURRENCIES]) paymentCurrency?: string; + @ApiPropertyOptional({ enum: PAYMENT_STATUSES }) + @IsOptional() + @IsIn([...PAYMENT_STATUSES]) + paymentStatus?: string; + @ApiPropertyOptional() @IsOptional() @Transform(({ value }) => value === 'true' || value === true)