mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 02:58:11 +00:00
Refactor clearance terminology to document approval across services and UI components
This commit is contained in:
@@ -157,13 +157,13 @@ export class BookingLifecycleNotifierService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Clearance finalized → customer can proceed to request operation. */
|
/** Document approval finalized → customer can proceed to request operation. */
|
||||||
clearanceReady(b: Booking): void {
|
clearanceReady(b: Booking): void {
|
||||||
const msg =
|
const msg =
|
||||||
`Clearance for booking ${b.reference} is complete. ` +
|
`Document approval for booking ${b.reference} is finalized. ` +
|
||||||
`You can now proceed to request operation from the portal.`;
|
`You can now proceed to request operation from the portal.`;
|
||||||
void this.notifyContact(b, msg, 'CLEARANCE READY');
|
void this.notifyContact(b, msg, 'DOCUMENT APPROVAL FINALIZED');
|
||||||
this.inApp(b, 'Clearance complete', msg, {
|
this.inApp(b, 'Document approval finalized', msg, {
|
||||||
type: NotificationType.CLEARANCE_DECISION,
|
type: NotificationType.CLEARANCE_DECISION,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -456,7 +456,7 @@ export class ContractClearanceService {
|
|||||||
const allowed = ['CLEARANCE_UNDER_REVIEW', 'AWAITING_CLEARANCE_DOCUMENTS'];
|
const allowed = ['CLEARANCE_UNDER_REVIEW', 'AWAITING_CLEARANCE_DOCUMENTS'];
|
||||||
if (!allowed.includes(contract.status)) {
|
if (!allowed.includes(contract.status)) {
|
||||||
throw new ConflictException(
|
throw new ConflictException(
|
||||||
`Cannot finalize clearance on status "${contract.status}".`,
|
`Cannot finalize document approval on status "${contract.status}".`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,12 +135,14 @@ export function ClearanceReviewSection({
|
|||||||
const finalizeMutation = useMutation({
|
const finalizeMutation = useMutation({
|
||||||
mutationFn: () => bookingsService.finalizeClearance(bookingId),
|
mutationFn: () => bookingsService.finalizeClearance(bookingId),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success("Clearance finalized");
|
toast.success("Document approval finalized");
|
||||||
refresh();
|
refresh();
|
||||||
},
|
},
|
||||||
onError: (e) =>
|
onError: (e) =>
|
||||||
toast.error(
|
toast.error(
|
||||||
e instanceof Error ? e.message : "Could not finalize clearance",
|
e instanceof Error
|
||||||
|
? e.message
|
||||||
|
: "Could not finalize document approval",
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -366,7 +368,7 @@ export function ClearanceReviewSection({
|
|||||||
<Alert color="red" radius="md" icon={<AlertCircle size={16} />}>
|
<Alert color="red" radius="md" icon={<AlertCircle size={16} />}>
|
||||||
{finalizeMutation.error instanceof Error
|
{finalizeMutation.error instanceof Error
|
||||||
? finalizeMutation.error.message
|
? finalizeMutation.error.message
|
||||||
: "Could not finalize clearance."}
|
: "Could not finalize document approval."}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -445,7 +447,7 @@ export function ClearanceReviewSection({
|
|||||||
loading={finalizeMutation.isPending}
|
loading={finalizeMutation.isPending}
|
||||||
onClick={() => finalizeMutation.mutate()}
|
onClick={() => finalizeMutation.mutate()}
|
||||||
>
|
>
|
||||||
Finalize clearance
|
Finalize document approval
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ export interface ContractClearanceReviewSectionProps {
|
|||||||
queriesLocked?: boolean;
|
queriesLocked?: boolean;
|
||||||
/**
|
/**
|
||||||
* ONE_TIME customs contracts use the phased milestone workflow. Hides the
|
* ONE_TIME customs contracts use the phased milestone workflow. Hides the
|
||||||
* legacy "Finalize clearance" shortcut; booking readiness follows delivery
|
* legacy "Finalize document approval" shortcut; booking readiness follows delivery
|
||||||
* order (import) or export release.
|
* order (import) or export release.
|
||||||
*/
|
*/
|
||||||
phasedCustoms?: boolean;
|
phasedCustoms?: boolean;
|
||||||
@@ -393,7 +393,7 @@ export function ContractClearanceReviewSection({
|
|||||||
<Alert color="red" radius="md" icon={<AlertCircle size={16} />}>
|
<Alert color="red" radius="md" icon={<AlertCircle size={16} />}>
|
||||||
{finalizeClearance.error instanceof Error
|
{finalizeClearance.error instanceof Error
|
||||||
? finalizeClearance.error.message
|
? finalizeClearance.error.message
|
||||||
: "Could not finalize clearance."}
|
: "Could not finalize document approval."}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -482,7 +482,7 @@ export function ContractClearanceReviewSection({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
Finalize clearance
|
Finalize document approval
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -160,7 +160,8 @@ export default function BuildTrainModal({ opened, onClose, onBuilt }: BuildTrain
|
|||||||
onChange={(value) => setImportTrainNumber(value ?? "")}
|
onChange={(value) => setImportTrainNumber(value ?? "")}
|
||||||
searchable
|
searchable
|
||||||
clearable
|
clearable
|
||||||
nothingFoundMessage="No free run numbers — add more in Dropdown Settings"
|
nothingFoundMessage={importNumbers.emptyMessage}
|
||||||
|
error={importNumbers.settingMissing ? importNumbers.emptyMessage : undefined}
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@@ -105,7 +105,8 @@ const EditTrainDetailsModal = ({ train, onClose }: EditTrainDetailsModalProps) =
|
|||||||
}}
|
}}
|
||||||
searchable
|
searchable
|
||||||
clearable
|
clearable
|
||||||
nothingFoundMessage="No free run numbers — add more in Dropdown Settings"
|
nothingFoundMessage={importNumbers.emptyMessage}
|
||||||
|
error={importNumbers.settingMissing ? importNumbers.emptyMessage : undefined}
|
||||||
radius="md"
|
radius="md"
|
||||||
/>
|
/>
|
||||||
<TextInput
|
<TextInput
|
||||||
|
|||||||
@@ -348,14 +348,16 @@ export function useContractClearanceMutations(
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success(
|
toast.success(
|
||||||
selfClear
|
selfClear
|
||||||
? "Clearance approved — customer can now book"
|
? "Document approval finalized — customer can now book"
|
||||||
: "Clearance finalized — ready for booking",
|
: "Document approval finalized — ready for booking",
|
||||||
);
|
);
|
||||||
refresh();
|
refresh();
|
||||||
},
|
},
|
||||||
onError: (e) =>
|
onError: (e) =>
|
||||||
toast.error(
|
toast.error(
|
||||||
e instanceof Error ? e.message : "Could not finalize clearance",
|
e instanceof Error
|
||||||
|
? e.message
|
||||||
|
: "Could not finalize document approval",
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { IMPORT_TRAIN_OPTIONS } from "@/constants/trainRuns";
|
|
||||||
import { api } from "@/services/api";
|
import { api } from "@/services/api";
|
||||||
|
|
||||||
/** Dropdown-settings code holding the admin-managed IMPORT run numbers. */
|
/** Dropdown-settings code holding the admin-managed IMPORT run numbers. */
|
||||||
@@ -14,10 +13,11 @@ export interface ImportTrainNumberOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selectable IMPORT run numbers for the Train Builder, sourced from the
|
* Selectable IMPORT run numbers for the Train Builder, sourced solely from the
|
||||||
* admin-managed `import_train_numbers` dropdown setting (admins add new runs
|
* admin-managed `import_train_numbers` dropdown setting — admins add and remove
|
||||||
* from the Dropdown Settings editor). Falls back to the legacy hardcoded run
|
* runs from /dashboard/dropdown-settings and the pickers follow. There is no
|
||||||
* list while the setting is missing or has no options.
|
* hardcoded fallback on purpose: a missing setting must be visible (see
|
||||||
|
* `settingMissing`) rather than masked by stale defaults.
|
||||||
*
|
*
|
||||||
* Numbers already claimed by an existing train are kept in the list but
|
* Numbers already claimed by an existing train are kept in the list but
|
||||||
* disabled and tagged "in use". Pass `currentNumber` when editing a train so
|
* disabled and tagged "in use". Pass `currentNumber` when editing a train so
|
||||||
@@ -37,14 +37,13 @@ export function useImportTrainNumberOptions(currentNumber?: string | null) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const options = useMemo<ImportTrainNumberOption[]>(() => {
|
const options = useMemo<ImportTrainNumberOption[]>(() => {
|
||||||
const configured = [...(settingQuery.data?.children ?? [])]
|
const base = [...(settingQuery.data?.children ?? [])]
|
||||||
.filter((option) => !option.disabled)
|
.filter((option) => !option.disabled)
|
||||||
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
||||||
.map((option) => ({
|
.map((option) => ({
|
||||||
value: option.value,
|
value: option.value,
|
||||||
label: option.label || option.value,
|
label: option.label || option.value,
|
||||||
}));
|
}));
|
||||||
const base = configured.length ? configured : IMPORT_TRAIN_OPTIONS;
|
|
||||||
|
|
||||||
const used = new Set(usedQuery.data?.importTrainNumbers ?? []);
|
const used = new Set(usedQuery.data?.importTrainNumbers ?? []);
|
||||||
if (currentNumber) used.delete(currentNumber);
|
if (currentNumber) used.delete(currentNumber);
|
||||||
@@ -60,8 +59,16 @@ export function useImportTrainNumberOptions(currentNumber?: string | null) {
|
|||||||
return items;
|
return items;
|
||||||
}, [settingQuery.data, usedQuery.data, currentNumber]);
|
}, [settingQuery.data, usedQuery.data, currentNumber]);
|
||||||
|
|
||||||
|
const settingMissing = settingQuery.isError;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
options,
|
options,
|
||||||
isLoading: settingQuery.isLoading || usedQuery.isLoading,
|
isLoading: settingQuery.isLoading || usedQuery.isLoading,
|
||||||
|
/** True when the dropdown setting is absent — surfaced instead of silently
|
||||||
|
* falling back, so a broken config is visible rather than looking normal. */
|
||||||
|
settingMissing,
|
||||||
|
emptyMessage: settingMissing
|
||||||
|
? `Dropdown setting "${IMPORT_TRAIN_NUMBERS_CODE}" is missing — create it in Dropdown Settings`
|
||||||
|
: "No free run numbers — add more in Dropdown Settings",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ export default function DocumentClearanceListPage({
|
|||||||
subtitle={
|
subtitle={
|
||||||
opsMode
|
opsMode
|
||||||
? "Review the customer's own clearance documents per shipment booking, raise queries, and finalize."
|
? "Review the customer's own clearance documents per shipment booking, raise queries, and finalize."
|
||||||
: "Review customer documents, raise queries, and finalize clearance for each booking."
|
: "Review customer documents, raise queries, and finalize document approval for each booking."
|
||||||
}
|
}
|
||||||
meta={statusBadge}
|
meta={statusBadge}
|
||||||
action={
|
action={
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ function StatusBadge({ row }: { row: ClearanceRow }) {
|
|||||||
if (row.ready) {
|
if (row.ready) {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label="Clearance finalized — the customer creates the booking in the portal"
|
label="Document approval finalized — the customer creates the booking in the portal"
|
||||||
withArrow
|
withArrow
|
||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
@@ -266,7 +266,7 @@ function StatusBadge({ row }: { row: ClearanceRow }) {
|
|||||||
radius="sm"
|
radius="sm"
|
||||||
leftSection={<PackageCheck size={12} />}
|
leftSection={<PackageCheck size={12} />}
|
||||||
>
|
>
|
||||||
Clearance finalized
|
Documents approved
|
||||||
</Badge>
|
</Badge>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user