mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
- Added pricing service integration to ContractsService for pre-persistence contract pricing. - Updated LegCapacityPanel to display cargo weight instead of gross weight for better clarity on booked cargo. - Enhanced train scheduling service to include cargo weight in booking details. - Modified ClearanceDocumentsPage to include FULLY_EXECUTED status in booking status options. - Refactored FileUploadSettingsPage to categorize file upload settings into tabs for better organization. - Removed legacy onboarding fields and settings from file upload settings seeder. - Improved type definitions for train scheduling to include cargo weight without wagon tare.
125 lines
4.2 KiB
TypeScript
125 lines
4.2 KiB
TypeScript
import { BadRequestException } from '@nestjs/common';
|
|
|
|
import { Contract } from './entities/contract.entity';
|
|
import { INTERCITY_DOCUMENTS_SETTING_CODE } from '../bookings/clearance.util';
|
|
|
|
/**
|
|
* Resolves which seeded clearance FileUploadSetting applies to a contract during
|
|
* the CONTRACT pre-booking phase (Path B). Mirrors clearance.util.ts but emits
|
|
* `contract_clearance_*` codes keyed on (tradeDirection, freightType, customs).
|
|
*/
|
|
|
|
type Op = 'import' | 'export';
|
|
type Freight = 'container' | 'bulk';
|
|
|
|
/** Trade direction → clearance operation. DOMESTIC has no customs clearance. */
|
|
function operationFor(tradeDirection: string): Op | null {
|
|
if (tradeDirection === 'IMPORT') return 'import';
|
|
if (tradeDirection === 'EXPORT') return 'export';
|
|
return null; // DOMESTIC / intercity — no clearance gate
|
|
}
|
|
|
|
function freightFor(freightType: string): Freight {
|
|
return freightType === 'BULK' ? 'bulk' : 'container';
|
|
}
|
|
|
|
/**
|
|
* The customer-input clearance setting code, or null when no gate applies.
|
|
*
|
|
* Contract-level IMPORT/EXPORT clearance has been removed — clearance is
|
|
* collected per booking instead (see bookings/clearance.util.ts), so this
|
|
* always returns null for IMPORT/EXPORT now.
|
|
*
|
|
* DOMESTIC/intercity has no border, but a ONE_TIME intercity contract still
|
|
* collects the admin-configured intercity document set after both signatures
|
|
* (ops-reviewed). GENERAL intercity contracts skip the contract gate and
|
|
* collect the same set per booking instead.
|
|
*/
|
|
export function contractClearanceSettingCode(
|
|
tradeDirection: string,
|
|
_freightType: string,
|
|
_includesCustoms: boolean,
|
|
): string | null {
|
|
if (tradeDirection === 'DOMESTIC') return INTERCITY_DOCUMENTS_SETTING_CODE;
|
|
return null;
|
|
}
|
|
|
|
/** The GL-output (customs output) setting code, keyed on op + freight. */
|
|
export function contractClearanceOutputSettingCode(
|
|
tradeDirection: string,
|
|
freightType: string,
|
|
includesCustoms: boolean,
|
|
): string | null {
|
|
if (!includesCustoms) return null;
|
|
const op = operationFor(tradeDirection);
|
|
if (!op) return null;
|
|
const freight = freightFor(freightType);
|
|
return `contract_clearance_output_${op}_${freight}`;
|
|
}
|
|
|
|
/** Convenience: resolve both codes for a loaded contract. */
|
|
export function contractClearanceCodes(contract: Contract): {
|
|
inputCode: string | null;
|
|
outputCode: string | null;
|
|
includesCustoms: boolean;
|
|
} {
|
|
const includesCustoms = contract.customsClearingEnabled ?? false;
|
|
return {
|
|
inputCode: contractClearanceSettingCode(
|
|
contract.tradeDirection,
|
|
contract.freightType,
|
|
includesCustoms,
|
|
),
|
|
outputCode: contractClearanceOutputSettingCode(
|
|
contract.tradeDirection,
|
|
contract.freightType,
|
|
includesCustoms,
|
|
),
|
|
includesCustoms,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Djibouti GL cannot record a Delivery Order without saying WHEN the vessel
|
|
* arrived and WHEN the DO was collected — the file alone leaves the import
|
|
* timeline unauditable. Shared by the contract and per-booking DO uploads so
|
|
* one endpoint can never be laxer than the other.
|
|
*
|
|
* Returns the normalized `YYYY-MM-DD` pair; throws if either is missing,
|
|
* unparseable, or the DO predates the vessel's arrival.
|
|
*/
|
|
export function assertDoCollectionDates(dates?: {
|
|
vesselArrivalDate?: string;
|
|
doCollectedDate?: string;
|
|
}): { vesselArrivalDate: string; doCollectedDate: string } {
|
|
const vesselArrivalDate = normalizeDoDate(
|
|
dates?.vesselArrivalDate,
|
|
'Vessel arrival date',
|
|
);
|
|
const doCollectedDate = normalizeDoDate(
|
|
dates?.doCollectedDate,
|
|
'DO collected date',
|
|
);
|
|
|
|
if (doCollectedDate < vesselArrivalDate) {
|
|
throw new BadRequestException(
|
|
'DO collected date cannot be earlier than the vessel arrival date.',
|
|
);
|
|
}
|
|
|
|
return { vesselArrivalDate, doCollectedDate };
|
|
}
|
|
|
|
/** `YYYY-MM-DD` or throw — the column is a DATE, so time zones never enter. */
|
|
function normalizeDoDate(value: string | undefined, label: string): string {
|
|
const trimmed = value?.trim();
|
|
if (!trimmed) {
|
|
throw new BadRequestException(`${label} is required to upload a Delivery Order.`);
|
|
}
|
|
const date = trimmed.slice(0, 10);
|
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(date) || Number.isNaN(Date.parse(date))) {
|
|
throw new BadRequestException(`${label} is not a valid date.`);
|
|
}
|
|
return date;
|
|
}
|