@@ -40,12 +38,6 @@ export function Step1ContractType({
onClick={() => {
field.onChange("new");
form.clearErrors(["contractType", "previousContractRef"]);
- if (!draftContractId) {
- form.setValue("draftContractId", genContractId(), {
- shouldDirty: true,
- shouldValidate: true,
- });
- }
}}
>
@@ -55,11 +47,6 @@ export function Step1ContractType({
Blank contract form. A draft ID is auto-generated.
- {field.value === "new" && draftContractId && (
-
- {draftContractId}
-
- )}
-
)}
/>
diff --git a/apps/edr-freight-web/portal/src/pages/portal/MyPortalPage.tsx b/apps/edr-freight-web/portal/src/pages/portal/MyPortalPage.tsx
index 231ff068d..d8cced8ba 100644
--- a/apps/edr-freight-web/portal/src/pages/portal/MyPortalPage.tsx
+++ b/apps/edr-freight-web/portal/src/pages/portal/MyPortalPage.tsx
@@ -1,13 +1,6 @@
-import {
- useEffect,
- useMemo,
- useState,
-} from "react";
+import { useEffect, useMemo, useState } from "react";
-import {
- Link,
- useNavigate,
-} from "react-router-dom";
+import { Link, useNavigate } from "react-router-dom";
import {
Building2,
@@ -31,19 +24,10 @@ import { formatCurrency } from "@/pages/billing/invoices.mock";
import { customersService } from "@/services/customers.service";
-import { getMyInfo } from "@/services/account";
+import { authService } from "@/services/auth.service";
import NewCustomerPage from "../customers/NewCustomerPage";
import { Button } from "@edr/ui-common";
-type Customer = {
- id: string;
- companyName: string;
- firstName: string;
- lastName: string;
- email: string;
- phone: string;
-};
-
export default function MyPortalPage() {
const navigate = useNavigate();
@@ -58,58 +42,10 @@ export default function MyPortalPage() {
// MOCK DATA
// ------------------------------------------------------------
- const me = useMemo(() => getCurrentCustomer(), []);
const myBookings = useMemo(() => getMyBookings(), []);
const myShipments = useMemo(() => getMyShipments(), []);
const myInvoices = useMemo(() => getMyInvoices(), []);
- // ------------------------------------------------------------
- // FETCH CUSTOMER
- // ------------------------------------------------------------
-
- useEffect(() => {
- const initialize = async () => {
- try {
- setLoading(true);
-
- const userRes = await getMyInfo();
- const userId = userRes?.data?.id;
- localStorage.setItem("currentUser", JSON.stringify(userRes.data));
- // if (!userId) {
- // navigate("/login");
- // return;
- // }
-
- const res = await customersService.getByUserId(userId);
- if (res) {
- setCustomer(res);
- return;
- }
-
- // customer not found → onboarding
- // navigate("/customers/register");
- } catch (error: any) {
- console.error("Customer fetch failed:", error);
-
- const status = error?.response?.status;
-
- if (status === 404) {
- // navigate("/customers/register");
- return;
- }
-
- if (status === 401) {
- // navigate("/login");
- return;
- }
- } finally {
- setLoading(false);
- }
- };
-
- initialize();
- }, [navigate]);
-
// ------------------------------------------------------------
// LOADING
// ------------------------------------------------------------
@@ -118,9 +54,7 @@ export default function MyPortalPage() {
return (
-
- Loading portal...
-
+
Loading portal...
);
@@ -134,9 +68,7 @@ export default function MyPortalPage() {
return (
-
- No customer profile found
-
+
No customer profile found
{/*
*/}
-
@@ -164,31 +93,24 @@ export default function MyPortalPage() {
// ------------------------------------------------------------
const activeBookings = myBookings.filter(
- (b) =>
- b.status === "Confirmed" ||
- b.status === "In Transit"
+ (b) => b.status === "Confirmed" || b.status === "In Transit",
);
- const activeShipments = myShipments.filter(
- (s) => s.status === "In Transit"
- );
+ const activeShipments = myShipments.filter((s) => s.status === "In Transit");
const outstandingInvoices = myInvoices.filter(
- (i) => i.status === "Sent" || i.status === "Overdue"
+ (i) => i.status === "Sent" || i.status === "Overdue",
);
const totalOutstanding = outstandingInvoices.reduce(
- (sum, i) =>
- i.currency === "USD" ? sum + i.amount : sum,
- 0
+ (sum, i) => (i.currency === "USD" ? sum + i.amount : sum),
+ 0,
);
const totalSpent = myInvoices.reduce(
(sum, i) =>
- i.status === "Paid" && i.currency === "USD"
- ? sum + i.amount
- : sum,
- 0
+ i.status === "Paid" && i.currency === "USD" ? sum + i.amount : sum,
+ 0,
);
// ------------------------------------------------------------
@@ -198,13 +120,11 @@ export default function MyPortalPage() {
return (
-
{/* HERO */}
-
{customer.companyName?.charAt(0)}
@@ -244,7 +164,6 @@ export default function MyPortalPage() {
{/* KPI */}
-
-
- {label}
-
+
{label}
-
- {value}
-
+
{value}
-
- {sub}
-
+
{sub}
+
{content}
);
}
- return (
-
- {content}
-
- );
-}
\ No newline at end of file
+ return
{content}
;
+}
diff --git a/apps/edr-freight-web/portal/src/services/account.ts b/apps/edr-freight-web/portal/src/services/account.ts
deleted file mode 100644
index 6e97e2dd0..000000000
--- a/apps/edr-freight-web/portal/src/services/account.ts
+++ /dev/null
@@ -1,92 +0,0 @@
-import { URL_CONSTANTS } from "@/constants/URLS";
-import { CreateUserPayload } from "@/types/createUser";
-import { VerificationCodePayload } from "@/types/generateVerificationCode";
-import { UserTypeRequest } from "@/types/userTypeRequest";
-import { client } from "@/utils/api";
-import { ApiResponse } from "@edr/types";
-import { GenerateVerifcationCodePayload } from "node_modules/@tria-plc/iamui-common/dist/types/shared/services/authService";
-
-// -----------------------------------------------------------------------------
-// API
-// -----------------------------------------------------------------------------
-
-export const createUser = async (
- body: CreateUserPayload
-) => {
- const res =
- await client.post<
- ApiResponse
- >(
- URL_CONSTANTS.USERS.SIGN_UP,
- body
- );
-
- return res.data;
-};
-
-export const getMyInfo = async () => {
- const res =
- await client.get<
- ApiResponse
- >(
- URL_CONSTANTS.USERS.ME
- );
-
- return res.data;
-};
-
-export const generateVerificationCode = async (
- body: VerificationCodePayload
-) => {
- const res =
- await client.patch<
- ApiResponse
- >(
- URL_CONSTANTS.USERS.GENERATE_VERIFICATION_CODE,
- body
- );
-
- return res.data.data;
-};
-
-export const setPassword = async (
- body: any
-) => {
- const res =
- await client.patch<
- ApiResponse
- >(
- URL_CONSTANTS.USERS.SET_PASSWORD,
- body
- );
-
- return res.data.data;
-};
-
-export const createOTP = async (
- body: any
-) => {
- const res =
- await client.post<
- ApiResponse
- >(
- URL_CONSTANTS.OTP.SEND,
- body
- );
-
- return res.data;
-};
-
-export const verifyOTP = async (
- body: any
-) => {
- const res =
- await client.post<
- ApiResponse
- >(
- URL_CONSTANTS.OTP.VERIFY,
- body
- );
-
- return res.data;
-};
\ No newline at end of file
diff --git a/apps/edr-freight-web/portal/src/services/api.ts b/apps/edr-freight-web/portal/src/services/api.ts
index ba2b227c6..ac1a708da 100644
--- a/apps/edr-freight-web/portal/src/services/api.ts
+++ b/apps/edr-freight-web/portal/src/services/api.ts
@@ -13,6 +13,8 @@ import { consignmentsService } from "./consignments.service";
import { trackingService } from "./tracking.service";
import { fileUploadSettingsService } from "./fileUploadSettings.service";
import { dropdownSettingsService } from "./dropdownSettings.service";
+import { authService } from "./auth.service";
+import { customersService } from "./customers.service";
import {
CreateDropdownOptionDto,
CreateDropdownSettingDto,
@@ -21,12 +23,101 @@ import {
UpdateDropdownOptionDto,
UpdateDropdownSettingDto,
} from "@/types/dropdownSettings";
+import {
+ CreateCustomerDto,
+ Customer,
+ UpdateCustomerDto,
+} from "@/types/customers";
+import type {
+ AuthUser,
+ GenerateVerificationCodePayload,
+ LoginPayload,
+ LoginResponse,
+ OtpPayload,
+ OtpResponse,
+ SetPasswordPayload,
+ SignupPayload,
+ SignupResponse,
+} from "@/types/auth";
// ---------------------------------------------------------------------------
// API definition
// ---------------------------------------------------------------------------
export const api = {
+ auth: {
+ login: endpoint(
+ "auth",
+ "login",
+ authService.login,
+ ),
+ createUser: endpoint(
+ "auth",
+ "createUser",
+ authService.createUser,
+ ),
+ getMyInfo: endpoint(
+ "auth",
+ "getMyInfo",
+ authService.getMyInfo,
+ ),
+ generateVerificationCode: endpoint(
+ "auth",
+ "generateVerificationCode",
+ authService.generateVerificationCode,
+ ),
+ setPassword: endpoint(
+ "auth",
+ "setPassword",
+ authService.setPassword,
+ ),
+ sendOTP: endpoint(
+ "auth",
+ "sendOTP",
+ authService.sendOTP,
+ ),
+ verifyOTP: endpoint(
+ "auth",
+ "verifyOTP",
+ authService.verifyOTP,
+ ),
+ logout: endpoint("auth", "logout", authService.logout),
+ },
+
+ customers: {
+ list: endpoint(
+ "customers",
+ "list",
+ customersService.list,
+ ),
+
+ get: endpoint<{ id: string }, Customer>("customers", "get", ({ id }) =>
+ customersService.getById(id),
+ ),
+
+ create: endpoint(
+ "customers",
+ "create",
+ customersService.create,
+ ),
+
+ update: endpoint<{ id: string; dto: UpdateCustomerDto }, Customer>(
+ "customers",
+ "update",
+ ({ id, dto }) => customersService.update(id, dto),
+ ),
+
+ remove: endpoint<{ id: string }, void>("customers", "remove", ({ id }) =>
+ customersService.remove(id),
+ ),
+
+ getByUserId: endpoint<{ id: string }, Customer | null>(
+ "customers",
+ "getByUserId",
+ ({ id }) => customersService.getByUserId(id),
+ ),
+ },
+
bookings: {
list: endpoint>(
"bookings",
diff --git a/apps/edr-freight-web/portal/src/services/auth.service.ts b/apps/edr-freight-web/portal/src/services/auth.service.ts
new file mode 100644
index 000000000..856d417cc
--- /dev/null
+++ b/apps/edr-freight-web/portal/src/services/auth.service.ts
@@ -0,0 +1,90 @@
+import { URL_CONSTANTS } from "@/constants/URLS";
+import { client } from "@/utils/api";
+import { ApiResponse } from "@edr/types";
+import type {
+ AuthUser,
+ GenerateVerificationCodePayload,
+ LoginPayload,
+ LoginResponse,
+ OtpPayload,
+ OtpResponse,
+ SetPasswordPayload,
+ SignupPayload,
+ SignupResponse,
+} from "@/types/auth";
+
+export const authService = {
+ login: async (body: LoginPayload) => {
+ const res = await client.post>(
+ URL_CONSTANTS.AUTH.LOGIN,
+ body,
+ );
+ return res.data.data;
+ },
+
+ createUser: async (body: SignupPayload) => {
+ const res = await client.post>(
+ URL_CONSTANTS.USERS.SIGN_UP,
+ body,
+ );
+ return res.data.data;
+ },
+
+ getMyInfo: async () => {
+ const res = await client.get>(
+ URL_CONSTANTS.USERS.ME,
+ );
+ return res.data.data;
+ },
+
+ generateVerificationCode: async (body: GenerateVerificationCodePayload) => {
+ const res = await client.patch>(
+ URL_CONSTANTS.USERS.GENERATE_VERIFICATION_CODE,
+ body,
+ );
+ return res.data.data;
+ },
+
+ setPassword: async (body: SetPasswordPayload) => {
+ const res = await client.patch>(
+ URL_CONSTANTS.USERS.SET_PASSWORD,
+ body,
+ );
+ return res.data.data;
+ },
+
+ sendOTP: async (body: OtpPayload) => {
+ const res = await client.post>(
+ URL_CONSTANTS.OTP.SEND,
+ body,
+ );
+ return res.data.data;
+ },
+
+ verifyOTP: async (body: OtpPayload) => {
+ const res = await client.post>(
+ URL_CONSTANTS.OTP.VERIFY,
+ body,
+ );
+ return res.data.data;
+ },
+
+ refreshToken: async () => {
+ const refreshTokenCookie = document.cookie
+ .split("; ")
+ .find((row) => row.startsWith("refresh-token="))
+ ?.split("=")[1];
+ const res = await client.post>(
+ URL_CONSTANTS.AUTH.REFRESH_TOKEN,
+ { refreshToken: refreshTokenCookie },
+ );
+ return res.data.data;
+ },
+
+ logout: async () => {
+ const res = await client.patch>(
+ URL_CONSTANTS.AUTH.LOGOUT,
+ );
+ return res.data.data;
+ },
+};
diff --git a/apps/edr-freight-web/portal/src/services/customers.service.ts b/apps/edr-freight-web/portal/src/services/customers.service.ts
index 3781859fa..3d809709d 100644
--- a/apps/edr-freight-web/portal/src/services/customers.service.ts
+++ b/apps/edr-freight-web/portal/src/services/customers.service.ts
@@ -7,6 +7,7 @@ import type {
Customer,
UpdateCustomerDto,
} from "@/types/customers";
+import { isAxiosError } from "axios";
const BASE = URL_CONSTANTS.CUSTOMERS_API.BASE;
@@ -23,22 +24,26 @@ export const customersService = {
return unwrap(response.data);
},
- getByUserId: async (userId: string): Promise => {
- const response = await client.get>(
- URL_CONSTANTS.CUSTOMERS_API.BY_USER_ID(userId),
- );
+ getByUserId: async (userId: string): Promise => {
+ try {
+ const response = await client.get>(
+ URL_CONSTANTS.CUSTOMERS_API.BY_USER_ID(userId),
+ );
+ return unwrap(response.data);
+ } catch (e) {
+ if (isAxiosError(e) && e.response?.status === 404) {
+ return null;
+ }
+ throw e;
+ }
+ },
+
+ create: async (payload: CreateCustomerDto): Promise => {
+ const response = await client.post>(BASE, payload);
return unwrap(response.data);
},
- create: async (payload: any): Promise => {
- const response = await client.post>(BASE, payload);
- return unwrap(response.data);
- },
-
- update: async (
- id: string,
- payload: UpdateCustomerDto,
- ): Promise => {
+ update: async (id: string, payload: UpdateCustomerDto): Promise => {
const response = await client.patch>(
URL_CONSTANTS.CUSTOMERS_API.BY_ID(id),
payload,
diff --git a/apps/edr-freight-web/portal/src/types/auth.ts b/apps/edr-freight-web/portal/src/types/auth.ts
new file mode 100644
index 000000000..08318417d
--- /dev/null
+++ b/apps/edr-freight-web/portal/src/types/auth.ts
@@ -0,0 +1,65 @@
+export interface AuthUser {
+ id: string;
+ name: { am: string; en: string };
+ email: string;
+ roles: string[];
+ status: string;
+ employee: any[];
+ userType: string;
+ username: string;
+ permissions: string[];
+ phoneNumber: string;
+ sharepointId: string | null;
+ hasSetPassword: boolean;
+ hasFinishedRegistration: boolean;
+ hasFinishedDMSOnboarding: boolean;
+}
+
+export interface SignupPayload {
+ email: string;
+ username: string;
+ phoneNumber: string;
+ userType: string;
+ name: { en: string; am: string };
+}
+
+export interface SignupResponse {
+ token: string;
+ refreshToken: string;
+ otp: string;
+ userId: string;
+}
+
+export interface OtpPayload {
+ phone: string;
+ otp: string;
+}
+
+export interface OtpResponse {
+ success: boolean;
+ message: string;
+}
+
+export interface SetPasswordPayload {
+ newPassword: string;
+ confirmPassword: string;
+ userId: string;
+ email: string;
+ verificationCode: string;
+}
+
+export interface GenerateVerificationCodePayload {
+ email: string;
+ phoneNumber: string;
+ type: string;
+}
+
+export interface LoginPayload {
+ email: string;
+ password: string;
+}
+
+export interface LoginResponse {
+ token: string;
+ refreshToken: string;
+}
diff --git a/apps/edr-freight-web/portal/src/types/createUser.ts b/apps/edr-freight-web/portal/src/types/createUser.ts
deleted file mode 100644
index 53bd6c8e9..000000000
--- a/apps/edr-freight-web/portal/src/types/createUser.ts
+++ /dev/null
@@ -1,10 +0,0 @@
-export type CreateUserPayload = {
- email: string;
- username: string;
- phoneNumber: string;
- userType: string;
- name: {
- en: string;
- am?: string;
- };
-};
\ No newline at end of file
diff --git a/apps/edr-freight-web/portal/src/types/generateVerificationCode.ts b/apps/edr-freight-web/portal/src/types/generateVerificationCode.ts
deleted file mode 100644
index 24f0e206d..000000000
--- a/apps/edr-freight-web/portal/src/types/generateVerificationCode.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export type VerificationCodePayload = {
- email: string;
- phoneNumber: string;
- type: string;
-};
\ No newline at end of file
diff --git a/apps/edr-freight-web/portal/src/utils/api.ts b/apps/edr-freight-web/portal/src/utils/api.ts
index 5fe426a0c..d152e6aad 100644
--- a/apps/edr-freight-web/portal/src/utils/api.ts
+++ b/apps/edr-freight-web/portal/src/utils/api.ts
@@ -2,38 +2,129 @@ import {
UseQueryOptions,
QueryObserverOptions,
} from "@tanstack/react-query";
-import axios from "axios";
+import axios, { AxiosError, InternalAxiosRequestConfig } from "axios";
+import { URL_CONSTANTS } from "@/constants/URLS";
-// ---------------------------------------------------------------------------
-// Axios client
-// ---------------------------------------------------------------------------
-
-export const client = axios.create({
+const client = axios.create({
baseURL: import.meta.env.VITE_API_URL,
});
+function getCookie(name: string): string | undefined {
+ return document.cookie
+ .split("; ")
+ .find((row) => row.startsWith(`${name}=`))
+ ?.split("=")[1];
+}
+
+function setCookie(name: string, value: string, days: number) {
+ const expires = new Date();
+ expires.setDate(expires.getDate() + days);
+ document.cookie = `${name}=${value}; path=/; expires=${expires.toUTCString()}; SameSite=Lax`;
+}
+
+function clearAuthCookies() {
+ ["auth-token", "refresh-token", "auth-user", "current-position-id", "selected-position-id"].forEach(
+ (name) => {
+ document.cookie = `${name}=; Max-Age=0; path=/`;
+ },
+ );
+}
+
// Attach auth token to every request
client.interceptors.request.use((config) => {
- // TODO: replace with secure storage (cookie/localStorage/auth provider)
- const token = document.cookie
- .split("; ")
- .find((row) => row.startsWith("auth-token="))
- ?.split("=")[1];
-
+ const token = getCookie("auth-token");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
-
return config;
});
-// Handle auth errors globally
+// Token refresh state
+let isRefreshing = false;
+let failedQueue: {
+ resolve: (token: string) => void;
+ reject: (error: unknown) => void;
+}[] = [];
+
+function processQueue(error: unknown, token?: string) {
+ failedQueue.forEach(({ resolve, reject }) => {
+ if (error) {
+ reject(error);
+ } else {
+ resolve(token!);
+ }
+ });
+ failedQueue = [];
+}
+
+// Handle auth errors globally with token refresh
client.interceptors.response.use(
(response) => response,
- (error) => {
- if (error.response?.status === 401) {
- window.location.href = "/auth";
+ async (error: AxiosError) => {
+ const originalRequest = error.config as InternalAxiosRequestConfig & {
+ _retry?: boolean;
+ };
+
+ // Don't intercept if:
+ // - no response (network error)
+ // - status is not 401
+ // - already retried
+ // - it's the refresh endpoint itself
+ if (
+ !error.response ||
+ error.response.status !== 401 ||
+ originalRequest._retry ||
+ originalRequest.url === URL_CONSTANTS.AUTH.REFRESH_TOKEN
+ ) {
+ return Promise.reject(error);
}
- return Promise.reject(error);
- }
+
+ if (isRefreshing) {
+ return new Promise((resolve, reject) => {
+ failedQueue.push({ resolve, reject });
+ }).then((token) => {
+ originalRequest.headers.Authorization = `Bearer ${token}`;
+ return client(originalRequest);
+ });
+ }
+
+ originalRequest._retry = true;
+ isRefreshing = true;
+
+ const refreshToken = getCookie("refresh-token");
+
+ if (!refreshToken) {
+ isRefreshing = false;
+ clearAuthCookies();
+ if (window.location.pathname !== "/login") {
+ window.location.href = "/login";
+ }
+ return Promise.reject(error);
+ }
+
+ try {
+ const { data } = await client.post<{ data: { token: string; refreshToken: string } }>(
+ URL_CONSTANTS.AUTH.REFRESH_TOKEN,
+ { refreshToken },
+ );
+ const { token, refreshToken: newRefreshToken } = data.data;
+ setCookie("auth-token", token, 7);
+ setCookie("refresh-token", newRefreshToken, 7);
+ originalRequest.headers.Authorization = `Bearer ${token}`;
+ processQueue(null, token);
+ return client(originalRequest);
+ } catch (refreshError) {
+ processQueue(refreshError, undefined);
+ clearAuthCookies();
+ if (window.location.pathname !== "/login") {
+ window.location.href = "/login";
+ }
+ return Promise.reject(refreshError);
+ } finally {
+ isRefreshing = false;
+ }
+ },
);
+
+export { client };
+export type { UseQueryOptions, QueryObserverOptions };
diff --git a/apps/edr-freight-web/portal/src/utils/result.ts b/apps/edr-freight-web/portal/src/utils/result.ts
new file mode 100644
index 000000000..3e9627445
--- /dev/null
+++ b/apps/edr-freight-web/portal/src/utils/result.ts
@@ -0,0 +1,29 @@
+export type Result =
+ | { success: true; data: T }
+ | { success: false; error: E };
+
+export type ApiError = {
+ code: string;
+ message: string;
+ statusCode?: number;
+};
+
+export function extractApiError(err: unknown): ApiError {
+ if (err && typeof err === "object") {
+ const obj = err as Record;
+ const response = obj.response as Record | undefined;
+ if (response) {
+ const statusCode = response.status as number | undefined;
+ const data = response.data as Record | undefined;
+ return {
+ code: (data?.error as string) || (data?.message as string) || "api_error",
+ message: (data?.message as string) || (data?.error as string) || "An unexpected error occurred",
+ statusCode,
+ };
+ }
+ if (obj.message && typeof obj.message === "string") {
+ return { code: "client_error", message: obj.message };
+ }
+ }
+ return { code: "unknown_error", message: "An unexpected error occurred" };
+}
diff --git a/packages/types/src/freight/index.ts b/packages/types/src/freight/index.ts
index b6d1e6dfa..da109ecda 100644
--- a/packages/types/src/freight/index.ts
+++ b/packages/types/src/freight/index.ts
@@ -64,17 +64,29 @@ export interface ICustomer extends BaseEntity {
}
export interface CreateCustomerDto {
- name: string;
+ userId: string;
+ firstName: string;
+ lastName: string;
email: string;
phone: string;
- company?: string;
- customerType?: CustomerType;
- status?: CustomerStatus;
- tinNumber?: string;
- city?: string;
- country?: string;
- address?: string;
- taxId?: string;
+ companyName: string;
+ companyEmail: string;
+ companyPhone: string;
+ companyLocation: string;
+ companyAddress: string;
+ contactPersonName: string;
+ contactPersonPhone: string;
+ tinNumber: string;
+ vatNumber: string;
+ fanNumber: string;
+ generalManagerName: string;
+ generalManagerEmail: string;
+ generalManagerPhone: string;
+ poaName?: string;
+ poaPhone?: string;
+ poaAddress?: string;
+ poaEmail?: string;
+ poaLocation?: string;
notes?: string;
}
@@ -108,11 +120,54 @@ export interface IConsignment extends BaseEntity {
export interface IBooking extends BaseEntity {
reference: string;
customerId: string;
- trainId?: string;
+ trainId?: string | null;
status: BookingStatus;
scheduledDate: string;
totalAmount: number;
paymentStatus: PaymentStatus;
+
+ contractType: "NEW" | "RENEWAL";
+ previousContractId?: string | null;
+ serviceType: "RAIL_ONLY" | "RAIL_AND_FORWARDING";
+
+ firstMileEnabled: boolean;
+ firstMilePickupAddress?: string | null;
+ lastMileEnabled: boolean;
+ lastMileDeliveryAddress?: string | null;
+
+ equipmentReturn: "WITH_RETURN" | "WITHOUT_RETURN";
+ originStation: string;
+ destinationStation: string;
+ cargoTotalWeightVgm: number;
+
+ freightType: "BULK" | "BREAK_BULK";
+ freightSubtype?: string | null;
+
+ isHazardous: boolean;
+ isRefrigerated: boolean;
+
+ tradeDirection: "IMPORT" | "EXPORT";
+ paymentCurrency: string;
+ allowConsolidation: boolean;
+ consolidationPartnerId?: string | null;
+
+ startDate?: string | null;
+ endDate?: string | null;
+ financialTerms?: string | null;
+
+ containers?: Array<{ type: string; qty: number; vgm: number }> | null;
+
+ versionNumber: number;
+ priorityScore: number;
+
+ approvedByStaffId?: string | null;
+ approvedByStaffAt?: string | null;
+ signedByDirectorId?: string | null;
+ signedByDirectorAt?: string | null;
+ signedByCeoId?: string | null;
+ signedByCeoAt?: string | null;
+
+ files?: Array<{ id: string; name: string; url: string; mimeType: string }>;
}
export interface IInvoice extends BaseEntity {