mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-02 16:13:40 +00:00
enhance contract and train scheduling features
- 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.
This commit is contained in:
@@ -26,30 +26,22 @@ function freightFor(freightType: string): Freight {
|
||||
/**
|
||||
* The customer-input clearance setting code, or null when no gate applies.
|
||||
*
|
||||
* - Path B (customs bundled): the customer uploads the documents GL needs to do
|
||||
* the clearance work → `contract_clearance_{op}_{freight}`.
|
||||
* - Path A (no customs): the customer clears the cargo himself and uploads his
|
||||
* own (smaller) clearance proof set → `contract_clearance_selfclear_{op}_{freight}`,
|
||||
* reviewed by Operations rather than GL.
|
||||
* 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, like Path A). GENERAL intercity contracts skip the contract
|
||||
* gate and collect the same set per booking instead.
|
||||
* (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,
|
||||
_freightType: string,
|
||||
_includesCustoms: boolean,
|
||||
): string | null {
|
||||
if (tradeDirection === 'DOMESTIC') return INTERCITY_DOCUMENTS_SETTING_CODE;
|
||||
const op = operationFor(tradeDirection);
|
||||
if (!op) return null;
|
||||
const freight = freightFor(freightType);
|
||||
if (!includesCustoms) {
|
||||
return `contract_clearance_selfclear_${op}_${freight}`;
|
||||
}
|
||||
return `contract_clearance_${op}_${freight}`;
|
||||
return null;
|
||||
}
|
||||
|
||||
/** The GL-output (customs output) setting code, keyed on op + freight. */
|
||||
|
||||
@@ -42,6 +42,7 @@ describe('ContractsService duplicate guard', () => {
|
||||
{} as never,
|
||||
{} as never,
|
||||
{} as never,
|
||||
{ buildBreakdown: async () => ({ lineItems: [] }) } as never,
|
||||
);
|
||||
return (
|
||||
service as unknown as {
|
||||
|
||||
@@ -28,6 +28,7 @@ import { ContractCargoScope } from './entities/contract-cargo-scope.entity';
|
||||
import { isEffectivelyExpired } from './utils/contract-expiry.util';
|
||||
import { diffContractFields } from './contract-document-diff.util';
|
||||
import { ContractDocumentHistoryService } from './contract-document-history.service';
|
||||
import { ContractPricingService } from './contract-pricing.service';
|
||||
import { FileRecord } from '../files/entities/file.entity';
|
||||
|
||||
/** Paginated contract list: flat `total` (backoffice) + `meta` block (portal). */
|
||||
@@ -113,6 +114,7 @@ export class ContractsService {
|
||||
private readonly filesService: FilesService,
|
||||
private readonly minioService: MinioService,
|
||||
private readonly documentHistory: ContractDocumentHistoryService,
|
||||
private readonly pricingService: ContractPricingService,
|
||||
) {}
|
||||
|
||||
/** Generate a unique contract reference number (CTR-YYYY-NNNNN). */
|
||||
@@ -331,6 +333,33 @@ export class ContractsService {
|
||||
);
|
||||
}
|
||||
|
||||
// Price the contract BEFORE anything persists: a lane with no configured
|
||||
// rate 422s here and the wizard shows its blocking modal — with no orphan
|
||||
// DRAFT row left behind for the customer to trip over on retry. The probe
|
||||
// carries exactly the fields buildBreakdown prices from; relation-only
|
||||
// niceties (cargoType labels) are absent, which only affects display
|
||||
// lines, never the missing-rate gates.
|
||||
await this.pricingService.buildBreakdown({
|
||||
tradeDirection: dto.tradeDirection,
|
||||
freightType: dto.freightType,
|
||||
paymentCurrency: 'USD',
|
||||
customsClearingEnabled: includesCustoms,
|
||||
isHazardous: dto.isHazardous ?? false,
|
||||
isReefer: dto.isReefer ?? false,
|
||||
equipmentReturn: dto.equipmentReturn ?? null,
|
||||
firstMilePickupAddress: dto.firstMilePickupAddress ?? null,
|
||||
lastMileDeliveryAddress: dto.lastMileDeliveryAddress ?? null,
|
||||
routes: (dto.routes ?? []).map((r, i) => ({
|
||||
originYardId: r.originYardId,
|
||||
destinationYardId: r.destinationYardId,
|
||||
sortOrder: r.sortOrder ?? i,
|
||||
})),
|
||||
cargoScope: (dto.cargoScope ?? []).map((c) => ({
|
||||
containerSize: c.containerSize ?? null,
|
||||
cargoTypeId: c.cargoTypeId ?? null,
|
||||
})),
|
||||
} as unknown as Contract);
|
||||
|
||||
// An explicit reference is caller-chosen — a collision there is a real
|
||||
// conflict and should surface. Auto-generated references retry past a
|
||||
// concurrent insert that grabbed the same sequence number.
|
||||
|
||||
Reference in New Issue
Block a user