Enhance clearance milestone management and introduce new contract actions

- Added new milestones for 'Transit Permit Uploaded' and 'Export Transport Document Issued' in the clearance milestone catalog.
- Implemented methods in ClearanceMilestoneService to skip milestones and complete them with metadata.
- Updated ContractBookingService to check boundary conditions before booking creation.
- Introduced new endpoints in ContractsController for uploading customs declarations, advising duty, and handling various document uploads.
- Enhanced the UI to support new clearance actions and display relevant components based on milestone statuses.
This commit is contained in:
marshal
2026-07-01 10:20:16 +03:00
parent ccd5d6de31
commit 612df8daff
36 changed files with 3018 additions and 57 deletions

View File

@@ -21,6 +21,7 @@ const DOC_CODE_TO_MILESTONE: Record<string, string> = {
import_release: 'IMPORT_RELEASE_GRANTED', // import — GL ET
full_in_interchange: 'OFFLOADED', // export — GL DJ
final_declaration: 'IMPORT_PROCESS_COMPLETED', // import — GL ET
export_transport_document: 'EXPORT_TRANSPORT_ISSUED', // export — GL ET post-allocation
};
/**
@@ -158,4 +159,47 @@ export class GlOperationsService {
}
return { uploaded: files.length, completedMilestones };
}
/**
* GL ET uploads export transport document after wagon allocation (export ONE_TIME).
*/
async uploadTransportDocument(
bookingId: string,
file: Express.Multer.File,
): Promise<{ uploaded: boolean; milestoneCompleted: boolean }> {
const booking = await this.getBooking(bookingId);
if (booking.tradeDirection !== 'EXPORT') {
throw new BadRequestException('Transport document upload applies to export shipments only.');
}
const milestones = await this.milestoneService.listForBooking(bookingId);
const wagonAllocated = milestones.find((m) => m.milestoneCode === 'WAGON_ALLOCATED');
const wagonDone =
wagonAllocated?.status === 'COMPLETED' || booking.schedulingStatus === 'SCHEDULED';
if (!wagonDone) {
throw new BadRequestException(
'Wagon must be allocated before the transport document can be uploaded.',
);
}
if (!file) throw new BadRequestException('No transport document uploaded');
await this.filesService.upsertByCode({
resourceId: bookingId,
resource: 'bookings',
code: 'export_transport_document',
file,
});
if (wagonAllocated && wagonAllocated.status !== 'COMPLETED') {
await this.milestoneService.completeForBooking(bookingId, 'WAGON_ALLOCATED');
}
await this.milestoneService.completeByDocTrigger(
{ bookingId },
'EXPORT_TRANSPORT_ISSUED',
);
return { uploaded: true, milestoneCompleted: true };
}
}