mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 21:48:18 +00:00
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:
@@ -7,6 +7,7 @@ import {
|
||||
import {
|
||||
ContractDocPhase,
|
||||
type ClearanceFinalInvoiceSummary,
|
||||
type ClearanceOffloadState,
|
||||
type ClearanceSecondDuty,
|
||||
type ClearanceT1State,
|
||||
type ClearanceTrainState,
|
||||
@@ -26,6 +27,7 @@ import { ClearanceWorkflowService } from './clearance-workflow.service';
|
||||
import { ClearanceMilestoneService } from './clearance-milestone.service';
|
||||
import { ContractNotifierService } from './contract-notifier.service';
|
||||
import { GlOperationsService } from './gl-operations.service';
|
||||
import { TransitAgentsService } from '../transit-agents/transit-agents.service';
|
||||
import {
|
||||
ClearanceMilestone,
|
||||
type RiskAssignmentRecord,
|
||||
@@ -140,6 +142,8 @@ export interface ContractClearanceView {
|
||||
t1Closed?: boolean;
|
||||
t1ClosedAt?: string | null;
|
||||
offloaded?: boolean;
|
||||
/** Offload stats for the linked booking (null until one exists). */
|
||||
offload?: ClearanceOffloadState | null;
|
||||
/** GL Djibouti post-offload final invoice (export). */
|
||||
finalInvoice?: ClearanceFinalInvoiceSummary | null;
|
||||
/** Customs risk level assigned by GL ET (import; visible to the customer). */
|
||||
@@ -165,6 +169,7 @@ export class ContractClearanceService {
|
||||
private readonly dropdownSettingsService: DropdownSettingsService,
|
||||
private readonly glOperationsService: GlOperationsService,
|
||||
private readonly notifier: ContractNotifierService,
|
||||
private readonly transitAgentsService: TransitAgentsService,
|
||||
) {}
|
||||
|
||||
private isPhasedCustoms(contract: Contract): boolean {
|
||||
@@ -433,6 +438,9 @@ export class ContractClearanceService {
|
||||
? t1ClosedMilestone.triggeredAt.toISOString()
|
||||
: null,
|
||||
offloaded: bookingMilestone('OFFLOADED')?.status === 'COMPLETED',
|
||||
offload: cycle?.bookingId
|
||||
? await this.glOperationsService.offloadState(cycle.bookingId, bookingMilestones)
|
||||
: null,
|
||||
finalInvoice,
|
||||
riskLevel:
|
||||
riskMilestone?.status === 'COMPLETED'
|
||||
@@ -1117,20 +1125,18 @@ export class ContractClearanceService {
|
||||
}
|
||||
|
||||
/**
|
||||
* GL Djibouti names the transit officer — free text, because the person is
|
||||
* not a platform user. Answering unblocks the declaration for Ethiopia. A
|
||||
* later call overwrites the name (reassignment) and re-notifies.
|
||||
* GL Djibouti picks the transit officer from the admin-managed roster —
|
||||
* rejected unless the agent is active and inside its validity window.
|
||||
* Answering unblocks the declaration for Ethiopia. A later call overwrites
|
||||
* the name (reassignment) and re-notifies.
|
||||
*/
|
||||
async assignTransitAssignee(
|
||||
contractId: string,
|
||||
assignee: string,
|
||||
transitAgentId: string,
|
||||
userId?: string,
|
||||
): Promise<Contract> {
|
||||
const contract = await this.contractsService.findById(contractId);
|
||||
this.assertPhasedCustoms(contract);
|
||||
if (!assignee?.trim()) {
|
||||
throw new BadRequestException('Name the officer who will handle the transit.');
|
||||
}
|
||||
const cycle = await this.contractsRepository.currentCycle(contractId);
|
||||
if (!cycle) throw new BadRequestException('No clearance cycle found');
|
||||
if (!cycle.transitAssigneeRequestedAt) {
|
||||
@@ -1138,16 +1144,17 @@ export class ContractClearanceService {
|
||||
'GL Ethiopia has not requested a transit assignee for this clearance yet.',
|
||||
);
|
||||
}
|
||||
const agent = await this.transitAgentsService.getAssignable(transitAgentId);
|
||||
|
||||
const previous = cycle.transitAssigneeName ?? null;
|
||||
await this.contractsRepository.updateCycle(cycle.id, {
|
||||
transitAssigneeName: assignee.trim(),
|
||||
transitAssigneeName: agent.name,
|
||||
transitAssigneeAssignedAt: new Date(),
|
||||
transitAssigneeAssignedByUserId: userId ?? null,
|
||||
});
|
||||
|
||||
const updated = await this.contractsService.findById(contractId);
|
||||
this.notifier.transitAssigneeAssigned(updated, assignee.trim(), previous);
|
||||
this.notifier.transitAssigneeAssigned(updated, agent.name, previous);
|
||||
return updated;
|
||||
}
|
||||
|
||||
@@ -1186,6 +1193,15 @@ export class ContractClearanceService {
|
||||
const contract = await this.contractsService.findById(contractId);
|
||||
this.assertPhasedCustoms(contract);
|
||||
await this.ensureDeclarationPrerequisites(contractId, contract);
|
||||
// The draft-declaration accept/change-request loop only exists on the
|
||||
// booking-scoped clearance page (portal customers never see contract-scoped
|
||||
// clearance) — skip it here so it can never block the ONE_TIME pre-booking
|
||||
// flow, which has no UI to complete it. Contracts seeded before this step
|
||||
// existed have no such row to skip — ignore, `assertPriorComplete` below
|
||||
// already tolerates a missing milestone as "not required".
|
||||
await this.workflowService
|
||||
.skipMilestones(contractId, ['DRAFT_DECLARATION_UPLOADED', 'DRAFT_DECLARATION_ACCEPTED'])
|
||||
.catch(() => undefined);
|
||||
await this.workflowService.assertPriorComplete(
|
||||
contractId,
|
||||
contract.tradeDirection,
|
||||
@@ -1215,12 +1231,6 @@ export class ContractClearanceService {
|
||||
});
|
||||
}
|
||||
|
||||
// Export: the declaration is the last GL ET pre-booking action — release
|
||||
// immediately so booking creation unlocks without a separate confirm click.
|
||||
if (contract.tradeDirection === 'EXPORT') {
|
||||
await this.workflowService.onExportReleased(contractId, userId);
|
||||
}
|
||||
|
||||
return this.contractsService.findById(contractId);
|
||||
}
|
||||
|
||||
@@ -1561,6 +1571,10 @@ export class ContractClearanceService {
|
||||
currentPhase: ContractDocPhase.GlEtOutput,
|
||||
});
|
||||
await this.workflowService.completeMilestone(contractId, 'RELEASE_ORDER_SECURED', userId);
|
||||
// Release Order is now the last GL DJ pre-booking action (it follows the
|
||||
// declaration) — release immediately so booking creation unlocks without a
|
||||
// separate confirm click.
|
||||
await this.workflowService.onExportReleased(contractId, userId);
|
||||
|
||||
return { contract: await this.contractsService.findById(contractId), hold: false };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user