mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 08:48:11 +00:00
contrat,booking,global logestic
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import type { Freight } from "@edr/types";
|
||||
|
||||
export interface ApprovalProgressSummary {
|
||||
label: string;
|
||||
detail: string;
|
||||
complete: boolean;
|
||||
}
|
||||
|
||||
function nextPending(
|
||||
steps: Freight.IContractApprovalStep[],
|
||||
): Freight.IContractApprovalStep | undefined {
|
||||
return steps.find((s) => s.status === "PENDING");
|
||||
}
|
||||
|
||||
/** Compact approval-chain summary for contract list rows (mirrors bookings). */
|
||||
export function formatContractApprovalProgress(
|
||||
status: string,
|
||||
steps?: Freight.IContractApprovalStep[] | null,
|
||||
): ApprovalProgressSummary {
|
||||
const sorted = [...(steps ?? [])].sort((a, b) => a.stepOrder - b.stepOrder);
|
||||
|
||||
if (sorted.length === 0) {
|
||||
if (status === "SUBMITTED") {
|
||||
return {
|
||||
label: "Awaiting accept",
|
||||
detail: "Staff must accept intake",
|
||||
complete: false,
|
||||
};
|
||||
}
|
||||
if (
|
||||
status === "PENDING_APPROVAL" ||
|
||||
status === "APPROVED_PENDING_SIGNATURE"
|
||||
) {
|
||||
return {
|
||||
label: "No steps",
|
||||
detail: "Approval chain not started",
|
||||
complete: false,
|
||||
};
|
||||
}
|
||||
if (
|
||||
[
|
||||
"APPROVED",
|
||||
"CONTRACT_READY",
|
||||
"SIGNED_CUSTOMER",
|
||||
"FULLY_EXECUTED",
|
||||
"CONTRACT_ACTIVE",
|
||||
"CLEARANCE_READY_FOR_BOOKING",
|
||||
"ACTIVE_SHIPMENT_IN_PROGRESS",
|
||||
"CONTRACT_CLOSED",
|
||||
].includes(status)
|
||||
) {
|
||||
return {
|
||||
label: "Approved",
|
||||
detail: "Internal approval complete",
|
||||
complete: true,
|
||||
};
|
||||
}
|
||||
return { label: "—", detail: "", complete: false };
|
||||
}
|
||||
|
||||
const approved = sorted.filter((s) => s.status === "APPROVED").length;
|
||||
const total = sorted.length;
|
||||
const next = nextPending(sorted);
|
||||
|
||||
if (!next && approved === total) {
|
||||
return {
|
||||
label: `${approved}/${total} done`,
|
||||
detail: sorted.map((s) => `${s.requiredRole} ✓`).join(" · "),
|
||||
complete: true,
|
||||
};
|
||||
}
|
||||
|
||||
if (next) {
|
||||
return {
|
||||
label: `${approved}/${total}`,
|
||||
detail: `Next: ${next.requiredRole} (step ${next.stepOrder})`,
|
||||
complete: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
label: `${approved}/${total}`,
|
||||
detail: sorted.map((s) => `${s.requiredRole}: ${s.status}`).join(" · "),
|
||||
complete: approved === total,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
import type { ContractStatus } from "@edr/types";
|
||||
|
||||
export interface StatusStyle {
|
||||
label: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
/** Tailwind chip styling per contract status (mirrors booking-status.config). */
|
||||
export const CONTRACT_STATUS_STYLES: Record<string, StatusStyle> = {
|
||||
DRAFT: {
|
||||
label: "Draft",
|
||||
color: "bg-slate-100 text-slate-700 border-slate-300",
|
||||
},
|
||||
SUBMITTED: {
|
||||
label: "Submitted",
|
||||
color: "bg-amber-50 text-amber-700 border-amber-200",
|
||||
},
|
||||
PRICE_CHANGED_PENDING_CONFIRM: {
|
||||
label: "Price Confirm",
|
||||
color: "bg-amber-50 text-amber-700 border-amber-200",
|
||||
},
|
||||
CHANGES_REQUESTED: {
|
||||
label: "Changes Requested",
|
||||
color: "bg-orange-50 text-orange-700 border-orange-200",
|
||||
},
|
||||
PENDING_APPROVAL: {
|
||||
label: "Pending Approval",
|
||||
color: "bg-amber-50 text-amber-700 border-amber-200",
|
||||
},
|
||||
APPROVED: {
|
||||
label: "Approved",
|
||||
color: "bg-[color:var(--freight-brand-muted)] text-[color:var(--freight-brand)] border-[color:var(--freight-brand-border)]",
|
||||
},
|
||||
APPROVED_PENDING_SIGNATURE: {
|
||||
label: "Pending Signature",
|
||||
color: "bg-sky-50 text-sky-700 border-sky-200",
|
||||
},
|
||||
CONTRACT_READY: {
|
||||
label: "Contract Ready",
|
||||
color: "bg-indigo-50 text-indigo-700 border-indigo-200",
|
||||
},
|
||||
SIGNED_CUSTOMER: {
|
||||
label: "Customer Signed",
|
||||
color: "bg-sky-50 text-sky-700 border-sky-200",
|
||||
},
|
||||
FULLY_EXECUTED: {
|
||||
label: "Fully Executed",
|
||||
color: "bg-indigo-50 text-indigo-700 border-indigo-200",
|
||||
},
|
||||
CONTRACT_ACTIVE: {
|
||||
label: "Active",
|
||||
color: "bg-[color:var(--freight-brand-muted)] text-[color:var(--freight-brand)] border-[color:var(--freight-brand-border)]",
|
||||
},
|
||||
AWAITING_CLEARANCE_DOCUMENTS: {
|
||||
label: "Awaiting Documents",
|
||||
color: "bg-amber-50 text-amber-700 border-amber-200",
|
||||
},
|
||||
CLEARANCE_UNDER_REVIEW: {
|
||||
label: "Clearance Review",
|
||||
color: "bg-amber-50 text-amber-800 border-amber-200",
|
||||
},
|
||||
CLEARANCE_READY_FOR_BOOKING: {
|
||||
label: "Ready for Booking",
|
||||
color: "bg-[color:var(--freight-brand-muted)] text-[color:var(--freight-brand)] border-[color:var(--freight-brand-border)]",
|
||||
},
|
||||
ACTIVE_SHIPMENT_IN_PROGRESS: {
|
||||
label: "Shipment in Progress",
|
||||
color: "bg-sky-50 text-sky-700 border-sky-200",
|
||||
},
|
||||
CONTRACT_CLOSED: {
|
||||
label: "Closed",
|
||||
color: "bg-slate-100 text-slate-700 border-slate-300",
|
||||
},
|
||||
EXPIRED: {
|
||||
label: "Expired",
|
||||
color: "bg-red-50 text-red-700 border-red-200",
|
||||
},
|
||||
REJECTED: {
|
||||
label: "Rejected",
|
||||
color: "bg-red-50 text-red-700 border-red-200",
|
||||
},
|
||||
CANCELLED: {
|
||||
label: "Cancelled",
|
||||
color: "bg-red-50 text-red-700 border-red-200",
|
||||
},
|
||||
RENEWAL_DRAFT: {
|
||||
label: "Renewal Draft",
|
||||
color: "bg-slate-100 text-slate-700 border-slate-300",
|
||||
},
|
||||
RENEWAL_SUBMITTED: {
|
||||
label: "Renewal Submitted",
|
||||
color: "bg-amber-50 text-amber-700 border-amber-200",
|
||||
},
|
||||
RENEWAL_PENDING_APPROVAL: {
|
||||
label: "Renewal Approval",
|
||||
color: "bg-amber-50 text-amber-700 border-amber-200",
|
||||
},
|
||||
AMENDMENTS_PROPOSED: {
|
||||
label: "Amendments Proposed",
|
||||
color: "bg-orange-50 text-orange-700 border-orange-200",
|
||||
},
|
||||
ARCHIVED: {
|
||||
label: "Archived",
|
||||
color: "bg-slate-100 text-slate-700 border-slate-300",
|
||||
},
|
||||
};
|
||||
|
||||
/** Mantine palette colour per contract status (mirrors BookingStatusBadge map). */
|
||||
export const CONTRACT_STATUS_COLOR: Record<string, string> = {
|
||||
DRAFT: "gray",
|
||||
SUBMITTED: "yellow",
|
||||
PRICE_CHANGED_PENDING_CONFIRM: "yellow",
|
||||
CHANGES_REQUESTED: "orange",
|
||||
PENDING_APPROVAL: "yellow",
|
||||
APPROVED: "edr-green",
|
||||
APPROVED_PENDING_SIGNATURE: "cyan",
|
||||
CONTRACT_READY: "indigo",
|
||||
SIGNED_CUSTOMER: "cyan",
|
||||
FULLY_EXECUTED: "indigo",
|
||||
CONTRACT_ACTIVE: "edr-green",
|
||||
AWAITING_CLEARANCE_DOCUMENTS: "yellow",
|
||||
CLEARANCE_UNDER_REVIEW: "yellow",
|
||||
CLEARANCE_READY_FOR_BOOKING: "edr-green",
|
||||
ACTIVE_SHIPMENT_IN_PROGRESS: "cyan",
|
||||
CONTRACT_CLOSED: "gray",
|
||||
EXPIRED: "red",
|
||||
REJECTED: "red",
|
||||
CANCELLED: "red",
|
||||
RENEWAL_DRAFT: "gray",
|
||||
RENEWAL_SUBMITTED: "yellow",
|
||||
RENEWAL_PENDING_APPROVAL: "yellow",
|
||||
AMENDMENTS_PROPOSED: "orange",
|
||||
ARCHIVED: "gray",
|
||||
};
|
||||
|
||||
export interface StatusMeta {
|
||||
title: string;
|
||||
description: string;
|
||||
color: string;
|
||||
stage: number;
|
||||
}
|
||||
|
||||
export const CONTRACT_STATUS_META: Record<string, StatusMeta> = {
|
||||
DRAFT: {
|
||||
title: "Draft",
|
||||
description: "Contract is being prepared by the customer.",
|
||||
color: "text-slate-500",
|
||||
stage: 0,
|
||||
},
|
||||
SUBMITTED: {
|
||||
title: "Submitted",
|
||||
description: "Awaiting staff review.",
|
||||
color: "text-amber-600",
|
||||
stage: 0,
|
||||
},
|
||||
PRICE_CHANGED_PENDING_CONFIRM: {
|
||||
title: "Price Confirm",
|
||||
description: "Awaiting customer confirmation of revised unit rates.",
|
||||
color: "text-amber-600",
|
||||
stage: 0,
|
||||
},
|
||||
CHANGES_REQUESTED: {
|
||||
title: "Changes Requested",
|
||||
description: "Returned to customer for updates.",
|
||||
color: "text-orange-600",
|
||||
stage: 0,
|
||||
},
|
||||
PENDING_APPROVAL: {
|
||||
title: "Pending Approval",
|
||||
description: "Moving through the internal approval chain.",
|
||||
color: "text-amber-600",
|
||||
stage: 1,
|
||||
},
|
||||
APPROVED: {
|
||||
title: "Approved",
|
||||
description: "Approved; contract document can be generated.",
|
||||
color: "text-[color:var(--freight-brand)]",
|
||||
stage: 1,
|
||||
},
|
||||
APPROVED_PENDING_SIGNATURE: {
|
||||
title: "Pending Signature",
|
||||
description: "Awaiting director or CEO signature steps.",
|
||||
color: "text-sky-600",
|
||||
stage: 1,
|
||||
},
|
||||
CONTRACT_READY: {
|
||||
title: "Contract Ready",
|
||||
description: "Contract generated; awaiting customer signature.",
|
||||
color: "text-indigo-600",
|
||||
stage: 2,
|
||||
},
|
||||
SIGNED_CUSTOMER: {
|
||||
title: "Customer Signed",
|
||||
description: "Awaiting contract execution.",
|
||||
color: "text-sky-600",
|
||||
stage: 2,
|
||||
},
|
||||
FULLY_EXECUTED: {
|
||||
title: "Fully Executed",
|
||||
description: "One-time contract executed; transport-only path.",
|
||||
color: "text-indigo-600",
|
||||
stage: 3,
|
||||
},
|
||||
CONTRACT_ACTIVE: {
|
||||
title: "Active",
|
||||
description: "General contract active over its validity window.",
|
||||
color: "text-[color:var(--freight-brand)]",
|
||||
stage: 3,
|
||||
},
|
||||
AWAITING_CLEARANCE_DOCUMENTS: {
|
||||
title: "Awaiting Documents",
|
||||
description: "Customer is uploading pre-booking clearance documents.",
|
||||
color: "text-amber-600",
|
||||
stage: 3,
|
||||
},
|
||||
CLEARANCE_UNDER_REVIEW: {
|
||||
title: "Clearance Review",
|
||||
description: "Global Logistics ET is reviewing clearance documents.",
|
||||
color: "text-amber-700",
|
||||
stage: 3,
|
||||
},
|
||||
CLEARANCE_READY_FOR_BOOKING: {
|
||||
title: "Ready for Booking",
|
||||
description: "Clearance complete — GL can create the shipment booking.",
|
||||
color: "text-[color:var(--freight-brand)]",
|
||||
stage: 4,
|
||||
},
|
||||
ACTIVE_SHIPMENT_IN_PROGRESS: {
|
||||
title: "Shipment in Progress",
|
||||
description: "A shipment booking is active under this contract.",
|
||||
color: "text-sky-600",
|
||||
stage: 4,
|
||||
},
|
||||
CONTRACT_CLOSED: {
|
||||
title: "Closed",
|
||||
description: "Contract fulfilled and closed.",
|
||||
color: "text-slate-500",
|
||||
stage: 5,
|
||||
},
|
||||
EXPIRED: {
|
||||
title: "Expired",
|
||||
description: "Validity window elapsed.",
|
||||
color: "text-red-600",
|
||||
stage: -1,
|
||||
},
|
||||
REJECTED: {
|
||||
title: "Rejected",
|
||||
description: "Contract was rejected.",
|
||||
color: "text-red-600",
|
||||
stage: -1,
|
||||
},
|
||||
CANCELLED: {
|
||||
title: "Cancelled",
|
||||
description: "Contract was cancelled.",
|
||||
color: "text-red-600",
|
||||
stage: -1,
|
||||
},
|
||||
};
|
||||
|
||||
export const CONTRACT_LIST_TABS = [
|
||||
{ key: "all", label: "All contracts", statuses: null as string[] | null },
|
||||
{
|
||||
key: "intake",
|
||||
label: "Submitted",
|
||||
statuses: ["SUBMITTED", "PRICE_CHANGED_PENDING_CONFIRM", "CHANGES_REQUESTED"],
|
||||
},
|
||||
{
|
||||
key: "in_approval",
|
||||
label: "In approval",
|
||||
statuses: ["PENDING_APPROVAL", "APPROVED", "APPROVED_PENDING_SIGNATURE"],
|
||||
},
|
||||
{
|
||||
key: "approved_contract",
|
||||
label: "Contract & signature",
|
||||
statuses: ["CONTRACT_READY", "SIGNED_CUSTOMER"],
|
||||
},
|
||||
{
|
||||
key: "clearance",
|
||||
label: "Clearance",
|
||||
statuses: [
|
||||
"AWAITING_CLEARANCE_DOCUMENTS",
|
||||
"CLEARANCE_UNDER_REVIEW",
|
||||
"CLEARANCE_READY_FOR_BOOKING",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "active",
|
||||
label: "Active",
|
||||
statuses: [
|
||||
"FULLY_EXECUTED",
|
||||
"CONTRACT_ACTIVE",
|
||||
"ACTIVE_SHIPMENT_IN_PROGRESS",
|
||||
],
|
||||
},
|
||||
{
|
||||
key: "closed",
|
||||
label: "Closed",
|
||||
statuses: ["CONTRACT_CLOSED", "EXPIRED", "REJECTED", "CANCELLED"],
|
||||
},
|
||||
] as const;
|
||||
|
||||
export type ContractStatusTabKey = (typeof CONTRACT_LIST_TABS)[number]["key"];
|
||||
|
||||
export const CONTRACT_WORKFLOW_STAGES = [
|
||||
{
|
||||
label: "Submission",
|
||||
statuses: [
|
||||
"DRAFT",
|
||||
"SUBMITTED",
|
||||
"PRICE_CHANGED_PENDING_CONFIRM",
|
||||
"CHANGES_REQUESTED",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Approval",
|
||||
statuses: ["PENDING_APPROVAL", "APPROVED", "APPROVED_PENDING_SIGNATURE"],
|
||||
},
|
||||
{
|
||||
label: "Signature",
|
||||
statuses: ["CONTRACT_READY", "SIGNED_CUSTOMER"],
|
||||
},
|
||||
{
|
||||
label: "Clearance",
|
||||
statuses: [
|
||||
"FULLY_EXECUTED",
|
||||
"CONTRACT_ACTIVE",
|
||||
"AWAITING_CLEARANCE_DOCUMENTS",
|
||||
"CLEARANCE_UNDER_REVIEW",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Shipment",
|
||||
statuses: ["CLEARANCE_READY_FOR_BOOKING", "ACTIVE_SHIPMENT_IN_PROGRESS"],
|
||||
},
|
||||
{ label: "Done", statuses: ["CONTRACT_CLOSED"] },
|
||||
] as const;
|
||||
|
||||
export function getContractStatusMeta(status: ContractStatus | string): StatusMeta {
|
||||
return (
|
||||
CONTRACT_STATUS_META[status] ?? {
|
||||
title: status,
|
||||
description: "",
|
||||
color: "text-muted-foreground",
|
||||
stage: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export function getContractWorkflowStageIndex(
|
||||
status: ContractStatus | string,
|
||||
): number {
|
||||
const meta = getContractStatusMeta(status);
|
||||
if (meta.stage < 0) return -1;
|
||||
return meta.stage;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { Freight } from "@edr/types";
|
||||
|
||||
export interface ContractListRow {
|
||||
id: string;
|
||||
reference: string;
|
||||
approvalSteps?: Freight.IContractApprovalStep[];
|
||||
customerLabel: string;
|
||||
status: string;
|
||||
contractKind: Freight.ContractKind;
|
||||
tradeDirection: string;
|
||||
freightType: string;
|
||||
originLabel: string;
|
||||
destinationLabel: string;
|
||||
validFrom?: string | null;
|
||||
validUntil?: string | null;
|
||||
validityDays?: number | null;
|
||||
isRenewal: boolean;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
function yardLabel(
|
||||
yard?: { label?: string; code?: string; name?: string } | null,
|
||||
fallback = "—",
|
||||
): string {
|
||||
if (!yard) return fallback;
|
||||
return yard.label ?? yard.name ?? yard.code ?? fallback;
|
||||
}
|
||||
|
||||
export function toContractListRow(contract: Freight.IContract): ContractListRow {
|
||||
const routes = [...(contract.routes ?? [])].sort(
|
||||
(a, b) => a.sortOrder - b.sortOrder,
|
||||
);
|
||||
const first = routes[0];
|
||||
const last = routes[routes.length - 1] ?? first;
|
||||
|
||||
return {
|
||||
id: contract.id,
|
||||
reference: contract.reference,
|
||||
approvalSteps: contract.approvalSteps,
|
||||
customerLabel: contract.isGovernment
|
||||
? (contract.governmentInstitution ?? "Government")
|
||||
: (contract.companyId ?? "—"),
|
||||
status: contract.status,
|
||||
contractKind: contract.contractKind,
|
||||
tradeDirection: contract.tradeDirection,
|
||||
freightType: contract.freightType,
|
||||
originLabel: yardLabel(first?.originYard),
|
||||
destinationLabel: yardLabel(last?.destinationYard),
|
||||
validFrom: contract.contractValidFrom,
|
||||
validUntil: contract.contractValidUntil,
|
||||
validityDays: contract.contractValidityDays,
|
||||
isRenewal: Boolean(contract.renewalOfId),
|
||||
createdAt: contract.createdAt,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user