mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 22:58:17 +00:00
refactor T1 document handling and update related logic for import/export processes
This commit is contained in:
@@ -244,9 +244,10 @@ export class GlOperationsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* T1 transit-document lifecycle state for an import shipment booking. Wagon
|
* T1 transit-document lifecycle state for an import shipment booking. The
|
||||||
* allocation opens the upload window; train departure locks it; train arrival
|
* gate pass (secured on the train schedule after wagon allocation) opens the
|
||||||
* lets GL Ethiopia close (accept) the T1 set.
|
* upload window; train departure locks it; train arrival lets GL Ethiopia
|
||||||
|
* close (accept) the T1 set.
|
||||||
*/
|
*/
|
||||||
async t1State(bookingId: string): Promise<Freight.ClearanceT1State> {
|
async t1State(bookingId: string): Promise<Freight.ClearanceT1State> {
|
||||||
const train = await this.trainState(bookingId);
|
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.
|
* Replaces the previous batch; locked once the train departs or T1 is closed.
|
||||||
*/
|
*/
|
||||||
async uploadT1Documents(
|
async uploadT1Documents(
|
||||||
@@ -287,6 +289,12 @@ export class GlOperationsService {
|
|||||||
'Wagons must be allocated before T1 transport documents can be uploaded.',
|
'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) {
|
if (state.closed) {
|
||||||
throw new BadRequestException('T1 has been closed by GL Ethiopia — documents are final.');
|
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.
|
* Close (accept) the T1/transport document set.
|
||||||
* Import: GL Ethiopia closes once the train has arrived (T1 files required).
|
* 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(
|
async closeT1(
|
||||||
bookingId: string,
|
bookingId: string,
|
||||||
@@ -337,10 +346,9 @@ export class GlOperationsService {
|
|||||||
'The transport document must be uploaded before T1 can be closed.',
|
'The transport document must be uploaded before T1 can be closed.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const gatepass = await this.gatepassForBooking(bookingId);
|
if (!state.trainArrivedAt) {
|
||||||
if (!gatepass.granted) {
|
|
||||||
throw new BadRequestException(
|
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.
|
// Export bookings seeded before T1_CLOSED joined the catalog lack the row.
|
||||||
|
|||||||
@@ -53,8 +53,9 @@ import { bookingsService } from "@/services/bookings.service";
|
|||||||
/**
|
/**
|
||||||
* Export customs flow, ordered per the stakeholder process:
|
* Export customs flow, ordered per the stakeholder process:
|
||||||
* customer docs → RO (DJ) → declaration (ET, auto-releases) → create booking (ET)
|
* customer docs → RO (DJ) → declaration (ET, auto-releases) → create booking (ET)
|
||||||
* → payment + wagons → transport document (ET) → train to Djibouti → gate pass (DJ)
|
* → payment + wagons → transport document / T1 (ET) → train to Djibouti
|
||||||
* → accept T1 (DJ) → final invoice (DJ) + customer slip + GL confirm.
|
* → accept T1 (DJ, one button after arrival) → gate pass (DJ)
|
||||||
|
* → final invoice (DJ) + customer slip + GL confirm.
|
||||||
*/
|
*/
|
||||||
export function computeExportActiveStep(
|
export function computeExportActiveStep(
|
||||||
clearance: ClearanceViewLike,
|
clearance: ClearanceViewLike,
|
||||||
@@ -77,8 +78,8 @@ export function computeExportActiveStep(
|
|||||||
}
|
}
|
||||||
if (!isBookingMilestoneDone(bookingMilestones, "EXPORT_TRANSPORT_ISSUED")) return 5;
|
if (!isBookingMilestoneDone(bookingMilestones, "EXPORT_TRANSPORT_ISSUED")) return 5;
|
||||||
if (!clearance.train?.arrivedAt) return 6;
|
if (!clearance.train?.arrivedAt) return 6;
|
||||||
if (!clearance.gatepassGranted) return 7;
|
if (!clearance.t1Closed) return 7;
|
||||||
if (!clearance.t1Closed) return 8;
|
if (!clearance.gatepassGranted) return 8;
|
||||||
if (clearance.finalInvoice?.status !== "PAID") return 9;
|
if (clearance.finalInvoice?.status !== "PAID") return 9;
|
||||||
return 10;
|
return 10;
|
||||||
}
|
}
|
||||||
@@ -395,17 +396,9 @@ export function ExportClearanceStepper({
|
|||||||
</Stack>
|
</Stack>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
|
|
||||||
<Stepper.Step
|
|
||||||
label="Gate pass"
|
|
||||||
description="Secured on the train schedule after arrival"
|
|
||||||
icon={clearance.gatepassGranted ? <CheckCircle2 size={14} /> : <Truck size={14} />}
|
|
||||||
>
|
|
||||||
<GatepassStep clearance={clearance} />
|
|
||||||
</Stepper.Step>
|
|
||||||
|
|
||||||
<Stepper.Step
|
<Stepper.Step
|
||||||
label="Accept T1"
|
label="Accept T1"
|
||||||
description="GL Djibouti closes the transport document"
|
description="GL Djibouti closes once the train arrives"
|
||||||
icon={clearance.t1Closed ? <CheckCircle2 size={14} /> : <PackageCheck size={14} />}
|
icon={clearance.t1Closed ? <CheckCircle2 size={14} /> : <PackageCheck size={14} />}
|
||||||
>
|
>
|
||||||
<AcceptT1Step
|
<AcceptT1Step
|
||||||
@@ -420,6 +413,14 @@ export function ExportClearanceStepper({
|
|||||||
/>
|
/>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
|
|
||||||
|
<Stepper.Step
|
||||||
|
label="Gate pass"
|
||||||
|
description="Secured on the train schedule after arrival"
|
||||||
|
icon={clearance.gatepassGranted ? <CheckCircle2 size={14} /> : <Truck size={14} />}
|
||||||
|
>
|
||||||
|
<GatepassStep clearance={clearance} />
|
||||||
|
</Stepper.Step>
|
||||||
|
|
||||||
<Stepper.Step
|
<Stepper.Step
|
||||||
label="Final invoice & payment"
|
label="Final invoice & payment"
|
||||||
description="GL Djibouti invoices after offload; customer pays"
|
description="GL Djibouti invoices after offload; customer pays"
|
||||||
@@ -554,6 +555,7 @@ function AcceptT1Step({
|
|||||||
}) {
|
}) {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const files = exportTransitFilesFromWorkflow(workflowFiles);
|
const files = exportTransitFilesFromWorkflow(workflowFiles);
|
||||||
|
const arrived = Boolean(clearance.train?.arrivedAt);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
@@ -582,9 +584,9 @@ function AcceptT1Step({
|
|||||||
pendingLabel={
|
pendingLabel={
|
||||||
!transportIssued
|
!transportIssued
|
||||||
? "Waiting for the transport document."
|
? "Waiting for the transport document."
|
||||||
: clearance.gatepassGranted
|
: arrived
|
||||||
? "Gate pass granted — GL Djibouti accepts (closes) the T1."
|
? "Train arrived — GL Djibouti accepts (closes) the T1."
|
||||||
: "Available after the gate pass is granted."
|
: "Available once the train arrives at Djibouti."
|
||||||
}
|
}
|
||||||
doneLabel=""
|
doneLabel=""
|
||||||
/>
|
/>
|
||||||
@@ -592,7 +594,7 @@ function AcceptT1Step({
|
|||||||
<Button
|
<Button
|
||||||
color="edr-green"
|
color="edr-green"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={!transportIssued || !clearance.gatepassGranted}
|
disabled={!transportIssued || !arrived}
|
||||||
leftSection={<PackageCheck size={16} />}
|
leftSection={<PackageCheck size={16} />}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ function computeImportActiveStep(
|
|||||||
clearance: ClearanceViewLike,
|
clearance: ClearanceViewLike,
|
||||||
bookingCreated: boolean,
|
bookingCreated: boolean,
|
||||||
bookingMilestones: MilestoneRow[],
|
bookingMilestones: MilestoneRow[],
|
||||||
|
t1Uploaded: boolean,
|
||||||
): number {
|
): number {
|
||||||
if (!isMilestoneDone(clearance.milestones, "DOCUMENTS_APPROVED")) return 0;
|
if (!isMilestoneDone(clearance.milestones, "DOCUMENTS_APPROVED")) return 0;
|
||||||
if (!isMilestoneDone(clearance.milestones, "DECLARED")) return 1;
|
if (!isMilestoneDone(clearance.milestones, "DECLARED")) return 1;
|
||||||
@@ -119,16 +120,17 @@ function computeImportActiveStep(
|
|||||||
if (!isMilestoneDone(clearance.milestones, "DO_COLLECTED")) return 6;
|
if (!isMilestoneDone(clearance.milestones, "DO_COLLECTED")) return 6;
|
||||||
if (!bookingCreated) return 7;
|
if (!bookingCreated) return 7;
|
||||||
if (!clearance.gatepassGranted) return 8;
|
if (!clearance.gatepassGranted) return 8;
|
||||||
if (!clearance.t1?.closed) return 9;
|
if (!t1Uploaded && !clearance.t1?.closed) return 9;
|
||||||
if (!isBookingMilestoneDone(bookingMilestones, "RISK_ASSIGNED")) return 10;
|
if (!clearance.t1?.closed) return 10;
|
||||||
|
if (!isBookingMilestoneDone(bookingMilestones, "RISK_ASSIGNED")) return 11;
|
||||||
// Additional duty round is optional — resolved once skipped or paid.
|
// Additional duty round is optional — resolved once skipped or paid.
|
||||||
const secondDutyResolved =
|
const secondDutyResolved =
|
||||||
clearance.secondDuty?.skipped ||
|
clearance.secondDuty?.skipped ||
|
||||||
clearance.secondDuty?.paid ||
|
clearance.secondDuty?.paid ||
|
||||||
isBookingMilestoneDone(bookingMilestones, "SECOND_DUTY_PAID");
|
isBookingMilestoneDone(bookingMilestones, "SECOND_DUTY_PAID");
|
||||||
if (!secondDutyResolved) return 11;
|
if (!secondDutyResolved) return 12;
|
||||||
if (!clearance.importReleaseGranted) return 12;
|
if (!clearance.importReleaseGranted) return 13;
|
||||||
return 13;
|
return 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
function t1FilesFromWorkflow(
|
function t1FilesFromWorkflow(
|
||||||
@@ -226,12 +228,18 @@ export function PhasedClearanceActionPanel({
|
|||||||
// The booking that carries the post-booking steps (gate pass, risk, duty, release).
|
// The booking that carries the post-booking steps (gate pass, risk, duty, release).
|
||||||
const actionBookingId =
|
const actionBookingId =
|
||||||
clearance.t1?.bookingId ?? clearance.linkedBookingId ?? bookingId ?? null;
|
clearance.t1?.bookingId ?? clearance.linkedBookingId ?? bookingId ?? null;
|
||||||
|
const t1Uploaded = t1FilesFromWorkflow(workflowFiles).length > 0;
|
||||||
const activeStep = useMemo(
|
const activeStep = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isImport
|
isImport
|
||||||
? computeImportActiveStep(clearance, effectiveBookingCreated, bookingMilestones)
|
? computeImportActiveStep(
|
||||||
|
clearance,
|
||||||
|
effectiveBookingCreated,
|
||||||
|
bookingMilestones,
|
||||||
|
t1Uploaded,
|
||||||
|
)
|
||||||
: 0,
|
: 0,
|
||||||
[clearance, isImport, effectiveBookingCreated, bookingMilestones],
|
[clearance, isImport, effectiveBookingCreated, bookingMilestones, t1Uploaded],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isImport) {
|
if (isImport) {
|
||||||
@@ -544,22 +552,45 @@ export function PhasedClearanceActionPanel({
|
|||||||
|
|
||||||
<Stepper.Step
|
<Stepper.Step
|
||||||
label="T1 transport documents"
|
label="T1 transport documents"
|
||||||
description="GL Djibouti uploads after wagon allocation; GL Ethiopia closes on arrival"
|
description="GL Djibouti uploads after the gate pass is secured"
|
||||||
icon={
|
icon={
|
||||||
clearance.t1?.closed ? <CheckCircle2 size={14} /> : <Truck size={14} />
|
t1Uploaded || clearance.t1?.closed ? (
|
||||||
|
<CheckCircle2 size={14} />
|
||||||
|
) : (
|
||||||
|
<Truck size={14} />
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<ImportT1Section
|
<ImportT1UploadStep
|
||||||
t1={clearance.t1 ?? null}
|
t1={clearance.t1 ?? null}
|
||||||
|
gatepassGranted={Boolean(clearance.gatepassGranted)}
|
||||||
workflowFiles={workflowFiles}
|
workflowFiles={workflowFiles}
|
||||||
canDjAct={showDj && canDj}
|
canDjAct={showDj && canDj}
|
||||||
canEtAct={showEt && canEt}
|
|
||||||
onChanged={onChanged}
|
onChanged={onChanged}
|
||||||
onViewFile={onViewFile}
|
onViewFile={onViewFile}
|
||||||
onDownloadFile={onDownloadFile}
|
onDownloadFile={onDownloadFile}
|
||||||
/>
|
/>
|
||||||
</Stepper.Step>
|
</Stepper.Step>
|
||||||
|
|
||||||
|
<Stepper.Step
|
||||||
|
label="Close T1"
|
||||||
|
description="GL Ethiopia closes once the train arrives"
|
||||||
|
icon={
|
||||||
|
clearance.t1?.closed ? (
|
||||||
|
<CheckCircle2 size={14} />
|
||||||
|
) : (
|
||||||
|
<PackageCheck size={14} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ImportT1CloseStep
|
||||||
|
t1={clearance.t1 ?? null}
|
||||||
|
t1Uploaded={t1Uploaded}
|
||||||
|
canEtAct={showEt && canEt}
|
||||||
|
onChanged={onChanged}
|
||||||
|
/>
|
||||||
|
</Stepper.Step>
|
||||||
|
|
||||||
<Stepper.Step
|
<Stepper.Step
|
||||||
label="Customs risk"
|
label="Customs risk"
|
||||||
description="GL Ethiopia assigns Green / Yellow / Red"
|
description="GL Ethiopia assigns Green / Yellow / Red"
|
||||||
@@ -645,26 +676,29 @@ export function PhasedClearanceActionPanel({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ImportT1Section({
|
/**
|
||||||
|
* GL Djibouti uploads the T1 transport documents (multi-file) once the gate
|
||||||
|
* pass is secured on the train schedule; locked on departure or T1 close.
|
||||||
|
*/
|
||||||
|
function ImportT1UploadStep({
|
||||||
t1,
|
t1,
|
||||||
|
gatepassGranted,
|
||||||
workflowFiles = [],
|
workflowFiles = [],
|
||||||
canDjAct,
|
canDjAct,
|
||||||
canEtAct,
|
|
||||||
onChanged,
|
onChanged,
|
||||||
onViewFile,
|
onViewFile,
|
||||||
onDownloadFile,
|
onDownloadFile,
|
||||||
}: {
|
}: {
|
||||||
t1: Freight.ClearanceT1State | null;
|
t1: Freight.ClearanceT1State | null;
|
||||||
|
gatepassGranted: boolean;
|
||||||
workflowFiles?: Freight.ClearanceWorkflowFile[];
|
workflowFiles?: Freight.ClearanceWorkflowFile[];
|
||||||
canDjAct: boolean;
|
canDjAct: boolean;
|
||||||
canEtAct: boolean;
|
|
||||||
onChanged?: () => void;
|
onChanged?: () => void;
|
||||||
onViewFile?: (file: { name: string; url: string }) => void;
|
onViewFile?: (file: { name: string; url: string }) => void;
|
||||||
onDownloadFile?: (file: { id: string; name: string }) => void;
|
onDownloadFile?: (file: { id: string; name: string }) => void;
|
||||||
}) {
|
}) {
|
||||||
const [files, setFiles] = useState<File[]>([]);
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const [closing, setClosing] = useState(false);
|
|
||||||
|
|
||||||
const uploaded = t1FilesFromWorkflow(workflowFiles);
|
const uploaded = t1FilesFromWorkflow(workflowFiles);
|
||||||
const replaceMode = uploaded.length > 0;
|
const replaceMode = uploaded.length > 0;
|
||||||
@@ -680,8 +714,8 @@ function ImportT1Section({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const departed = Boolean(t1.trainDepartedAt);
|
const departed = Boolean(t1.trainDepartedAt);
|
||||||
const arrived = Boolean(t1.trainArrivedAt);
|
const canUpload =
|
||||||
const canUpload = canDjAct && t1.wagonAllocated && !departed && !t1.closed;
|
canDjAct && t1.wagonAllocated && gatepassGranted && !departed && !t1.closed;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="sm">
|
<Stack gap="sm">
|
||||||
@@ -707,7 +741,7 @@ function ImportT1Section({
|
|||||||
<StepStatus
|
<StepStatus
|
||||||
done
|
done
|
||||||
pendingLabel=""
|
pendingLabel=""
|
||||||
doneLabel="T1 accepted and closed by GL Ethiopia — documents are final."
|
doneLabel="T1 documents are final — closed by GL Ethiopia."
|
||||||
/>
|
/>
|
||||||
) : !t1.wagonAllocated ? (
|
) : !t1.wagonAllocated ? (
|
||||||
<StepStatus
|
<StepStatus
|
||||||
@@ -715,6 +749,12 @@ function ImportT1Section({
|
|||||||
pendingLabel="Waiting for operations to allocate wagons."
|
pendingLabel="Waiting for operations to allocate wagons."
|
||||||
doneLabel=""
|
doneLabel=""
|
||||||
/>
|
/>
|
||||||
|
) : !gatepassGranted ? (
|
||||||
|
<StepStatus
|
||||||
|
done={false}
|
||||||
|
pendingLabel="Waiting for the gate pass to be secured on the train schedule."
|
||||||
|
doneLabel=""
|
||||||
|
/>
|
||||||
) : departed ? (
|
) : departed ? (
|
||||||
<Alert color="orange" variant="light" icon={<AlertTriangle size={16} />}>
|
<Alert color="orange" variant="light" icon={<AlertTriangle size={16} />}>
|
||||||
The train has departed — T1 documents are locked and can no longer be changed.
|
The train has departed — T1 documents are locked and can no longer be changed.
|
||||||
@@ -725,6 +765,8 @@ function ImportT1Section({
|
|||||||
pendingLabel="Waiting for GL Djibouti to upload T1 transport documents."
|
pendingLabel="Waiting for GL Djibouti to upload T1 transport documents."
|
||||||
doneLabel=""
|
doneLabel=""
|
||||||
/>
|
/>
|
||||||
|
) : uploaded.length > 0 && !canUpload ? (
|
||||||
|
<StepStatus done pendingLabel="" doneLabel="T1 documents uploaded." />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{canUpload ? (
|
{canUpload ? (
|
||||||
@@ -771,40 +813,103 @@ function ImportT1Section({
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
{canEtAct && !t1.closed ? (
|
/**
|
||||||
arrived ? (
|
* GL Ethiopia closes (accepts) the T1 set with one click once the train has
|
||||||
<Stack gap="xs">
|
* arrived. Separate step from the GL Djibouti upload.
|
||||||
<Text size="sm" c="dimmed">
|
*/
|
||||||
The train has arrived — review the T1 documents and close (accept) them.
|
function ImportT1CloseStep({
|
||||||
</Text>
|
t1,
|
||||||
<Button
|
t1Uploaded,
|
||||||
color="edr-green"
|
canEtAct,
|
||||||
loading={closing}
|
onChanged,
|
||||||
disabled={uploaded.length === 0}
|
}: {
|
||||||
leftSection={<PackageCheck size={16} />}
|
t1: Freight.ClearanceT1State | null;
|
||||||
onClick={async () => {
|
t1Uploaded: boolean;
|
||||||
setClosing(true);
|
canEtAct: boolean;
|
||||||
try {
|
onChanged?: () => void;
|
||||||
await contractsService.closeT1(t1.bookingId);
|
}) {
|
||||||
toast.success("T1 closed");
|
const [closing, setClosing] = useState(false);
|
||||||
onChanged?.();
|
|
||||||
} catch (e) {
|
if (!t1) {
|
||||||
toast.error(e instanceof Error ? e.message : "Failed");
|
return (
|
||||||
} finally {
|
<StepStatus
|
||||||
setClosing(false);
|
done={false}
|
||||||
}
|
pendingLabel="Available once the shipment booking is created."
|
||||||
}}
|
doneLabel=""
|
||||||
>
|
/>
|
||||||
Accept & close T1
|
);
|
||||||
</Button>
|
}
|
||||||
</Stack>
|
|
||||||
) : departed ? (
|
if (t1.closed) {
|
||||||
<Alert color="blue" variant="light" icon={<Clock size={16} />}>
|
return (
|
||||||
Train en route — T1 can be closed once it arrives in Ethiopia.
|
<StepStatus
|
||||||
</Alert>
|
done
|
||||||
) : null
|
pendingLabel=""
|
||||||
) : null}
|
doneLabel={`T1 accepted and closed by GL Ethiopia${
|
||||||
|
t1.closedAt ? ` · ${new Date(t1.closedAt).toLocaleString()}` : ""
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!t1Uploaded) {
|
||||||
|
return (
|
||||||
|
<StepStatus
|
||||||
|
done={false}
|
||||||
|
pendingLabel="Waiting for GL Djibouti to upload T1 transport documents."
|
||||||
|
doneLabel=""
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!t1.trainArrivedAt) {
|
||||||
|
return (
|
||||||
|
<Alert color="blue" variant="light" icon={<Clock size={16} />}>
|
||||||
|
{t1.trainDepartedAt
|
||||||
|
? "Train en route — T1 can be closed once it arrives in Ethiopia."
|
||||||
|
: "T1 can be closed once the train arrives in Ethiopia."}
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!canEtAct) {
|
||||||
|
return (
|
||||||
|
<StepStatus
|
||||||
|
done={false}
|
||||||
|
pendingLabel="Train arrived — waiting for GL Ethiopia to close the T1."
|
||||||
|
doneLabel=""
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
The train has arrived — review the T1 documents and close (accept) them.
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
color="edr-green"
|
||||||
|
loading={closing}
|
||||||
|
leftSection={<PackageCheck size={16} />}
|
||||||
|
onClick={async () => {
|
||||||
|
setClosing(true);
|
||||||
|
try {
|
||||||
|
await contractsService.closeT1(t1.bookingId);
|
||||||
|
toast.success("T1 closed");
|
||||||
|
onChanged?.();
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(e instanceof Error ? e.message : "Failed");
|
||||||
|
} finally {
|
||||||
|
setClosing(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Accept & close T1
|
||||||
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user