New Transit Agents admin table (name, valid-from/to, active/suspended, validity badge) — DJ assign step now picks from it, active+valid only.

This commit is contained in:
Marshal
2026-07-29 05:38:35 +00:00
parent 891311d861
commit b17a72c280
59 changed files with 2903 additions and 451 deletions

View File

@@ -841,13 +841,13 @@ export class BookingsController {
@BookingStaff(FREIGHT_PERMS.contracts.clearanceDjActions)
@ApiOperation({
summary:
'GL Djibouti names the transit officer (free text) — unblocks the customs declaration; calling again reassigns',
'GL Djibouti picks the transit officer from the roster — unblocks the customs declaration; calling again reassigns',
})
async assignBookingTransitAssignee(
@Param('id', ParseUUIDPipe) id: string,
@Body('assignee') assignee: string,
@Body('transitAgentId', ParseUUIDPipe) transitAgentId: string,
) {
const booking = await this.bookingClearanceService.assignTransitAssignee(id, assignee);
const booking = await this.bookingClearanceService.assignTransitAssignee(id, transitAgentId);
return this.transitionService.enrichBookingResponse(booking);
}
@@ -900,17 +900,52 @@ export class BookingsController {
return this.transitionService.enrichBookingResponse(booking);
}
@Post(':id/clearance/duty/dispute')
@Post(':id/clearance/draft-declaration')
@BookingStaff(FREIGHT_PERMS.contracts.clearanceEtActions)
@UseInterceptors(AnyFilesInterceptor())
@ApiConsumes('multipart/form-data')
@ApiOperation({
summary:
'Customer disputes the advised duty/tax with a reason — reopens the step so GL Ethiopia can re-advise (repeatable)',
'GL ET sends a draft customs declaration (multi-file) with an estimated price for the customer to review',
})
async disputeBookingDuty(
async uploadBookingDraftDeclaration(
@Param('id', ParseUUIDPipe) id: string,
@Body('price') priceRaw: string,
@Body('currency') currency: string | undefined,
@UploadedFiles() files: Express.Multer.File[],
@CurrentUser() user: TCurrentUser,
) {
const booking = await this.bookingClearanceService.uploadDraftDeclaration(
id,
files ?? [],
Number(priceRaw),
currency ?? 'ETB',
resolveAuthUserId(user),
);
return this.transitionService.enrichBookingResponse(booking);
}
@Post(':id/clearance/draft-declaration/accept')
@ApiOperation({
summary:
'Customer accepts the draft customs declaration — unlocks the real customs declaration step for GL Ethiopia',
})
async acceptBookingDraftDeclaration(@Param('id', ParseUUIDPipe) id: string) {
const booking = await this.bookingClearanceService.acceptDraftDeclaration(id);
return this.transitionService.enrichBookingResponse(booking);
}
@Post(':id/clearance/draft-declaration/change')
@ApiOperation({
summary:
'Customer requests a change to the draft customs declaration with a reason — GL Ethiopia sends a corrected draft (repeatable)',
})
async requestBookingDraftDeclarationChange(
@Param('id', ParseUUIDPipe) id: string,
@Body('note') note: string,
@CurrentUser() user: TCurrentUser,
) {
const booking = await this.bookingClearanceService.disputeDuty(
const booking = await this.bookingClearanceService.requestDraftDeclarationChange(
id,
note,
resolveAuthUserId(user),