mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 13:40:57 +00:00
fix issue -> consolidation
This commit is contained in:
@@ -2091,6 +2091,7 @@ export class ContractBookingService {
|
||||
): Promise<{
|
||||
overweightLines: Array<{
|
||||
containerTypeCode: string;
|
||||
containerLabel: string;
|
||||
totalVgmTons: number;
|
||||
maxAllowedTons: number;
|
||||
excessTons: number;
|
||||
@@ -2200,6 +2201,12 @@ export class ContractBookingService {
|
||||
: 0,
|
||||
vgmPerUnitTons: line.units.length ? totalVgmTons / line.units.length : 0,
|
||||
totalVgmTons,
|
||||
// Per-box weights drive the overweight check — the limit is per
|
||||
// container, so a heavy box is billed even when the line total fits.
|
||||
units: (line.units ?? []).map((u, idx) => ({
|
||||
vgmTons: Number(u.vgmTons ?? 0),
|
||||
sortOrder: idx,
|
||||
})) as BookingContainer['units'],
|
||||
wagonsRequired: Math.ceil(line.quantity * wagonsPerUnitForSize(ct.sizeFt)),
|
||||
}),
|
||||
),
|
||||
@@ -2233,6 +2240,7 @@ export class ContractBookingService {
|
||||
containerTypeId: ct.id,
|
||||
quantity: line.quantity,
|
||||
totalVgmTons,
|
||||
unitVgmTons: (line.units ?? []).map((u) => Number(u.vgmTons ?? 0)),
|
||||
})),
|
||||
contract.tradeDirection,
|
||||
);
|
||||
@@ -2312,7 +2320,12 @@ export class ContractBookingService {
|
||||
(s, u) => s + Number(u.vgmTons ?? 0),
|
||||
0,
|
||||
);
|
||||
return { containerTypeId: ct.id, quantity: line.quantity, totalVgmTons };
|
||||
return {
|
||||
containerTypeId: ct.id,
|
||||
quantity: line.quantity,
|
||||
totalVgmTons,
|
||||
unitVgmTons: (line.units ?? []).map((u) => Number(u.vgmTons ?? 0)),
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
type ClearanceTrainState,
|
||||
} from '@edr/types';
|
||||
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { DropdownSettingsService } from '../dropdown-settings/dropdown-settings.service';
|
||||
import { FileUploadSettingsService } from '../file-upload-settings/file-upload-settings.service';
|
||||
import { FilesService } from '../files/files.service';
|
||||
@@ -117,6 +119,18 @@ export interface ContractClearanceView {
|
||||
linkedBookingReviewNote?: string | null;
|
||||
/** Shipment day the booking currently holds — the default when GL resubmits. */
|
||||
linkedBookingScheduledDate?: string | null;
|
||||
/**
|
||||
* Open wagon-cancellation on a CANCELLED linked booking (consolidation
|
||||
* partner lapsed, staff cut): FEE_PENDING = customer must pay the
|
||||
* cancellation fee; CREDIT_AVAILABLE = fee settled, GL rebooks the credit.
|
||||
*/
|
||||
linkedBookingCancellation?: {
|
||||
id: string;
|
||||
status: string;
|
||||
wagonsCancelled: number;
|
||||
creditAmount: number;
|
||||
creditCurrency: string;
|
||||
} | null;
|
||||
dutyAdvice?: {
|
||||
amount: number;
|
||||
currency: string;
|
||||
@@ -171,6 +185,7 @@ export class ContractClearanceService {
|
||||
private readonly glOperationsService: GlOperationsService,
|
||||
private readonly notifier: ContractNotifierService,
|
||||
private readonly transitAgentsService: TransitAgentsService,
|
||||
private readonly dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
private isPhasedCustoms(contract: Contract): boolean {
|
||||
@@ -369,9 +384,27 @@ export class ContractClearanceService {
|
||||
// without a cycle row), so fall back to the contract's own live booking —
|
||||
// otherwise the clearance page sees no linked booking at all and cannot show
|
||||
// its status or the actions that depend on it.
|
||||
const booking = cycle?.bookingId
|
||||
let booking = cycle?.bookingId
|
||||
? await this.bookingsService.findById(cycle.bookingId)
|
||||
: await this.contractsRepository.findLatestBookingForContract(contractId);
|
||||
// The fallback skips terminal bookings, but a CANCELLED one with an open
|
||||
// wagon-cancellation still belongs on this page: the fee gate and the
|
||||
// rebook-from-credit action live here. Surface the newest such booking.
|
||||
if (!booking) {
|
||||
const [open] = await this.dataSource.query<{ booking_id: string }[]>(
|
||||
`SELECT c.booking_id
|
||||
FROM freight.booking_wagon_cancellations c
|
||||
JOIN freight.bookings b ON b.id = c.booking_id
|
||||
WHERE b.contract_id = $1
|
||||
AND b.status = 'CANCELLED'
|
||||
AND c.status IN ('FEE_PENDING', 'CREDIT_AVAILABLE')
|
||||
AND c.deleted_at IS NULL
|
||||
ORDER BY c.created_at DESC
|
||||
LIMIT 1`,
|
||||
[contractId],
|
||||
);
|
||||
if (open) booking = await this.bookingsService.findById(open.booking_id);
|
||||
}
|
||||
if (booking) {
|
||||
linkedBookingId = booking.id ?? null;
|
||||
linkedBookingReference = booking.reference ?? null;
|
||||
@@ -395,6 +428,40 @@ export class ContractClearanceService {
|
||||
}
|
||||
}
|
||||
|
||||
// A CANCELLED booking may carry an open wagon-cancellation (consolidation
|
||||
// partner lapsed, staff cut): FEE_PENDING gates on the customer paying the
|
||||
// cancellation fee; CREDIT_AVAILABLE lets GL rebook from the credit here.
|
||||
let linkedBookingCancellation: {
|
||||
id: string;
|
||||
status: string;
|
||||
wagonsCancelled: number;
|
||||
creditAmount: number;
|
||||
creditCurrency: string;
|
||||
} | null = null;
|
||||
if (booking && linkedBookingStatus === 'CANCELLED') {
|
||||
const [row] = await this.dataSource.query<
|
||||
{ id: string; status: string; wagons_cancelled: string; credit_amount: string }[]
|
||||
>(
|
||||
`SELECT id, status, wagons_cancelled, credit_amount
|
||||
FROM freight.booking_wagon_cancellations
|
||||
WHERE booking_id = $1
|
||||
AND status IN ('FEE_PENDING', 'CREDIT_AVAILABLE')
|
||||
AND deleted_at IS NULL
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1`,
|
||||
[booking.id],
|
||||
);
|
||||
if (row) {
|
||||
linkedBookingCancellation = {
|
||||
id: row.id,
|
||||
status: row.status,
|
||||
wagonsCancelled: Number(row.wagons_cancelled),
|
||||
creditAmount: Number(row.credit_amount),
|
||||
creditCurrency: booking.paymentCurrency ?? 'ETB',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
contractId,
|
||||
status: contract.status,
|
||||
@@ -433,6 +500,7 @@ export class ContractClearanceService {
|
||||
linkedBookingStatus,
|
||||
linkedBookingReviewNote,
|
||||
linkedBookingScheduledDate,
|
||||
linkedBookingCancellation,
|
||||
dutyAdvice,
|
||||
dutyDispute,
|
||||
transitAssignee,
|
||||
|
||||
@@ -63,6 +63,7 @@ describe('ContractClearanceService — duty dispute', () => {
|
||||
{} as never, // glOperationsService
|
||||
notifier as never,
|
||||
{} as never, // transitAgentsService
|
||||
{} as never, // dataSource
|
||||
);
|
||||
build([
|
||||
milestone('DUTY_TAXES_ADVISED', 'COMPLETED'),
|
||||
|
||||
@@ -61,6 +61,7 @@ describe('ContractClearanceService — transit assignee', () => {
|
||||
{} as never,
|
||||
notifier as never,
|
||||
transitAgentsService as never,
|
||||
{} as never, // dataSource
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user