mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-27 14:20:58 +00:00
update booking process for customs clearance contracts, allowing customers to create bookings post-clearance finalization
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
@@ -192,18 +191,15 @@ export class ContractBookingService {
|
||||
*/
|
||||
private async assertGate(contract: Contract, isGlActor: boolean): Promise<string> {
|
||||
if (contract.customsClearingEnabled) {
|
||||
// Path B — GL Ethiopia only.
|
||||
if (!isGlActor) {
|
||||
throw new ForbiddenException(
|
||||
'Only Global Logistics Ethiopia can create bookings for customs-clearance contracts.',
|
||||
);
|
||||
}
|
||||
// Path B — the customer creates the booking once GL has finalized the
|
||||
// pre-booking clearance (GL "create booking" was removed; clearance ends
|
||||
// at finalize and hands the booking back to the customer).
|
||||
if (contract.clearanceStatus !== 'CLEARANCE_READY_FOR_BOOKING') {
|
||||
throw new BadRequestException(
|
||||
'Contract clearance is not ready for booking yet.',
|
||||
);
|
||||
}
|
||||
return 'GL_ET';
|
||||
return isGlActor ? 'GL_ET' : 'CUSTOMER';
|
||||
}
|
||||
|
||||
// Path A — customer (or staff) once the contract is executed.
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
Check,
|
||||
FileSignature,
|
||||
MessageSquareWarning,
|
||||
PackagePlus,
|
||||
ShieldCheck,
|
||||
Sparkles,
|
||||
XCircle,
|
||||
@@ -21,8 +20,6 @@ import {
|
||||
import type { Freight } from "@edr/types";
|
||||
|
||||
import { SectionCard } from "@/components/bookings/detail/SectionCard";
|
||||
import { useAuth } from "@/auth/useAuth";
|
||||
import { canCreateContractBooking } from "@/lib/permissions";
|
||||
import type { useContractMutations } from "@/hooks/contracts/useContracts";
|
||||
|
||||
type Mutations = ReturnType<typeof useContractMutations>;
|
||||
@@ -49,7 +46,6 @@ export function ContractActionsToolbar({
|
||||
onReviewClearance,
|
||||
}: ContractActionsToolbarProps) {
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
const { status } = contract;
|
||||
|
||||
const [acceptOpen, setAcceptOpen] = useState(false);
|
||||
@@ -85,10 +81,6 @@ export function ContractActionsToolbar({
|
||||
const canViewContract =
|
||||
["CONTRACT_READY", "SIGNED_CUSTOMER"].includes(status) &&
|
||||
Boolean(contract.contractGeneratedAt);
|
||||
const canCreateBooking =
|
||||
status === "CLEARANCE_READY_FOR_BOOKING" &&
|
||||
contract.customsClearingEnabled &&
|
||||
canCreateContractBooking(user);
|
||||
// Show "Review clearance" while the contract is in the document-review phase.
|
||||
// Reviewer = GL (Path B / customs) or Operations (Path A / no customs).
|
||||
const canReviewClearance =
|
||||
@@ -174,23 +166,12 @@ export function ContractActionsToolbar({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{canCreateBooking && (
|
||||
<Button
|
||||
fullWidth
|
||||
color="edr-green"
|
||||
leftSection={<PackagePlus size={16} />}
|
||||
onClick={() =>
|
||||
navigate(`/dashboard/contracts/${contract.id}/create-booking`)
|
||||
}
|
||||
>
|
||||
Create booking (GL)
|
||||
</Button>
|
||||
)}
|
||||
{/* GL "Create booking" removed for now — clearance ends at finalize and
|
||||
the customer creates the booking in the portal. */}
|
||||
|
||||
{!canAccept &&
|
||||
!needsManualGenerate &&
|
||||
!canViewContract &&
|
||||
!canCreateBooking &&
|
||||
!canReviewClearance && (
|
||||
<Text size="sm" c="dimmed">
|
||||
No staff actions available for this status. Monitor until the
|
||||
|
||||
@@ -160,6 +160,7 @@ export default function ContractClearanceDetailPage() {
|
||||
contractId={id!}
|
||||
hideSummary
|
||||
selfClear={false}
|
||||
readOnly={ready}
|
||||
/>
|
||||
</Grid.Col>
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ function StatusBadge({ row }: { row: ClearanceRow }) {
|
||||
radius="sm"
|
||||
leftSection={<PackageCheck size={12} />}
|
||||
>
|
||||
Ready — customer books
|
||||
Clearance finalized
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import {
|
||||
Alert,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
Download,
|
||||
Eye,
|
||||
FileText,
|
||||
PackagePlus,
|
||||
Plus,
|
||||
Upload,
|
||||
} from "lucide-react";
|
||||
@@ -85,6 +87,7 @@ export function ContractClearancePanel({
|
||||
bare,
|
||||
}: ContractClearancePanelProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
const [pending, setPending] = useState<Record<string, File>>({});
|
||||
const [adHoc, setAdHoc] = useState<AdHocDoc[]>([]);
|
||||
const { view, viewer } = useFileViewer();
|
||||
@@ -192,9 +195,8 @@ export function ContractClearancePanel({
|
||||
)}
|
||||
{isReady ? (
|
||||
<Alert color="teal" radius="md" icon={<CheckCircle2 size={18} />} mb="md">
|
||||
{customsPath
|
||||
? "Your clearance documents are approved. Global Logistics will create your booking — you will be notified when payment is due."
|
||||
: "Your clearance documents are approved. You can now create a shipment booking under this contract."}
|
||||
Your clearance documents are approved. You can now create a shipment
|
||||
booking under this contract.
|
||||
</Alert>
|
||||
) : isUnderReview ? (
|
||||
<Alert color="blue" radius="md" icon={<Clock size={18} />} mb="md">
|
||||
@@ -437,6 +439,20 @@ export function ContractClearancePanel({
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
|
||||
{/* Clearance finalized → the customer creates the booking himself. */}
|
||||
{isReady && status === "CLEARANCE_READY_FOR_BOOKING" && (
|
||||
<Group justify="flex-end" mt="lg">
|
||||
<Button
|
||||
color="edr-green"
|
||||
radius="md"
|
||||
leftSection={<PackagePlus size={16} />}
|
||||
onClick={() => navigate(`/contracts/${contractId}/bookings/new`)}
|
||||
>
|
||||
Create booking
|
||||
</Button>
|
||||
</Group>
|
||||
)}
|
||||
{viewer}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -205,14 +205,21 @@ export default function ContractDetailPage() {
|
||||
|
||||
const canSign = contract.status === "CONTRACT_READY";
|
||||
const customsPath = contract.customsClearingEnabled;
|
||||
// The customer creates the booking himself unless GL owns it (customs / Path B).
|
||||
// Reached only once the contract is executed (after self-clearance on Path A).
|
||||
const canBookShipment =
|
||||
!customsPath && PATH_A_BOOKABLE.includes(contract.status);
|
||||
// The customer uploads clearance documents on the contract while in a clearance
|
||||
// status — Path B (customs, GL-reviewed) or Path A self-clearance (Operations).
|
||||
// The customer creates the booking himself on BOTH paths:
|
||||
// - Path A (no customs): once the contract is executed after self-clearance.
|
||||
// - Path B (customs): once GL finalizes the pre-booking clearance
|
||||
// (CLEARANCE_READY_FOR_BOOKING). GL "create booking" was removed.
|
||||
const clearanceFinalized =
|
||||
contract.clearanceStatus === "CLEARANCE_READY_FOR_BOOKING" ||
|
||||
contract.status === "CLEARANCE_READY_FOR_BOOKING";
|
||||
const canBookShipment = customsPath
|
||||
? clearanceFinalized
|
||||
: PATH_A_BOOKABLE.includes(contract.status);
|
||||
// The customer uploads clearance documents while in a clearance status — but
|
||||
// once clearance is finalized the upload step is done; the action becomes
|
||||
// "create booking" instead.
|
||||
const canUploadClearance =
|
||||
CLEARANCE_UPLOAD_STATUSES.includes(contract.status);
|
||||
CLEARANCE_UPLOAD_STATUSES.includes(contract.status) && !clearanceFinalized;
|
||||
|
||||
return (
|
||||
<Box style={{ padding: "28px 32px 40px" }}>
|
||||
@@ -517,10 +524,10 @@ export default function ContractDetailPage() {
|
||||
<Text fz={13} c="dimmed">
|
||||
{customsPath
|
||||
? contract.status === "AWAITING_CLEARANCE_DOCUMENTS"
|
||||
? "Upload your clearance documents so Global Logistics can review them. After approval, Global Logistics creates your booking — you only pay the freight."
|
||||
? "Upload your clearance documents so Global Logistics can review them. Once they finalize the clearance you can create your shipment booking."
|
||||
: contract.status === "CLEARANCE_UNDER_REVIEW"
|
||||
? "Global Logistics is reviewing your clearance documents. Re-upload any queried documents to proceed."
|
||||
: "Your documents are cleared. Global Logistics will create your booking shortly — you will be notified when payment is due."
|
||||
: "Your documents are cleared. You can now create a shipment booking under this contract."
|
||||
: contract.status === "AWAITING_CLEARANCE_DOCUMENTS"
|
||||
? "This service does not include EDR customs clearance. Clear the cargo yourself and upload your clearance documents so the Operations team can review them before you book a shipment."
|
||||
: contract.status === "CLEARANCE_UNDER_REVIEW"
|
||||
|
||||
Reference in New Issue
Block a user