Merge branch 'dev' into freight/nati-2

Conflict in ClearanceDocumentsPage: this branch migrated the page to the
pill FilterBar, dev added filters to the Select stack it replaced. Kept
the FilterBar and carried dev's additions across as a "Booked by"
(customerKind) FilterDef plus the shipping-line search placeholder; dev's
startOfDayIso/endOfDayIso went away because dateRangeParams already does
that. The Ship icon import is needed by dev's shipping-line customer cell,
which merged cleanly on its own.
This commit is contained in:
Nathnael
2026-08-17 12:43:35 +00:00
117 changed files with 6115 additions and 830 deletions

View File

@@ -110,6 +110,7 @@ function makeService(overrides?: {
.fn()
.mockResolvedValue({ id: 'ta-1', name: 'Ahmed Bourhan' }),
} as never, // transit agents
{ findAll: jest.fn().mockResolvedValue([]) } as never, // contracts repository
);
return {

View File

@@ -1,4 +1,5 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import {
ContractDocPhase,
isDeliveryOrderFileCode,
@@ -29,6 +30,7 @@ import { ClearanceMilestoneService } from './clearance-milestone.service';
import { GlOperationsService } from './gl-operations.service';
import { GlExchangeService } from './gl-exchange.service';
import { TransitAgentsService } from '../transit-agents/transit-agents.service';
import { ContractsRepository } from './contracts.repository';
import { AdviseContractDutyDto } from './dto/phased-clearance.dto';
import { buildWorkflowFiles, belongsOnDjClearanceQueue, belongsOnEtClearanceQueue, DJ_BOOKING_QUEUE_STATUSES, persistDeclarationUploads, persistDeliveryOrderUploads, persistDraftDeclarationUploads, persistReleaseOrderUploads, persistTransitPermitUploads, PHASED_CUSTOMS_BOOKING_QUEUE_STATUSES } from './phased-clearance.util';
@@ -155,6 +157,7 @@ export class BookingClearanceService {
private readonly notifier: BookingLifecycleNotifierService,
private readonly glExchangeService: GlExchangeService,
private readonly transitAgentsService: TransitAgentsService,
private readonly contractsRepository: ContractsRepository,
) {}
private async assertPhasedCustoms(booking: Booking): Promise<void> {
@@ -988,7 +991,39 @@ export class BookingClearanceService {
const milestones = await this.workflowService.listMilestonesForBooking(b.id);
if (belongsOnEtClearanceQueue(milestones)) filtered.push(b);
}
return filtered;
return this.attachContractSummary(filtered);
}
/**
* Queue rows show the parent contract's reference and lane. Booking has no
* contract relation, and a bare initiated instance may not carry yards yet —
* so batch-load the contracts (with routes) and fill in what's missing:
* `contractReference` always, origin/destination yards only when the booking
* lacks them (its own route wins).
*/
private async attachContractSummary(bookings: Booking[]): Promise<Booking[]> {
const ids = [...new Set(bookings.map((b) => b.contractId).filter(Boolean))] as string[];
if (!ids.length) return bookings;
const contracts = await this.contractsRepository.findAll({
where: { id: In(ids) },
relations: { routes: { originYard: true, destinationYard: true } },
});
const byId = new Map(contracts.map((c) => [c.id, c]));
for (const b of bookings) {
const contract = b.contractId ? byId.get(b.contractId) : undefined;
if (!contract) continue;
const row = b as Booking & { contractReference?: string | null };
row.contractReference = contract.reference ?? null;
if (b.originYard && b.destinationYard) continue;
const routes = contract.routes ?? [];
const route =
routes.find((r) => r.id === b.contractRouteId) ??
(routes.length === 1 ? routes[0] : undefined);
if (!route) continue;
b.originYard = b.originYard ?? route.originYard;
b.destinationYard = b.destinationYard ?? route.destinationYard;
}
return bookings;
}
async djQueue(): Promise<Booking[]> {
@@ -1008,6 +1043,6 @@ export class BookingClearanceService {
filtered.push(b);
}
}
return filtered;
return this.attachContractSummary(filtered);
}
}