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

@@ -12,11 +12,12 @@ import {
Res,
UnauthorizedException,
UploadedFiles,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { CurrentUser } from '@edr/api-common';
import type { TCurrentUser } from '@tria-plc/api-common/modules/auth/types/current-user.type';
import { AnyFilesInterceptor } from '@nestjs/platform-express';
import { AnyFilesInterceptor, FileInterceptor } from '@nestjs/platform-express';
import type { Response } from 'express';
import {
ApiBearerAuth,
@@ -72,6 +73,10 @@ import {
CompleteMilestoneDto,
ReportIncidentDto,
} from './dto/gl-operations.dto';
import {
AdviseContractDutyDto,
RoAmendmentDto,
} from './dto/phased-clearance.dto';
@ApiTags('contracts')
@Controller('contracts')
@@ -511,11 +516,126 @@ export class ContractsController {
@Post(':id/clearance/finalize')
@BookingStaff(FREIGHT_PERMS.contracts.finalizeClearance)
@ApiOperation({ summary: 'GL ET finalizes clearance → CLEARANCE_READY_FOR_BOOKING' })
@ApiOperation({ summary: 'GL ET finalizes clearance → CLEARANCE_READY_FOR_BOOKING (legacy)' })
finalizeClearance(@Param('id', ParseUUIDPipe) id: string) {
return this.clearanceService.finalize(id);
}
@Post(':id/clearance/declaration')
@BookingStaff(FREIGHT_PERMS.contracts.clearanceEtActions)
@UseInterceptors(AnyFilesInterceptor())
@ApiConsumes('multipart/form-data')
@ApiOperation({ summary: 'GL ET uploads customs declaration (IM4/IM5 or EX3/EX8)' })
uploadDeclaration(
@Param('id', ParseUUIDPipe) id: string,
@UploadedFiles() files: Express.Multer.File[],
@CurrentUser() user: AuthUserPayload,
) {
return this.clearanceService.uploadDeclaration(id, files ?? [], resolveAuthUserId(user));
}
@Post(':id/clearance/duty')
@BookingStaff(FREIGHT_PERMS.contracts.clearanceDutyAdvise)
@ApiOperation({ summary: 'GL ET sets duty/tax requirement and advises amount' })
adviseContractDuty(
@Param('id', ParseUUIDPipe) id: string,
@Body() dto: AdviseContractDutyDto,
@CurrentUser() user: AuthUserPayload,
) {
return this.clearanceService.adviseDuty(id, dto, resolveAuthUserId(user));
}
@Post(':id/clearance/duty-slip')
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiOperation({ summary: 'Customer uploads duty/tax payment slip on contract' })
uploadContractDutySlip(
@Param('id', ParseUUIDPipe) id: string,
@UploadedFile() file: Express.Multer.File,
) {
return this.clearanceService.uploadDutySlip(id, file);
}
@Post(':id/clearance/transit-permit')
@BookingStaff(FREIGHT_PERMS.contracts.clearanceEtActions)
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiOperation({ summary: 'GL ET uploads transit permit screenshot (import)' })
uploadTransitPermit(
@Param('id', ParseUUIDPipe) id: string,
@UploadedFile() file: Express.Multer.File,
@CurrentUser() user: AuthUserPayload,
) {
return this.clearanceService.uploadTransitPermit(id, file, resolveAuthUserId(user));
}
@Post(':id/clearance/delivery-order')
@BookingStaff(FREIGHT_PERMS.contracts.clearanceDjActions)
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiOperation({ summary: 'GL DJ uploads Delivery Order (import)' })
uploadDeliveryOrder(
@Param('id', ParseUUIDPipe) id: string,
@UploadedFile() file: Express.Multer.File,
@CurrentUser() user: AuthUserPayload,
) {
return this.clearanceService.uploadDeliveryOrder(id, file, resolveAuthUserId(user));
}
@Post(':id/clearance/release-order')
@BookingStaff(FREIGHT_PERMS.contracts.clearanceDjActions)
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiOperation({ summary: 'GL DJ uploads Release Order + vessel departure date (export)' })
uploadReleaseOrder(
@Param('id', ParseUUIDPipe) id: string,
@UploadedFile() file: Express.Multer.File,
@Body('vesselDepartureDate') vesselDepartureDate: string,
@CurrentUser() user: AuthUserPayload,
) {
return this.clearanceService.uploadReleaseOrder(
id,
file,
vesselDepartureDate,
resolveAuthUserId(user),
);
}
@Post(':id/clearance/ro-amendment')
@BookingStaff(FREIGHT_PERMS.contracts.clearanceDjActions)
@ApiOperation({ summary: 'GL DJ requests port amendment when RO vessel window is too short' })
requestRoAmendment(
@Param('id', ParseUUIDPipe) id: string,
@Body() dto: RoAmendmentDto,
@CurrentUser() user: AuthUserPayload,
) {
return this.clearanceService.requestRoAmendment(id, dto.note, resolveAuthUserId(user));
}
@Post(':id/clearance/export-release')
@BookingStaff(FREIGHT_PERMS.contracts.clearanceEtActions)
@ApiOperation({ summary: 'GL ET confirms export release after declaration' })
confirmExportRelease(
@Param('id', ParseUUIDPipe) id: string,
@CurrentUser() user: AuthUserPayload,
) {
return this.clearanceService.confirmExportRelease(id, resolveAuthUserId(user));
}
@Get('clearance/et-queue')
@BookingStaff(FREIGHT_PERMS.contracts.clearanceEtActions)
@ApiOperation({ summary: 'GL Ethiopia phased clearance queue' })
etClearanceQueue(@Query() filter: FilterContractDto) {
return this.clearanceService.etQueue(filter);
}
@Get('clearance/dj-queue')
@BookingStaff(FREIGHT_PERMS.contracts.clearanceDjActions)
@ApiOperation({ summary: 'GL Djibouti phased clearance queue' })
djClearanceQueue(@Query() filter: FilterContractDto) {
return this.clearanceService.djQueue(filter);
}
// ── Path A self-clearance — Operations reviews the customer's own docs ───────
@Get('clearance/ops-queue')
@@ -696,6 +816,18 @@ export class ContractsController {
});
}
@Post('bookings/:bookingId/transport-document')
@BookingStaff(FREIGHT_PERMS.contracts.clearanceEtActions)
@UseInterceptors(FileInterceptor('file'))
@ApiConsumes('multipart/form-data')
@ApiOperation({ summary: 'GL ET uploads export transport document after wagon allocation' })
uploadTransportDocument(
@Param('bookingId', ParseUUIDPipe) bookingId: string,
@UploadedFile() file: Express.Multer.File,
) {
return this.glOperationsService.uploadTransportDocument(bookingId, file);
}
@Post('bookings/:bookingId/documents')
@BookingStaff(FREIGHT_PERMS.bookings.uploadClearanceOutput)
@UseInterceptors(AnyFilesInterceptor())