mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
update import gl flow
This commit is contained in:
@@ -135,7 +135,7 @@ export class BookingInvoiceService {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (booking.paymentStatus === "PAID") return;
|
||||
// if (booking.paymentStatus === "PAID") return;
|
||||
|
||||
await this.dataSource.transaction(async (mg) => {
|
||||
await mg.update(
|
||||
|
||||
@@ -272,7 +272,7 @@ export class Booking extends BaseEntity {
|
||||
@Column({ name: 'origin_yard_id', type: 'uuid' })
|
||||
originYardId!: string;
|
||||
|
||||
@ManyToOne(() => Yard)
|
||||
@ManyToOne(() => Yard)
|
||||
@JoinColumn({ name: 'origin_yard_id' })
|
||||
originYard?: Yard;
|
||||
|
||||
|
||||
@@ -65,6 +65,16 @@ function makeService(overrides?: {
|
||||
children: [{ value: '2' }],
|
||||
}),
|
||||
};
|
||||
const glOperationsService = {
|
||||
t1State: jest.fn().mockResolvedValue({
|
||||
bookingId: 'b-general',
|
||||
wagonAllocated: false,
|
||||
trainDepartedAt: null,
|
||||
trainArrivedAt: null,
|
||||
closed: false,
|
||||
closedAt: null,
|
||||
}),
|
||||
};
|
||||
|
||||
const service = new BookingClearanceService(
|
||||
bookingsRepository as never,
|
||||
@@ -74,6 +84,7 @@ function makeService(overrides?: {
|
||||
workflowService as never,
|
||||
milestoneService as never,
|
||||
dropdownSettingsService as never,
|
||||
glOperationsService as never,
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { BadRequestException, 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';
|
||||
@@ -11,6 +11,7 @@ import { Booking } from '../bookings/entities/booking.entity';
|
||||
import { clearanceCodesForBooking } from '../bookings/clearance.util';
|
||||
import { ClearanceWorkflowService } from './clearance-workflow.service';
|
||||
import { ClearanceMilestoneService } from './clearance-milestone.service';
|
||||
import { GlOperationsService } from './gl-operations.service';
|
||||
import { AdviseContractDutyDto } from './dto/phased-clearance.dto';
|
||||
import { buildWorkflowFiles, belongsOnDjClearanceQueue, belongsOnEtClearanceQueue, DJ_BOOKING_QUEUE_STATUSES, persistDeclarationUploads, persistTransitPermitUploads, PHASED_CUSTOMS_BOOKING_QUEUE_STATUSES } from './phased-clearance.util';
|
||||
|
||||
@@ -63,6 +64,8 @@ export interface BookingClearanceView {
|
||||
noticeFile?: { id: string; name: string; url: string } | null;
|
||||
} | null;
|
||||
workflowFiles?: ReturnType<typeof buildWorkflowFiles>;
|
||||
/** Import post-allocation T1 transit document state (null until wagon allocation). */
|
||||
t1?: ClearanceT1State | null;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@@ -75,6 +78,7 @@ export class BookingClearanceService {
|
||||
private readonly workflowService: ClearanceWorkflowService,
|
||||
private readonly milestoneService: ClearanceMilestoneService,
|
||||
private readonly dropdownSettingsService: DropdownSettingsService,
|
||||
private readonly glOperationsService: GlOperationsService,
|
||||
) {}
|
||||
|
||||
private async assertPhasedGeneralCustoms(booking: Booking): Promise<void> {
|
||||
@@ -162,6 +166,15 @@ export class BookingClearanceService {
|
||||
booking.tradeDirection ?? 'IMPORT',
|
||||
);
|
||||
|
||||
let t1: ClearanceT1State | null = null;
|
||||
if ((booking.tradeDirection ?? 'IMPORT') === 'IMPORT') {
|
||||
try {
|
||||
t1 = await this.glOperationsService.t1State(bookingId);
|
||||
} catch {
|
||||
t1 = null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
bookingId,
|
||||
status: booking.status,
|
||||
@@ -192,6 +205,7 @@ export class BookingClearanceService {
|
||||
preClearanceFinalized: Boolean(booking.preClearanceFinalizedAt),
|
||||
dutyAdvice,
|
||||
workflowFiles,
|
||||
t1,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -421,6 +435,13 @@ export class BookingClearanceService {
|
||||
clearanceCurrentPhase: ContractDocPhase.GlDjCollection,
|
||||
} as never);
|
||||
|
||||
// GL Djibouti may have uploaded the DO early (un-gated) — count it now.
|
||||
const files = await this.filesService.findByResource(bookingId, 'bookings');
|
||||
if (files.some((f) => f.code === 'delivery_order')) {
|
||||
await this.workflowService.completeMilestoneForBooking(bookingId, 'DO_COLLECTED');
|
||||
await this.workflowService.markReadyForOperation(bookingId);
|
||||
}
|
||||
|
||||
return this.bookingsService.findById(bookingId);
|
||||
}
|
||||
|
||||
@@ -434,15 +455,11 @@ export class BookingClearanceService {
|
||||
throw new BadRequestException('Delivery Order applies only to import bookings.');
|
||||
}
|
||||
|
||||
if (!booking.preClearanceFinalizedAt) {
|
||||
throw new BadRequestException(
|
||||
'GL Ethiopia must finalize pre-clearance before the Delivery Order can be uploaded.',
|
||||
);
|
||||
}
|
||||
|
||||
await this.workflowService.assertPriorCompleteForBooking(bookingId, '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 operation readiness) still
|
||||
// waits for GL Ethiopia to finalize pre-clearance so the workflow order holds.
|
||||
await this.filesService.upsertByCode({
|
||||
resourceId: bookingId,
|
||||
resource: 'bookings',
|
||||
@@ -450,8 +467,10 @@ export class BookingClearanceService {
|
||||
file,
|
||||
});
|
||||
|
||||
await this.workflowService.completeMilestoneForBooking(bookingId, 'DO_COLLECTED', userId);
|
||||
await this.workflowService.markReadyForOperation(bookingId);
|
||||
if (booking.preClearanceFinalizedAt) {
|
||||
await this.workflowService.completeMilestoneForBooking(bookingId, 'DO_COLLECTED', userId);
|
||||
await this.workflowService.markReadyForOperation(bookingId);
|
||||
}
|
||||
|
||||
return this.bookingsService.findById(bookingId);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -873,6 +873,33 @@ export class ContractsController {
|
||||
return this.glOperationsService.uploadTransportDocument(bookingId, files ?? []);
|
||||
}
|
||||
|
||||
@Post('bookings/:bookingId/t1-documents')
|
||||
@BookingStaff(FREIGHT_PERMS.contracts.clearanceDjActions)
|
||||
@UseInterceptors(AnyFilesInterceptor())
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiOperation({
|
||||
summary:
|
||||
'GL Djibouti uploads T1 transit documents (multi-file) after wagon allocation; locked once the train departs',
|
||||
})
|
||||
uploadT1Documents(
|
||||
@Param('bookingId', ParseUUIDPipe) bookingId: string,
|
||||
@UploadedFiles() files: Express.Multer.File[],
|
||||
) {
|
||||
return this.glOperationsService.uploadT1Documents(bookingId, files ?? []);
|
||||
}
|
||||
|
||||
@Post('bookings/:bookingId/t1-close')
|
||||
@BookingStaff(FREIGHT_PERMS.contracts.clearanceEtActions)
|
||||
@ApiOperation({
|
||||
summary: 'GL Ethiopia closes (accepts) the T1 document set after the train arrives',
|
||||
})
|
||||
closeT1(
|
||||
@Param('bookingId', ParseUUIDPipe) bookingId: string,
|
||||
@CurrentUser() user: AuthUserPayload,
|
||||
) {
|
||||
return this.glOperationsService.closeT1(bookingId, resolveAuthUserId(user));
|
||||
}
|
||||
|
||||
@Post('bookings/:bookingId/documents')
|
||||
@BookingStaff(FREIGHT_PERMS.bookings.uploadClearanceOutput)
|
||||
@UseInterceptors(AnyFilesInterceptor())
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { isT1TransportFileCode, type Freight } from '@edr/types';
|
||||
|
||||
import { FilesService } from '../files/files.service';
|
||||
import { Booking } from '../bookings/entities/booking.entity';
|
||||
import { TrainSchedule } from '../train-schedules/entities/train-schedule.entity';
|
||||
import {
|
||||
ClearanceIncident,
|
||||
IncidentType,
|
||||
} from './entities/clearance-incident.entity';
|
||||
import { ClearanceMilestoneService } from './clearance-milestone.service';
|
||||
import { persistExportTransportUploads } from './phased-clearance.util';
|
||||
import {
|
||||
persistExportTransportUploads,
|
||||
persistT1TransportUploads,
|
||||
} from './phased-clearance.util';
|
||||
|
||||
/**
|
||||
* Maps a GL post-booking document `code` to the milestone it auto-completes when
|
||||
@@ -18,7 +23,8 @@ import { persistExportTransportUploads } from './phased-clearance.util';
|
||||
const DOC_CODE_TO_MILESTONE: Record<string, string> = {
|
||||
release_order: 'RELEASE_ORDER_SECURED', // export — GL DJ
|
||||
delivery_order: 'DO_COLLECTED', // import — GL DJ
|
||||
t1_transport_document: 'T1_CLOSED', // import — GL ET
|
||||
// t1_transport_document intentionally NOT doc-triggered: T1_CLOSED completes only
|
||||
// when GL Ethiopia accepts the T1 set after the train arrives (closeT1).
|
||||
import_release: 'IMPORT_RELEASE_GRANTED', // import — GL ET
|
||||
full_in_interchange: 'OFFLOADED', // export — GL DJ
|
||||
final_declaration: 'IMPORT_PROCESS_COMPLETED', // import — GL ET
|
||||
@@ -161,6 +167,114 @@ export class GlOperationsService {
|
||||
return { uploaded: files.length, completedMilestones };
|
||||
}
|
||||
|
||||
/**
|
||||
* T1 transit-document lifecycle state for an import shipment booking. Wagon
|
||||
* allocation opens the upload window; train departure locks it; train arrival
|
||||
* lets GL Ethiopia close (accept) the T1 set.
|
||||
*/
|
||||
async t1State(bookingId: string): Promise<Freight.ClearanceT1State> {
|
||||
const booking = await this.getBooking(bookingId);
|
||||
const milestones = await this.milestoneService.listForBooking(bookingId);
|
||||
|
||||
const wagonMilestone = milestones.find((m) => m.milestoneCode === 'WAGON_ALLOCATED');
|
||||
const wagonAllocated =
|
||||
wagonMilestone?.status === 'COMPLETED' ||
|
||||
booking.schedulingStatus === 'SCHEDULED' ||
|
||||
booking.schedulingStatus === 'DISPATCHED' ||
|
||||
Boolean(booking.trainScheduleId);
|
||||
|
||||
let schedule: TrainSchedule | null = null;
|
||||
if (booking.trainScheduleId) {
|
||||
schedule = await this.dataSource
|
||||
.getRepository(TrainSchedule)
|
||||
.findOne({ where: { id: booking.trainScheduleId } });
|
||||
}
|
||||
|
||||
const closedMilestone = milestones.find(
|
||||
(m) => m.milestoneCode === 'T1_CLOSED' && m.status === 'COMPLETED',
|
||||
);
|
||||
|
||||
return {
|
||||
bookingId,
|
||||
wagonAllocated,
|
||||
trainDepartedAt: schedule?.actualDepartureAt
|
||||
? new Date(schedule.actualDepartureAt).toISOString()
|
||||
: null,
|
||||
trainArrivedAt: schedule?.actualArrivalAt
|
||||
? new Date(schedule.actualArrivalAt).toISOString()
|
||||
: null,
|
||||
closed: Boolean(closedMilestone),
|
||||
closedAt: closedMilestone?.triggeredAt
|
||||
? new Date(closedMilestone.triggeredAt).toISOString()
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* GL Djibouti uploads T1 transport documents (multi-file) after wagon allocation.
|
||||
* Replaces the previous batch; locked once the train departs or T1 is closed.
|
||||
*/
|
||||
async uploadT1Documents(
|
||||
bookingId: string,
|
||||
files: Express.Multer.File[],
|
||||
): Promise<{ uploaded: number }> {
|
||||
const booking = await this.getBooking(bookingId);
|
||||
if (booking.tradeDirection !== 'IMPORT') {
|
||||
throw new BadRequestException('T1 transport documents apply to import shipments only.');
|
||||
}
|
||||
|
||||
const state = await this.t1State(bookingId);
|
||||
if (!state.wagonAllocated) {
|
||||
throw new BadRequestException(
|
||||
'Wagons must be allocated before T1 transport documents can be uploaded.',
|
||||
);
|
||||
}
|
||||
if (state.closed) {
|
||||
throw new BadRequestException('T1 has been closed by GL Ethiopia — documents are final.');
|
||||
}
|
||||
if (state.trainDepartedAt) {
|
||||
throw new BadRequestException(
|
||||
'The train has departed — T1 transport documents can no longer be changed.',
|
||||
);
|
||||
}
|
||||
|
||||
await persistT1TransportUploads(this.filesService, bookingId, files);
|
||||
return { uploaded: files.length };
|
||||
}
|
||||
|
||||
/**
|
||||
* GL Ethiopia closes (accepts) the T1 document set once the train has arrived.
|
||||
* Completes the T1_CLOSED milestone; the document set becomes final.
|
||||
*/
|
||||
async closeT1(
|
||||
bookingId: string,
|
||||
userId?: string,
|
||||
): Promise<Freight.ClearanceT1State> {
|
||||
const booking = await this.getBooking(bookingId);
|
||||
if (booking.tradeDirection !== 'IMPORT') {
|
||||
throw new BadRequestException('T1 closure applies to import shipments only.');
|
||||
}
|
||||
|
||||
const state = await this.t1State(bookingId);
|
||||
if (state.closed) return state;
|
||||
if (!state.trainArrivedAt) {
|
||||
throw new BadRequestException(
|
||||
'The train has not arrived yet — T1 can be closed only after arrival.',
|
||||
);
|
||||
}
|
||||
|
||||
const files = await this.filesService.findByResource(bookingId, 'bookings');
|
||||
const hasT1 = files.some((f) => isT1TransportFileCode(f.code));
|
||||
if (!hasT1) {
|
||||
throw new BadRequestException(
|
||||
'No T1 transport documents on file — GL Djibouti must upload them first.',
|
||||
);
|
||||
}
|
||||
|
||||
await this.milestoneService.completeForBooking(bookingId, 'T1_CLOSED', userId);
|
||||
return this.t1State(bookingId);
|
||||
}
|
||||
|
||||
/**
|
||||
* GL ET uploads export transport document after wagon allocation (export ONE_TIME).
|
||||
*/
|
||||
|
||||
@@ -5,7 +5,9 @@ import {
|
||||
isDeclarationFileCode,
|
||||
isImportTransitPermitFileCode,
|
||||
isExportTransportFileCode,
|
||||
isT1TransportFileCode,
|
||||
exportTransportFileLabel,
|
||||
t1TransportFileLabel,
|
||||
transitPermitFileLabel,
|
||||
type ClearanceWorkflowFile,
|
||||
} from '@edr/types';
|
||||
@@ -160,6 +162,50 @@ export async function persistExportTransportUploads(
|
||||
);
|
||||
}
|
||||
|
||||
/** Require at least one T1 transport document in the upload batch. */
|
||||
export function assertT1TransportFiles(files: Express.Multer.File[]): void {
|
||||
if (files.length === 0) {
|
||||
throw new BadRequestException('No T1 transport documents uploaded');
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeT1TransportFieldNames(
|
||||
files: Express.Multer.File[],
|
||||
): Express.Multer.File[] {
|
||||
return files.map((file, index) => ({
|
||||
...file,
|
||||
fieldname: `t1_transport_document_${index}`,
|
||||
}));
|
||||
}
|
||||
|
||||
/** Replace all T1 transport documents on a booking with a new multi-file batch. */
|
||||
export async function persistT1TransportUploads(
|
||||
store: DeclarationFileStore,
|
||||
bookingId: string,
|
||||
files: Express.Multer.File[],
|
||||
): Promise<void> {
|
||||
const normalized = normalizeT1TransportFieldNames(files);
|
||||
assertT1TransportFiles(normalized);
|
||||
|
||||
const existing = await store.findByResource(bookingId, 'bookings');
|
||||
await Promise.all(
|
||||
existing
|
||||
.filter((f) => f.code && isT1TransportFileCode(f.code))
|
||||
.map((f) => store.deleteByCode(bookingId, 'bookings', f.code!)),
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
normalized.map((file, index) =>
|
||||
store.upload({
|
||||
resourceId: bookingId,
|
||||
resource: 'bookings',
|
||||
code: `t1_transport_document_${index}`,
|
||||
file,
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
export function parseDutyRequiredForm(value: string | boolean | undefined): boolean {
|
||||
if (typeof value === 'boolean') return value;
|
||||
if (value === undefined || value === '') return false;
|
||||
@@ -194,9 +240,9 @@ export function belongsOnDjClearanceQueue(
|
||||
);
|
||||
if (hasDjActivity) return true;
|
||||
|
||||
const preFinalized =
|
||||
cycle?.preClearanceFinalizedAt ?? extras?.preClearanceFinalizedAt ?? null;
|
||||
if (tradeDirection === 'IMPORT' && preFinalized) return true;
|
||||
// Import DO upload is un-gated — Djibouti GL must see import customs items from
|
||||
// the start, not only after Ethiopia finalizes pre-clearance.
|
||||
if (tradeDirection === 'IMPORT') return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -295,6 +341,22 @@ export function buildWorkflowFiles(
|
||||
file: { id: file.id, name: file.name, url: file.url },
|
||||
});
|
||||
});
|
||||
|
||||
const extraT1 = files
|
||||
.filter((f) => f.code && isT1TransportFileCode(f.code) && !included.has(f.code))
|
||||
.sort((a, b) => (a.code ?? '').localeCompare(b.code ?? ''));
|
||||
|
||||
extraT1.forEach((file, index) => {
|
||||
if (!file.code) return;
|
||||
included.add(file.code);
|
||||
out.push({
|
||||
code: file.code,
|
||||
label: t1TransportFileLabel(file.code, index),
|
||||
uploadedBy: 'gl_dj',
|
||||
category: 'djibouti',
|
||||
file: { id: file.id, name: file.name, url: file.url },
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (tradeDirection === 'EXPORT') {
|
||||
|
||||
Reference in New Issue
Block a user