diff --git a/apps/edr-freight-api/src/modules/contracts/gl-operations.service.ts b/apps/edr-freight-api/src/modules/contracts/gl-operations.service.ts index e639f0867..72e2d14d9 100644 --- a/apps/edr-freight-api/src/modules/contracts/gl-operations.service.ts +++ b/apps/edr-freight-api/src/modules/contracts/gl-operations.service.ts @@ -244,9 +244,10 @@ export class GlOperationsService { } /** - * 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. + * T1 transit-document lifecycle state for an import shipment booking. The + * gate pass (secured on the train schedule after 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 { const train = await this.trainState(bookingId); @@ -269,7 +270,8 @@ export class GlOperationsService { } /** - * GL Djibouti uploads T1 transport documents (multi-file) after wagon allocation. + * GL Djibouti uploads T1 transport documents (multi-file) once the gate pass + * is secured on the train schedule (which itself follows wagon allocation). * Replaces the previous batch; locked once the train departs or T1 is closed. */ async uploadT1Documents( @@ -287,6 +289,12 @@ export class GlOperationsService { 'Wagons must be allocated before T1 transport documents can be uploaded.', ); } + const gatepass = await this.gatepassForBooking(bookingId); + if (!gatepass.granted) { + throw new BadRequestException( + 'Secure the Djibouti gate pass on the train schedule before uploading T1 transport documents.', + ); + } if (state.closed) { throw new BadRequestException('T1 has been closed by GL Ethiopia — documents are final.'); } @@ -303,7 +311,8 @@ export class GlOperationsService { /** * Close (accept) the T1/transport document set. * Import: GL Ethiopia closes once the train has arrived (T1 files required). - * Export: GL Djibouti closes after the gate pass (transport document required). + * Export: GL Djibouti closes once the train arrives at Djibouti (transport + * document required). */ async closeT1( bookingId: string, @@ -337,10 +346,9 @@ export class GlOperationsService { 'The transport document must be uploaded before T1 can be closed.', ); } - const gatepass = await this.gatepassForBooking(bookingId); - if (!gatepass.granted) { + if (!state.trainArrivedAt) { throw new BadRequestException( - 'Secure the Djibouti gate pass on the train schedule before closing T1.', + 'The train has not arrived at Djibouti yet — T1 can be closed only after arrival.', ); } // Export bookings seeded before T1_CLOSED joined the catalog lack the row. diff --git a/apps/edr-freight-web/backoffice/src/components/contracts/ExportClearanceStepper.tsx b/apps/edr-freight-web/backoffice/src/components/contracts/ExportClearanceStepper.tsx index b13e38961..db6e79430 100644 --- a/apps/edr-freight-web/backoffice/src/components/contracts/ExportClearanceStepper.tsx +++ b/apps/edr-freight-web/backoffice/src/components/contracts/ExportClearanceStepper.tsx @@ -53,8 +53,9 @@ import { bookingsService } from "@/services/bookings.service"; /** * Export customs flow, ordered per the stakeholder process: * customer docs → RO (DJ) → declaration (ET, auto-releases) → create booking (ET) - * → payment + wagons → transport document (ET) → train to Djibouti → gate pass (DJ) - * → accept T1 (DJ) → final invoice (DJ) + customer slip + GL confirm. + * → payment + wagons → transport document / T1 (ET) → train to Djibouti + * → accept T1 (DJ, one button after arrival) → gate pass (DJ) + * → final invoice (DJ) + customer slip + GL confirm. */ export function computeExportActiveStep( clearance: ClearanceViewLike, @@ -77,8 +78,8 @@ export function computeExportActiveStep( } if (!isBookingMilestoneDone(bookingMilestones, "EXPORT_TRANSPORT_ISSUED")) return 5; if (!clearance.train?.arrivedAt) return 6; - if (!clearance.gatepassGranted) return 7; - if (!clearance.t1Closed) return 8; + if (!clearance.t1Closed) return 7; + if (!clearance.gatepassGranted) return 8; if (clearance.finalInvoice?.status !== "PAID") return 9; return 10; } @@ -395,17 +396,9 @@ export function ExportClearanceStepper({ - : } - > - - - : } > + : } + > + + + @@ -582,9 +584,9 @@ function AcceptT1Step({ pendingLabel={ !transportIssued ? "Waiting for the transport document." - : clearance.gatepassGranted - ? "Gate pass granted — GL Djibouti accepts (closes) the T1." - : "Available after the gate pass is granted." + : arrived + ? "Train arrived — GL Djibouti accepts (closes) the T1." + : "Available once the train arrives at Djibouti." } doneLabel="" /> @@ -592,7 +594,7 @@ function AcceptT1Step({ ) : null} + + ); +} - {canEtAct && !t1.closed ? ( - arrived ? ( - - - The train has arrived — review the T1 documents and close (accept) them. - - - - ) : departed ? ( - }> - Train en route — T1 can be closed once it arrives in Ethiopia. - - ) : null - ) : null} +/** + * GL Ethiopia closes (accepts) the T1 set with one click once the train has + * arrived. Separate step from the GL Djibouti upload. + */ +function ImportT1CloseStep({ + t1, + t1Uploaded, + canEtAct, + onChanged, +}: { + t1: Freight.ClearanceT1State | null; + t1Uploaded: boolean; + canEtAct: boolean; + onChanged?: () => void; +}) { + const [closing, setClosing] = useState(false); + + if (!t1) { + return ( + + ); + } + + if (t1.closed) { + return ( + + ); + } + + if (!t1Uploaded) { + return ( + + ); + } + + if (!t1.trainArrivedAt) { + return ( + }> + {t1.trainDepartedAt + ? "Train en route — T1 can be closed once it arrives in Ethiopia." + : "T1 can be closed once the train arrives in Ethiopia."} + + ); + } + + if (!canEtAct) { + return ( + + ); + } + + return ( + + + The train has arrived — review the T1 documents and close (accept) them. + + ); }