mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 22:58:17 +00:00
changes on transit agent
This commit is contained in:
@@ -417,12 +417,14 @@ export class BookingLifecycleNotifierService {
|
||||
b: Booking,
|
||||
agent: { id: string; name: string },
|
||||
previous: string | null,
|
||||
/** Who named the officer — GL Djibouti unless the clearing agent did. */
|
||||
by = 'GL Djibouti',
|
||||
): void {
|
||||
const assignee = agent.name;
|
||||
const msg = previous
|
||||
? `GL Djibouti changed the transit assignee for shipment ${b.reference} from ` +
|
||||
? `${by} changed the transit assignee for shipment ${b.reference} from ` +
|
||||
`"${previous}" to "${assignee}".`
|
||||
: `GL Djibouti assigned ${assignee} to handle shipment ${b.reference} in transit. ` +
|
||||
: `${by} assigned ${assignee} to handle shipment ${b.reference} in transit. ` +
|
||||
`The customs declaration can now be filed.`;
|
||||
this.logger.log(`TRANSIT ASSIGNEE ASSIGNED — ${this.ref(b)}`);
|
||||
this.inAppStaff(b, `Transit assignee set — ${b.reference}`, msg, {
|
||||
@@ -441,7 +443,7 @@ export class BookingLifecycleNotifierService {
|
||||
{
|
||||
title: 'New shipment assigned to you',
|
||||
body:
|
||||
`GL Djibouti assigned shipment ${b.reference} to ${assignee} for transit. ` +
|
||||
`${by} assigned shipment ${b.reference} to ${assignee} for transit. ` +
|
||||
`Open it in the portal to see what is needed.`,
|
||||
officerLink: `/transit-agent/bookings/${b.id}`,
|
||||
forwarderLink: '/forwarder/assigned-bookings',
|
||||
|
||||
@@ -878,6 +878,33 @@ export class BookingsController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gate a GL review action that the assigned clearing agent may also take.
|
||||
*
|
||||
* On a without-customs booking the freight forwarder the customer assigned
|
||||
* clears customs in GL's place: it reviews the customer's documents, asks
|
||||
* for missing ones and finalizes. Staff pass on their permission; a portal
|
||||
* caller must be assigned to THIS booking and the booking must be one GL
|
||||
* does not clear — a customs booking stays with Global Logistics.
|
||||
*/
|
||||
private async assertStaffOrClearingAgent(
|
||||
bookingId: string,
|
||||
user: TCurrentUser,
|
||||
staffPermission: string,
|
||||
): Promise<void> {
|
||||
if (hasFreightPermission(user, staffPermission)) return;
|
||||
const booking = await this.bookingsService.findById(bookingId);
|
||||
if (
|
||||
booking.customsClearingEnabled ||
|
||||
!(await this.bookingsService.isTransitAgentForBooking(
|
||||
user?.id,
|
||||
bookingId,
|
||||
))
|
||||
) {
|
||||
throw new NotFoundException(`Booking ${bookingId} not found`);
|
||||
}
|
||||
}
|
||||
|
||||
private async assertWagonCancellationActor(
|
||||
cancellationId: string,
|
||||
user: TCurrentUser,
|
||||
@@ -1372,16 +1399,24 @@ export class BookingsController {
|
||||
return this.transitionService.enrichBookingResponse(booking);
|
||||
}
|
||||
|
||||
// Also the clearing agent assigned to a without-customs booking — see
|
||||
// assertStaffOrClearingAgent.
|
||||
@Post(":id/clearance/review")
|
||||
@BookingStaff(FREIGHT_PERMS.bookings.reviewDocuments)
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.reviewDocuments)
|
||||
@ApiOperation({
|
||||
summary: "GL reviews a clearance document (Approve | Query)",
|
||||
summary:
|
||||
"GL — or the assigned clearing agent — reviews a clearance document (Approve | Query)",
|
||||
})
|
||||
async reviewClearanceDocument(
|
||||
@Param("id", ParseUUIDPipe) id: string,
|
||||
@Body() dto: ReviewDocumentDto,
|
||||
@CurrentUser() user: AuthUserPayload,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
await this.assertStaffOrClearingAgent(
|
||||
id,
|
||||
user,
|
||||
FREIGHT_PERMS.bookings.reviewDocuments,
|
||||
);
|
||||
const booking = await this.transitionService.reviewDocument(
|
||||
id,
|
||||
dto.fileKey,
|
||||
@@ -1393,16 +1428,21 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/clearance/doc-requests")
|
||||
@BookingStaff(FREIGHT_PERMS.contracts.clearanceEtActions)
|
||||
@MixedAudience(FREIGHT_PERMS.contracts.clearanceEtActions)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"GL asks the customer for additional clearance document(s) — shown on the portal with author and time",
|
||||
"GL — or the assigned clearing agent — asks the customer for additional clearance document(s); shown on the portal with author and time",
|
||||
})
|
||||
async requestAdditionalDocuments(
|
||||
@Param("id", ParseUUIDPipe) id: string,
|
||||
@Body("note") note: string,
|
||||
@CurrentUser() user: AuthUserPayload,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
await this.assertStaffOrClearingAgent(
|
||||
id,
|
||||
user,
|
||||
FREIGHT_PERMS.contracts.clearanceEtActions,
|
||||
);
|
||||
await this.transitionService.requestAdditionalDocuments(
|
||||
id,
|
||||
note,
|
||||
@@ -1686,15 +1726,20 @@ export class BookingsController {
|
||||
}
|
||||
|
||||
@Post(":id/clearance/finalize")
|
||||
@BookingStaff(FREIGHT_PERMS.bookings.finalizeClearance)
|
||||
@MixedAudience(FREIGHT_PERMS.bookings.finalizeClearance)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"GL finalizes clearance (requires 100% approved) → CLEARANCE_READY",
|
||||
"GL — or the assigned clearing agent — finalizes clearance (requires 100% approved) → CLEARANCE_READY",
|
||||
})
|
||||
async finalizeClearance(
|
||||
@Param("id", ParseUUIDPipe) id: string,
|
||||
@CurrentUser() user: AuthUserPayload,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
await this.assertStaffOrClearingAgent(
|
||||
id,
|
||||
user,
|
||||
FREIGHT_PERMS.bookings.finalizeClearance,
|
||||
);
|
||||
const booking = await this.transitionService.finalizeClearance(
|
||||
id,
|
||||
resolveAuthUserId(user),
|
||||
@@ -1721,21 +1766,36 @@ export class BookingsController {
|
||||
return this.transitionService.enrichBookingResponse(booking);
|
||||
}
|
||||
|
||||
// Also the clearing agent (freight forwarder) the customer assigned: on a
|
||||
// without-customs booking there is no GL Djibouti desk in the loop, so the
|
||||
// forwarder names the Djibouti officer itself. Any other portal caller is
|
||||
// rejected below, hidden behind a NotFound like the ownership checks.
|
||||
@Post(":id/clearance/transit-assignee/assign")
|
||||
@BookingStaff(FREIGHT_PERMS.contracts.clearanceDjActions)
|
||||
@MixedAudience(FREIGHT_PERMS.contracts.clearanceDjActions)
|
||||
@ApiOperation({
|
||||
summary:
|
||||
"GL Djibouti picks the transit officer from the roster — unblocks the customs declaration; calling again reassigns",
|
||||
"GL Djibouti — or the assigned clearing agent — picks the Djibouti transit officer from the roster; calling again reassigns",
|
||||
})
|
||||
async assignBookingTransitAssignee(
|
||||
@Param("id", ParseUUIDPipe) id: string,
|
||||
@Body("transitAgentId", ParseUUIDPipe) transitAgentId: string,
|
||||
@CurrentUser() user: AuthUserPayload,
|
||||
@CurrentUser() user: TCurrentUser,
|
||||
) {
|
||||
const isStaff = hasFreightPermission(
|
||||
user,
|
||||
FREIGHT_PERMS.contracts.clearanceDjActions,
|
||||
);
|
||||
if (
|
||||
!isStaff &&
|
||||
!(await this.bookingsService.isTransitAgentForBooking(user?.id, id))
|
||||
) {
|
||||
throw new NotFoundException(`Booking ${id} not found`);
|
||||
}
|
||||
const booking = await this.bookingClearanceService.assignTransitAssignee(
|
||||
id,
|
||||
transitAgentId,
|
||||
resolveAuthUserId(user),
|
||||
{ byAssignedAgent: !isStaff },
|
||||
);
|
||||
return this.transitionService.enrichBookingResponse(booking);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user