mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 08:48:11 +00:00
266 lines
6.5 KiB
TypeScript
266 lines
6.5 KiB
TypeScript
import type { Freight } from "@edr/types";
|
|
import type { LucideIcon } from "lucide-react";
|
|
import {
|
|
CreditCard,
|
|
Eye,
|
|
FileSignature,
|
|
Hourglass,
|
|
PackagePlus,
|
|
PencilLine,
|
|
Receipt,
|
|
RotateCcw,
|
|
Upload,
|
|
} from "lucide-react";
|
|
|
|
import { getContractBookingAction } from "@/pages/contracts/contract-booking-action";
|
|
|
|
const CLEARANCE_IN_PROGRESS_STATUSES = [
|
|
"AWAITING_CLEARANCE_DOCUMENTS",
|
|
"CLEARANCE_UNDER_REVIEW",
|
|
];
|
|
|
|
export function contractNeedsClearanceAction(c: Freight.IContract): {
|
|
show: boolean;
|
|
urgent: boolean;
|
|
} {
|
|
const clearance = (c as { clearanceStatus?: string }).clearanceStatus;
|
|
const ready =
|
|
clearance === "CLEARANCE_READY_FOR_BOOKING" ||
|
|
clearance === "SELF_CLEARED" ||
|
|
clearance === "ACTIVE_SHIPMENT_IN_PROGRESS";
|
|
if (ready) return { show: false, urgent: false };
|
|
|
|
const awaiting =
|
|
c.status === "AWAITING_CLEARANCE_DOCUMENTS" ||
|
|
clearance === "AWAITING_DOCUMENTS";
|
|
const inProgress =
|
|
CLEARANCE_IN_PROGRESS_STATUSES.includes(c.status) ||
|
|
clearance === "AWAITING_DOCUMENTS" ||
|
|
clearance === "DOCUMENTS_UNDER_REVIEW";
|
|
|
|
return { show: inProgress, urgent: awaiting };
|
|
}
|
|
|
|
export type ContractCustomerAction =
|
|
| {
|
|
type: "sign";
|
|
label: string;
|
|
to: string;
|
|
primary: boolean;
|
|
icon: LucideIcon;
|
|
}
|
|
| {
|
|
type: "navigate";
|
|
label: string;
|
|
to: string;
|
|
primary: boolean;
|
|
icon: LucideIcon;
|
|
}
|
|
| {
|
|
type: "clearance";
|
|
contractId: string;
|
|
label: string;
|
|
primary: boolean;
|
|
icon: LucideIcon;
|
|
urgent: boolean;
|
|
/** True when it's GL's turn — render calm/informational, not a call to action. */
|
|
waiting?: boolean;
|
|
}
|
|
| {
|
|
type: "pay";
|
|
booking: Freight.IBooking;
|
|
label: string;
|
|
primary: boolean;
|
|
icon: LucideIcon;
|
|
}
|
|
| {
|
|
/** One-click bare booking instance (GENERAL non-customs) — mutation, not navigation. */
|
|
type: "initiate";
|
|
contract: Freight.IContract;
|
|
label: string;
|
|
primary: boolean;
|
|
icon: LucideIcon;
|
|
};
|
|
|
|
function findPayableBookingForContract(
|
|
contractId: string,
|
|
bookings: Freight.IBooking[],
|
|
): Freight.IBooking | null {
|
|
return (
|
|
bookings.find((b) => {
|
|
if (b.contractId !== contractId) return false;
|
|
if (b.paymentStatus === "PAID") return false;
|
|
const isGeneral = b.bookingType === "GENERAL_CONTRACT";
|
|
return isGeneral
|
|
? b.status === "FULLY_EXECUTED"
|
|
: b.status === "SELECTED_FOR_BATCH";
|
|
}) ?? null
|
|
);
|
|
}
|
|
|
|
/** Single best customer action for a contract row (list / home). */
|
|
export function deriveContractCustomerAction(
|
|
contract: Freight.IContract,
|
|
bookings: Freight.IBooking[],
|
|
): ContractCustomerAction {
|
|
const id = contract.id;
|
|
|
|
if (contract.status === "CONTRACT_READY") {
|
|
return {
|
|
type: "sign",
|
|
label: "View & sign",
|
|
to: `/contracts/${id}/view`,
|
|
primary: true,
|
|
icon: FileSignature,
|
|
};
|
|
}
|
|
|
|
if (contract.status === "CHANGES_REQUESTED") {
|
|
return {
|
|
type: "navigate",
|
|
label: "Edit & resubmit",
|
|
to: `/contracts/${id}`,
|
|
primary: true,
|
|
icon: PencilLine,
|
|
};
|
|
}
|
|
|
|
// Saved-but-not-submitted contract — send the customer back into the wizard to
|
|
// finish editing and submit it for review.
|
|
if (contract.status === "DRAFT" || contract.status === "RENEWAL_DRAFT") {
|
|
return {
|
|
type: "navigate",
|
|
label: "Continue draft",
|
|
to: `/contracts/${id}/edit`,
|
|
primary: true,
|
|
icon: PencilLine,
|
|
};
|
|
}
|
|
|
|
const payable = findPayableBookingForContract(id, bookings);
|
|
if (payable) {
|
|
return {
|
|
type: "pay",
|
|
booking: payable,
|
|
label: "Pay now",
|
|
primary: true,
|
|
icon: CreditCard,
|
|
};
|
|
}
|
|
|
|
const clr = contractNeedsClearanceAction(contract);
|
|
if (clr.show) {
|
|
// Refine the generic clearance action by the persisted clearance phase so
|
|
// the button says what the customer actually has to do right now (e.g.
|
|
// "Pay duty & upload slip" during CUSTOMER_DUTY, not "Update clearance").
|
|
const phase = contract.clearancePhase ?? null;
|
|
switch (phase) {
|
|
case "CUSTOMER_INTAKE":
|
|
return {
|
|
type: "clearance",
|
|
contractId: id,
|
|
label: "Upload clearance documents",
|
|
primary: true,
|
|
icon: Upload,
|
|
urgent: true,
|
|
};
|
|
case "CUSTOMER_DUTY":
|
|
return {
|
|
type: "clearance",
|
|
contractId: id,
|
|
label: "Pay duty & upload slip",
|
|
primary: true,
|
|
icon: Receipt,
|
|
urgent: true,
|
|
};
|
|
case "GL_ET_REVIEW":
|
|
case "GL_DJ_COLLECTION":
|
|
case "GL_ET_OUTPUT":
|
|
case "GL_ET_POST_CLEARANCE":
|
|
case "GL_DJ_LOADING":
|
|
case "POST_TRANSIT":
|
|
// GL's turn — nothing for the customer to do; show a calm status.
|
|
return {
|
|
type: "clearance",
|
|
contractId: id,
|
|
label: "Clearance in progress",
|
|
primary: false,
|
|
icon: Hourglass,
|
|
urgent: false,
|
|
waiting: true,
|
|
};
|
|
default:
|
|
// No persisted phase (legacy / early cycles) — keep the status-derived label.
|
|
return {
|
|
type: "clearance",
|
|
contractId: id,
|
|
label: clr.urgent ? "Upload clearance" : "Update clearance",
|
|
primary: true,
|
|
icon: Upload,
|
|
urgent: clr.urgent,
|
|
};
|
|
}
|
|
}
|
|
|
|
if (
|
|
contract.customsClearingEnabled &&
|
|
["CLEARANCE_READY_FOR_BOOKING", "ACTIVE_SHIPMENT_IN_PROGRESS"].includes(
|
|
contract.status,
|
|
)
|
|
) {
|
|
return {
|
|
type: "navigate",
|
|
label: "View",
|
|
to: `/contracts/${id}`,
|
|
primary: false,
|
|
icon: Eye,
|
|
};
|
|
}
|
|
|
|
const bookingAction = getContractBookingAction(contract, bookings);
|
|
if (bookingAction.kind === "initiate") {
|
|
return {
|
|
type: "initiate",
|
|
contract,
|
|
label: "Initiate booking",
|
|
primary: true,
|
|
icon: PackagePlus,
|
|
};
|
|
}
|
|
if (bookingAction.kind === "book") {
|
|
return {
|
|
type: "navigate",
|
|
label: "Book shipment",
|
|
to: bookingAction.to,
|
|
primary: true,
|
|
icon: PackagePlus,
|
|
};
|
|
}
|
|
if (bookingAction.kind === "rebook") {
|
|
return {
|
|
type: "navigate",
|
|
label: "Re-book shipment",
|
|
to: bookingAction.to,
|
|
primary: true,
|
|
icon: RotateCcw,
|
|
};
|
|
}
|
|
if (bookingAction.kind === "request") {
|
|
return {
|
|
type: "navigate",
|
|
label: "Request shipment",
|
|
to: bookingAction.to,
|
|
primary: true,
|
|
icon: PackagePlus,
|
|
};
|
|
}
|
|
|
|
return {
|
|
type: "navigate",
|
|
label: "View",
|
|
to: `/contracts/${id}`,
|
|
primary: false,
|
|
icon: Eye,
|
|
};
|
|
}
|