Merge branch 'freight_feature/contrat' of github.com:Tria-plc/edr-platform into freight_feature/contrat

This commit is contained in:
marshal
2026-06-29 00:48:00 +03:00
6 changed files with 43 additions and 42 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -160,6 +160,7 @@ export default function ContractClearanceDetailPage() {
contractId={id!}
hideSummary
selfClear={false}
readOnly={ready}
/>
</Grid.Col>

View File

@@ -145,7 +145,7 @@ function StatusBadge({ row }: { row: ClearanceRow }) {
radius="sm"
leftSection={<PackageCheck size={12} />}
>
Ready customer books
Clearance finalized
</Badge>
</Tooltip>
);

View File

@@ -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>
);

View File

@@ -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"