update import gl flow

This commit is contained in:
Marshal
2026-07-02 13:24:39 +00:00
parent 03740ee719
commit fe40d9f4be
8 changed files with 280 additions and 29 deletions

View File

@@ -1,5 +1,5 @@
import { BadRequestException, ConflictException, Injectable } from '@nestjs/common';
import { ContractDocPhase } from '@edr/types';
import { ContractDocPhase, type ClearanceT1State } from '@edr/types';
import { DropdownSettingsService } from '../dropdown-settings/dropdown-settings.service';
import { FileUploadSettingsService } from '../file-upload-settings/file-upload-settings.service';
@@ -10,6 +10,7 @@ import { BookingsService } from '../bookings/bookings.service';
import { contractClearanceCodes } from './contract-clearance.util';
import { ClearanceWorkflowService } from './clearance-workflow.service';
import { ClearanceMilestoneService } from './clearance-milestone.service';
import { GlOperationsService } from './gl-operations.service';
import { ClearanceMilestone } from './entities/clearance-milestone.entity';
import { Contract } from './entities/contract.entity';
import { ContractDocReviewStatus } from './entities/contract-document-review.entity';
@@ -77,6 +78,8 @@ export interface ContractClearanceView {
noticeFile?: { id: string; name: string; url: string } | null;
} | null;
workflowFiles?: ReturnType<typeof buildWorkflowFiles>;
/** Import post-allocation T1 transit document state (null until a booking is linked). */
t1?: ClearanceT1State | null;
}
@Injectable()
@@ -90,6 +93,7 @@ export class ContractClearanceService {
private readonly workflowService: ClearanceWorkflowService,
private readonly milestoneService: ClearanceMilestoneService,
private readonly dropdownSettingsService: DropdownSettingsService,
private readonly glOperationsService: GlOperationsService,
) {}
private isPhasedCustoms(contract: Contract): boolean {
@@ -224,6 +228,15 @@ export class ContractClearanceService {
workflowFiles = [...byCode.values()];
}
let t1: ClearanceT1State | null = null;
if (cycle?.bookingId && contract.tradeDirection === 'IMPORT') {
try {
t1 = await this.glOperationsService.t1State(cycle.bookingId);
} catch {
t1 = null; // linked booking missing — view stays usable
}
}
let nextAction = this.workflowService.computeNextAction(contract, cycle, milestones);
if (cycle?.bookingId && contract.tradeDirection === 'EXPORT') {
const bookingMilestones = await this.workflowService.listMilestonesForBooking(
@@ -272,6 +285,7 @@ export class ContractClearanceService {
linkedBookingId: cycle?.bookingId ?? null,
dutyAdvice,
workflowFiles,
t1,
};
}
@@ -1011,6 +1025,13 @@ export class ContractClearanceService {
currentPhase: ContractDocPhase.GlDjCollection,
});
// GL Djibouti may have uploaded the DO early (un-gated) — count it now.
const files = await this.filesService.findByResource(contractId, 'contracts');
if (files.some((f) => f.code === 'delivery_order')) {
await this.workflowService.completeMilestone(contractId, 'DO_COLLECTED');
await this.workflowService.markReadyForBooking(contractId);
}
return this.contractsService.findById(contractId);
}
@@ -1025,17 +1046,11 @@ export class ContractClearanceService {
throw new BadRequestException('Delivery Order applies only to import contracts.');
}
const cycle = await this.contractsRepository.currentCycle(contractId);
if (!cycle?.preClearanceFinalizedAt) {
throw new BadRequestException(
'GL Ethiopia must finalize pre-clearance before the Delivery Order can be uploaded.',
);
}
await this.workflowService.assertPriorComplete(contractId, 'IMPORT', 'DO_COLLECTED');
if (!file) throw new BadRequestException('No Delivery Order uploaded');
// DO upload is deliberately un-gated: GL Djibouti may attach it at any point,
// any file type. The DO_COLLECTED milestone (and booking readiness) still waits
// for GL Ethiopia to finalize pre-clearance so the workflow order holds.
await this.filesService.upsertByCode({
resourceId: contractId,
resource: 'contracts',
@@ -1043,8 +1058,11 @@ export class ContractClearanceService {
file,
});
await this.workflowService.completeMilestone(contractId, 'DO_COLLECTED', userId);
await this.workflowService.markReadyForBooking(contractId);
const cycle = await this.contractsRepository.currentCycle(contractId);
if (cycle?.preClearanceFinalizedAt) {
await this.workflowService.completeMilestone(contractId, 'DO_COLLECTED', userId);
await this.workflowService.markReadyForBooking(contractId);
}
return this.contractsService.findById(contractId);
}