mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
Implement comprehensive Global Logistics workflow for Unimodal Export and Import processes, including contract verification, documentation submission, compliance management, customs clearance, payment settlement, and last-mile transport handling.
This commit is contained in:
@@ -235,13 +235,55 @@ export class ContractClearanceService {
|
||||
}
|
||||
}
|
||||
|
||||
/** GL ET reviews a single document: APPROVED or QUERIED (→ back to upload). */
|
||||
/**
|
||||
* Path A (no customs) — the customer clears the cargo himself and uploads his
|
||||
* own clearance proof, reviewed by Operations rather than GL. True when a
|
||||
* clearance doc set resolves for a non-customs contract.
|
||||
*/
|
||||
private isSelfClear(contract: Contract): boolean {
|
||||
if (contract.customsClearingEnabled) return false;
|
||||
return contractClearanceCodes(contract).inputCode != null;
|
||||
}
|
||||
|
||||
/** GL ET (Path B) reviews a single document: APPROVED or QUERIED. */
|
||||
async reviewDocument(
|
||||
contractId: string,
|
||||
fileKey: string,
|
||||
status: 'APPROVED' | 'QUERIED',
|
||||
staffId: string,
|
||||
note?: string,
|
||||
): Promise<Contract> {
|
||||
return this.applyReview(contractId, fileKey, status, staffId, 'GL_ET', note);
|
||||
}
|
||||
|
||||
/**
|
||||
* Operations (Path A) reviews a customer self-clearance document. Identical
|
||||
* approve/query loop to {@link reviewDocument}; rejects customs (Path B)
|
||||
* contracts, which are GL-reviewed.
|
||||
*/
|
||||
async opsReviewDocument(
|
||||
contractId: string,
|
||||
fileKey: string,
|
||||
status: 'APPROVED' | 'QUERIED',
|
||||
staffId: string,
|
||||
note?: string,
|
||||
): Promise<Contract> {
|
||||
const contract = await this.contractsService.findById(contractId);
|
||||
if (!this.isSelfClear(contract)) {
|
||||
throw new ConflictException(
|
||||
'Operations review applies only to self-clearance (non-customs) contracts.',
|
||||
);
|
||||
}
|
||||
return this.applyReview(contractId, fileKey, status, staffId, 'OPERATIONS', note);
|
||||
}
|
||||
|
||||
private async applyReview(
|
||||
contractId: string,
|
||||
fileKey: string,
|
||||
status: 'APPROVED' | 'QUERIED',
|
||||
staffId: string,
|
||||
reviewerRole: 'GL_ET' | 'OPERATIONS',
|
||||
note?: string,
|
||||
): Promise<Contract> {
|
||||
const contract = await this.contractsService.findById(contractId);
|
||||
if (contract.status !== 'CLEARANCE_UNDER_REVIEW') {
|
||||
@@ -280,7 +322,7 @@ export class ContractClearanceService {
|
||||
`Document "${fileKey}" queried: ${note}`,
|
||||
'CHANGES_REQUESTED',
|
||||
staffId,
|
||||
'GL_ET',
|
||||
reviewerRole,
|
||||
);
|
||||
// Return the contract to the customer to re-upload the queried document.
|
||||
await this.contractsRepository.update(contractId, {
|
||||
@@ -325,11 +367,17 @@ export class ContractClearanceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* GL ET finalizes pre-booking clearance: requires every customer document
|
||||
* APPROVED (and required output docs present) → CLEARANCE_READY_FOR_BOOKING.
|
||||
* GL ET finalizes Path B pre-booking clearance: requires every customer
|
||||
* document APPROVED and required output docs present → CLEARANCE_READY_FOR_BOOKING
|
||||
* (GL then creates the booking). Rejects self-clearance (Path A) contracts.
|
||||
*/
|
||||
async finalize(contractId: string): Promise<Contract> {
|
||||
const contract = await this.contractsService.findById(contractId);
|
||||
if (this.isSelfClear(contract)) {
|
||||
throw new ConflictException(
|
||||
'Self-clearance (Path A) contracts are finalized by Operations, not GL.',
|
||||
);
|
||||
}
|
||||
if (contract.status !== 'CLEARANCE_UNDER_REVIEW') {
|
||||
throw new ConflictException(
|
||||
`Cannot finalize clearance on status "${contract.status}".`,
|
||||
@@ -376,8 +424,46 @@ export class ContractClearanceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* GL ET queue: contracts awaiting pre-booking document review. Scoped to
|
||||
* CLEARANCE_UNDER_REVIEW (customs contracts only).
|
||||
* Operations finalizes Path A self-clearance: requires every customer document
|
||||
* APPROVED, then the contract becomes bookable BY THE CUSTOMER. There is no GL
|
||||
* output phase on Path A, so the contract goes straight to FULLY_EXECUTED
|
||||
* (ONE_TIME) / CONTRACT_ACTIVE (GENERAL).
|
||||
*/
|
||||
async opsFinalize(contractId: string): Promise<Contract> {
|
||||
const contract = await this.contractsService.findById(contractId);
|
||||
if (!this.isSelfClear(contract)) {
|
||||
throw new ConflictException(
|
||||
'Operations finalize applies only to self-clearance (non-customs) contracts.',
|
||||
);
|
||||
}
|
||||
if (contract.status !== 'CLEARANCE_UNDER_REVIEW') {
|
||||
throw new ConflictException(
|
||||
`Cannot finalize clearance on status "${contract.status}".`,
|
||||
);
|
||||
}
|
||||
|
||||
const approved = await this.isClearanceFullyApproved(contract);
|
||||
if (!approved) {
|
||||
throw new BadRequestException(
|
||||
'All required documents must be approved before clearance can be finalized',
|
||||
);
|
||||
}
|
||||
|
||||
const cycle = await this.contractsRepository.currentCycle(contractId);
|
||||
await this.contractsRepository.update(contractId, {
|
||||
status: contract.contractKind === 'GENERAL' ? 'CONTRACT_ACTIVE' : 'FULLY_EXECUTED',
|
||||
clearanceStatus: 'SELF_CLEARED',
|
||||
} as never);
|
||||
if (cycle) {
|
||||
await this.contractsRepository.setCycleStatus(cycle.id, 'CLEARANCE_READY_FOR_BOOKING', {
|
||||
clearanceReadyAt: new Date(),
|
||||
});
|
||||
}
|
||||
return this.contractsService.findById(contractId);
|
||||
}
|
||||
|
||||
/**
|
||||
* GL ET queue: customs (Path B) contracts awaiting pre-booking document review.
|
||||
*/
|
||||
async queue(
|
||||
filter: FilterContractDto,
|
||||
@@ -388,6 +474,22 @@ export class ContractClearanceService {
|
||||
page: filter.page ?? 1,
|
||||
pageSize: filter.pageSize ?? 100,
|
||||
statuses: ['CLEARANCE_UNDER_REVIEW'],
|
||||
customsClearingEnabled: true,
|
||||
sortBy: filter.sortBy,
|
||||
sortOrder: filter.sortOrder,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Operations queue: self-clearance (Path A) contracts awaiting Operations
|
||||
* review of the customer's own clearance documents.
|
||||
*/
|
||||
async opsQueue(filter: FilterContractDto): Promise<PaginatedContracts> {
|
||||
return this.contractsRepository.findAllPaginated({
|
||||
page: filter.page ?? 1,
|
||||
pageSize: filter.pageSize ?? 100,
|
||||
statuses: ['CLEARANCE_UNDER_REVIEW'],
|
||||
customsClearingEnabled: false,
|
||||
sortBy: filter.sortBy,
|
||||
sortOrder: filter.sortOrder,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user