mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 04:08:11 +00:00
Merge branch 'dev' of github.com:Tria-plc/edr-platform into freight_feature/usermanagement
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
SelectQueryBuilder,
|
||||
} from 'typeorm';
|
||||
|
||||
import { computeFacets, FacetBucket } from '../../common/utils/facets.util';
|
||||
import { wagonsPerUnitForSize } from '../rule-engine/container-type.util';
|
||||
import { ContainerType } from '../rule-engine/entities/container-type.entity';
|
||||
import { Contract } from '../contracts/entities/contract.entity';
|
||||
@@ -857,6 +858,29 @@ export class BookingsRepository extends BaseRepository<Booking> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Facet counts for the filter bar's enum popovers: one `GROUP BY` per
|
||||
* column, each with every OTHER active filter applied but its own
|
||||
* predicate omitted (see `applyListFilters`'s `omit` param). `bookingType`
|
||||
* is derived from `contract_kind` (see the comment in `applyListFilters`),
|
||||
* not a plain column, so it facets on the same CASE expression the filter
|
||||
* itself applies.
|
||||
*/
|
||||
async getFacets(options: BookingListFilterOptions): Promise<Record<string, FacetBucket[]>> {
|
||||
return computeFacets(
|
||||
() => this.repository.createQueryBuilder('booking').where('booking.deleted_at IS NULL'),
|
||||
(qb, omit) => this.applyListFilters(qb, options, omit as keyof BookingListFilterOptions),
|
||||
{
|
||||
status: 'booking.status',
|
||||
freightType: 'booking.freight_type',
|
||||
tradeDirection: 'booking.trade_direction',
|
||||
paymentStatus: 'booking.payment_status',
|
||||
bookingType:
|
||||
"CASE WHEN booking.contract_kind = 'GENERAL' THEN 'GENERAL_CONTRACT' ELSE 'ONE_TIME' END",
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async getStatusCounts(): Promise<Record<string, number>> {
|
||||
const rows = await this.repository
|
||||
.createQueryBuilder('booking')
|
||||
@@ -915,16 +939,24 @@ export class BookingsRepository extends BaseRepository<Booking> {
|
||||
return { inQueue, onThisPage, needsAction, urgent };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param omit skip this one predicate — used by `getFacets` so a facet's
|
||||
* own filter doesn't hide its own sibling values. Every other caller
|
||||
* (list, summary metrics) passes nothing.
|
||||
*/
|
||||
private applyListFilters(
|
||||
qb: SelectQueryBuilder<Booking>,
|
||||
options: BookingListFilterOptions,
|
||||
omit?: keyof BookingListFilterOptions | 'status',
|
||||
): void {
|
||||
if (options.statuses?.length) {
|
||||
qb.andWhere('booking.status IN (:...statuses)', {
|
||||
statuses: options.statuses,
|
||||
});
|
||||
} else if (options.status) {
|
||||
qb.andWhere('booking.status = :status', { status: options.status });
|
||||
if (omit !== 'status') {
|
||||
if (options.statuses?.length) {
|
||||
qb.andWhere('booking.status IN (:...statuses)', {
|
||||
statuses: options.statuses,
|
||||
});
|
||||
} else if (options.status) {
|
||||
qb.andWhere('booking.status = :status', { status: options.status });
|
||||
}
|
||||
}
|
||||
|
||||
if (options.companyId) {
|
||||
@@ -957,12 +989,12 @@ export class BookingsRepository extends BaseRepository<Booking> {
|
||||
cargoTypeId: options.cargoTypeId,
|
||||
});
|
||||
}
|
||||
if (options.freightType) {
|
||||
if (omit !== 'freightType' && options.freightType) {
|
||||
qb.andWhere('booking.freight_type = :freightType', {
|
||||
freightType: options.freightType,
|
||||
});
|
||||
}
|
||||
if (options.bookingType) {
|
||||
if (omit !== 'bookingType' && options.bookingType) {
|
||||
// The stored booking_type column is 'ONE_TIME' for every row (contract
|
||||
// drawdowns included — see contract-booking.service create), so the
|
||||
// one-time vs general split keys on the denormalized contract_kind:
|
||||
@@ -1011,12 +1043,12 @@ export class BookingsRepository extends BaseRepository<Booking> {
|
||||
} else if (options.isGovernment === 'false') {
|
||||
qb.andWhere('booking.is_government = FALSE');
|
||||
}
|
||||
if (options.tradeDirection) {
|
||||
if (omit !== 'tradeDirection' && options.tradeDirection) {
|
||||
qb.andWhere('booking.trade_direction = :tradeDirection', {
|
||||
tradeDirection: options.tradeDirection,
|
||||
});
|
||||
}
|
||||
if (options.tradeDirections) {
|
||||
if (omit !== 'tradeDirection' && options.tradeDirections) {
|
||||
applyDirectionScope(qb, 'booking.trade_direction', options.tradeDirections);
|
||||
}
|
||||
if (options.paymentCurrency) {
|
||||
@@ -1024,7 +1056,7 @@ export class BookingsRepository extends BaseRepository<Booking> {
|
||||
paymentCurrency: options.paymentCurrency,
|
||||
});
|
||||
}
|
||||
if (options.paymentStatus) {
|
||||
if (omit !== 'paymentStatus' && options.paymentStatus) {
|
||||
qb.andWhere('booking.payment_status = :paymentStatus', {
|
||||
paymentStatus: options.paymentStatus,
|
||||
});
|
||||
|
||||
@@ -2051,8 +2051,9 @@ export class BookingsService {
|
||||
consolidationPaired: filter.consolidationPaired,
|
||||
};
|
||||
|
||||
const [statusCounts, metrics] = await Promise.all([
|
||||
const [statusCounts, facets, metrics] = await Promise.all([
|
||||
this.bookingsRepository.getStatusCounts(),
|
||||
this.bookingsRepository.getFacets(listFilter),
|
||||
this.bookingsRepository.getListSummaryMetrics({
|
||||
...listFilter,
|
||||
page,
|
||||
@@ -2064,7 +2065,9 @@ export class BookingsService {
|
||||
|
||||
return {
|
||||
metrics,
|
||||
// Tabs stay unfiltered (whole-set) on purpose — see the DTO comment.
|
||||
tabs: mapStatusCountsToTabs(statusCounts),
|
||||
facets,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { FacetBucket } from '../../../common/utils/facets.util';
|
||||
|
||||
export class BookingListSummaryMetricsDto {
|
||||
@ApiProperty({ example: 42 })
|
||||
@@ -31,4 +32,18 @@ export class BookingListSummaryDto {
|
||||
|
||||
@ApiProperty({ type: BookingListSummaryTabsDto })
|
||||
tabs!: BookingListSummaryTabsDto;
|
||||
|
||||
/**
|
||||
* Per-column value counts for the filter bar's enum popovers, scoped to
|
||||
* every OTHER currently-active filter (own predicate omitted per column —
|
||||
* see `BookingsRepository.getFacets`). Unlike `tabs`, which is
|
||||
* deliberately unfiltered so tab counts stay stable while you filter
|
||||
* within a tab, these move with the filter set.
|
||||
*/
|
||||
@ApiProperty({
|
||||
description: 'Facet counts keyed by filter field, for the pill filter bar',
|
||||
type: 'object',
|
||||
additionalProperties: { type: 'array', items: { type: 'object' } },
|
||||
})
|
||||
facets!: Record<string, FacetBucket[]>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user