mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-03 06:13:39 +00:00
Merge pull request #963 from Tria-plc/freight_feature/usermanagement
Freight feature/usermanagement
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import type { PaginatedResponse } from "@edr/types";
|
||||
|
||||
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
|
||||
import type { FleetResourceSlug } from "@/pages/fleet/config/resources";
|
||||
import type { BookingDetail } from "@/types/booking";
|
||||
@@ -208,6 +210,7 @@ import {
|
||||
type CreateTransferRequestPayload,
|
||||
type BulkFulfillResult,
|
||||
type TransferHistory,
|
||||
type TransferRequestListFilter,
|
||||
} from "./wagon.service";
|
||||
import { warehouseService } from "./warehouse.service";
|
||||
|
||||
@@ -1719,14 +1722,14 @@ export const api = {
|
||||
|
||||
wagonTransferRequests: {
|
||||
list: endpoint<
|
||||
{ status?: WagonTransferRequest["status"] },
|
||||
WagonTransferRequest[]
|
||||
{ filter?: TransferRequestListFilter },
|
||||
PaginatedResponse<WagonTransferRequest>
|
||||
>(
|
||||
"wagonTransferRequests",
|
||||
"list",
|
||||
({ status }) =>
|
||||
wagonTransferRequestService.list(status).then((r) => r.data),
|
||||
({ status }) => ["wagonTransferRequests", "list", status ?? "ALL"],
|
||||
({ filter }) =>
|
||||
wagonTransferRequestService.list(filter).then((r) => r.data),
|
||||
({ filter }) => ["wagonTransferRequests", "list", filter ?? {}],
|
||||
),
|
||||
|
||||
getById: endpoint<{ id: string }, WagonTransferRequest>(
|
||||
@@ -1774,19 +1777,47 @@ export const api = {
|
||||
() => [["wagonTransferRequests"]],
|
||||
),
|
||||
|
||||
history: endpoint<void, TransferHistory>(
|
||||
closeShort: endpoint<{ id: string; note?: string }, WagonTransferRequest>(
|
||||
"wagonTransferRequests",
|
||||
"history",
|
||||
() => wagonTransferRequestService.myHistory().then((r) => r.data),
|
||||
() => ["wagonTransferRequests", "history", "mine"],
|
||||
"closeShort",
|
||||
({ id, note }) =>
|
||||
wagonTransferRequestService.closeShort(id, note).then((r) => r.data),
|
||||
undefined,
|
||||
() => [["wagonTransferRequests"]],
|
||||
),
|
||||
|
||||
historyAll: endpoint<{ userId?: string }, TransferHistory>(
|
||||
history: endpoint<{ page?: number; pageSize?: number }, TransferHistory>(
|
||||
"wagonTransferRequests",
|
||||
"history",
|
||||
({ page, pageSize }) =>
|
||||
wagonTransferRequestService.myHistory(page, pageSize).then((r) => r.data),
|
||||
({ page, pageSize }) => [
|
||||
"wagonTransferRequests",
|
||||
"history",
|
||||
"mine",
|
||||
page ?? 1,
|
||||
pageSize ?? 20,
|
||||
],
|
||||
),
|
||||
|
||||
historyAll: endpoint<
|
||||
{ userId?: string; page?: number; pageSize?: number },
|
||||
TransferHistory
|
||||
>(
|
||||
"wagonTransferRequests",
|
||||
"historyAll",
|
||||
({ userId }) =>
|
||||
wagonTransferRequestService.allHistory(userId).then((r) => r.data),
|
||||
({ userId }) => ["wagonTransferRequests", "history", "all", userId ?? ""],
|
||||
({ userId, page, pageSize }) =>
|
||||
wagonTransferRequestService
|
||||
.allHistory(userId, page, pageSize)
|
||||
.then((r) => r.data),
|
||||
({ userId, page, pageSize }) => [
|
||||
"wagonTransferRequests",
|
||||
"history",
|
||||
"all",
|
||||
userId ?? "",
|
||||
page ?? 1,
|
||||
pageSize ?? 20,
|
||||
],
|
||||
),
|
||||
},
|
||||
|
||||
|
||||
@@ -390,11 +390,12 @@ export const bookingsService = {
|
||||
uploadDeliveryOrder: async (
|
||||
id: string,
|
||||
file: File,
|
||||
vesselDepartureDate?: string,
|
||||
dates: { vesselArrivalDate: string; doCollectedDate: string },
|
||||
): Promise<BookingDetail> => {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
if (vesselDepartureDate) form.append("vesselDepartureDate", vesselDepartureDate);
|
||||
form.append("vesselArrivalDate", dates.vesselArrivalDate);
|
||||
form.append("doCollectedDate", dates.doCollectedDate);
|
||||
const response = await client.post(B.CLEARANCE_DELIVERY_ORDER(id), form, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
});
|
||||
|
||||
@@ -126,6 +126,23 @@ export interface SignContractPayload {
|
||||
consentText?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* One stored version of a clearance document. The current row plus every
|
||||
* superseded upload — staff corrections never erase what the customer sent.
|
||||
*/
|
||||
export interface ClearanceDocumentVersion {
|
||||
id: string;
|
||||
name: string;
|
||||
url: string;
|
||||
size: number;
|
||||
mimeType: string;
|
||||
uploadedAt: string;
|
||||
isCurrent: boolean;
|
||||
replacedAt: string | null;
|
||||
replacedByUserId: string | null;
|
||||
replaceReason: string | null;
|
||||
}
|
||||
|
||||
async function postContract<T>(url: string, body?: unknown): Promise<T> {
|
||||
const response = await client.post<T>(url, body ?? {});
|
||||
return unwrap(response.data);
|
||||
@@ -297,6 +314,50 @@ export const contractsService = {
|
||||
) =>
|
||||
postContract<Freight.IContract>(C.CLEARANCE_REVIEW(id), payload),
|
||||
|
||||
/** GL ET asks Djibouti to name the officer handling the shipment in transit. */
|
||||
requestTransitAssignee: (id: string, note?: string) =>
|
||||
postContract<Freight.IContract>(
|
||||
C.CLEARANCE_TRANSIT_ASSIGNEE_REQUEST(id),
|
||||
{ note },
|
||||
),
|
||||
|
||||
/** GL Djibouti names (or changes) that officer — unblocks the declaration. */
|
||||
assignTransitAssignee: (id: string, assignee: string) =>
|
||||
postContract<Freight.IContract>(C.CLEARANCE_TRANSIT_ASSIGNEE_ASSIGN(id), {
|
||||
assignee,
|
||||
}),
|
||||
|
||||
/**
|
||||
* Replace a clearance document in place. The customer's original is retired
|
||||
* into the version history rather than overwritten, and the new file comes
|
||||
* back unreviewed so it still has to be approved.
|
||||
*/
|
||||
replaceClearanceDocument: async (
|
||||
id: string,
|
||||
fileKey: string,
|
||||
file: File,
|
||||
reason: string,
|
||||
): Promise<Freight.IContract> => {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
form.append("reason", reason);
|
||||
const response = await client.post(
|
||||
C.CLEARANCE_DOC_REPLACE(id, fileKey),
|
||||
form,
|
||||
{ headers: { "Content-Type": "multipart/form-data" } },
|
||||
);
|
||||
return unwrap(response.data) as Freight.IContract;
|
||||
},
|
||||
|
||||
/** Every stored version of one clearance document, newest first. */
|
||||
getClearanceDocumentVersions: async (
|
||||
id: string,
|
||||
fileKey: string,
|
||||
): Promise<ClearanceDocumentVersion[]> => {
|
||||
const response = await client.get(C.CLEARANCE_DOC_VERSIONS(id, fileKey));
|
||||
return (unwrap(response.data) as ClearanceDocumentVersion[]) ?? [];
|
||||
},
|
||||
|
||||
uploadClearanceOutput: async (
|
||||
id: string,
|
||||
files: Record<string, File | null>,
|
||||
@@ -390,11 +451,12 @@ export const contractsService = {
|
||||
uploadDeliveryOrder: async (
|
||||
id: string,
|
||||
file: File,
|
||||
vesselDepartureDate?: string,
|
||||
dates: { vesselArrivalDate: string; doCollectedDate: string },
|
||||
): Promise<Freight.IContract> => {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
if (vesselDepartureDate) form.append("vesselDepartureDate", vesselDepartureDate);
|
||||
form.append("vesselArrivalDate", dates.vesselArrivalDate);
|
||||
form.append("doCollectedDate", dates.doCollectedDate);
|
||||
const response = await client.post(C.CLEARANCE_DELIVERY_ORDER(id), form, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Freight } from "@edr/types";
|
||||
import type { Freight, PaginatedResponse } from "@edr/types";
|
||||
|
||||
import { api as apiClient } from "../auth/http";
|
||||
|
||||
@@ -109,10 +109,15 @@ export interface WagonTransferRequest {
|
||||
toYardId: string;
|
||||
wagonTypeId: string;
|
||||
quantity: number;
|
||||
/** How many have actually moved so far — OCC delivers in instalments. */
|
||||
fulfilledQuantity: number;
|
||||
status: Freight.WagonTransferRequestStatus;
|
||||
requestedByUserId: string | null;
|
||||
fulfilledByUserId: string | null;
|
||||
/** When the LAST instalment ran, not necessarily the full count. */
|
||||
fulfilledAt: string | null;
|
||||
closedShortAt?: string | null;
|
||||
closedShortByUserId?: string | null;
|
||||
/** Why the wagons are needed — required for new requests, shown on the queue. */
|
||||
reason?: string | null;
|
||||
note: string | null;
|
||||
@@ -132,7 +137,7 @@ export interface CreateTransferRequestPayload {
|
||||
note?: string;
|
||||
}
|
||||
|
||||
/** Bulk accept-and-execute result: what ran, what stayed PENDING and why. */
|
||||
/** Bulk accept-and-execute result: what ran, what stayed open and why. */
|
||||
export interface BulkFulfillResult {
|
||||
fulfilled: WagonTransferRequest[];
|
||||
skipped: Array<{ id: string; reason: string }>;
|
||||
@@ -142,20 +147,52 @@ export interface BulkFulfillResult {
|
||||
export interface TransferHistory {
|
||||
requests: WagonTransferRequest[];
|
||||
movements: WagonMovementRecord[];
|
||||
meta: {
|
||||
page: number;
|
||||
pageSize: number;
|
||||
requestsTotal: number;
|
||||
movementsTotal: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
/** Desk list filters — `status` may be a comma-separated set ("open" tab). */
|
||||
export interface TransferRequestListFilter {
|
||||
status?: string;
|
||||
fromYardId?: string;
|
||||
toYardId?: string;
|
||||
wagonTypeId?: string;
|
||||
search?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
const listQuery = (filter: TransferRequestListFilter = {}): string => {
|
||||
const params = new URLSearchParams();
|
||||
for (const [key, value] of Object.entries(filter)) {
|
||||
if (value !== undefined && value !== null && value !== '') {
|
||||
params.set(key, String(value));
|
||||
}
|
||||
}
|
||||
const qs = params.toString();
|
||||
return qs ? `?${qs}` : '';
|
||||
};
|
||||
|
||||
export const wagonTransferRequestService = {
|
||||
list: (status?: Freight.WagonTransferRequestStatus) =>
|
||||
apiClient.get<WagonTransferRequest[]>(
|
||||
`/wagon-transfer-requests${status ? `?status=${status}` : ''}`,
|
||||
list: (filter: TransferRequestListFilter = {}) =>
|
||||
apiClient.get<PaginatedResponse<WagonTransferRequest>>(
|
||||
`/wagon-transfer-requests${listQuery(filter)}`,
|
||||
),
|
||||
/** The caller's own history (both roles: requests they filed and fulfilled). */
|
||||
myHistory: () =>
|
||||
apiClient.get<TransferHistory>('/wagon-transfer-requests/history'),
|
||||
/** Admin: any/all staff's history (optional userId filter). */
|
||||
allHistory: (userId?: string) =>
|
||||
myHistory: (page = 1, pageSize = 20) =>
|
||||
apiClient.get<TransferHistory>(
|
||||
`/wagon-transfer-requests/history/all${userId ? `?userId=${userId}` : ''}`,
|
||||
`/wagon-transfer-requests/history?page=${page}&pageSize=${pageSize}`,
|
||||
),
|
||||
/** Admin: any/all staff's history (optional userId filter). */
|
||||
allHistory: (userId?: string, page = 1, pageSize = 20) =>
|
||||
apiClient.get<TransferHistory>(
|
||||
`/wagon-transfer-requests/history/all?page=${page}&pageSize=${pageSize}` +
|
||||
`${userId ? `&userId=${userId}` : ''}`,
|
||||
),
|
||||
getById: (id: string) =>
|
||||
apiClient.get<WagonTransferRequest>(`/wagon-transfer-requests/${id}`),
|
||||
@@ -176,4 +213,10 @@ export const wagonTransferRequestService = {
|
||||
apiClient.post<WagonTransferRequest>(
|
||||
`/wagon-transfer-requests/${id}/cancel`,
|
||||
),
|
||||
/** OCC: end the request short — the source yard has no more to give. */
|
||||
closeShort: (id: string, note?: string) =>
|
||||
apiClient.post<WagonTransferRequest>(
|
||||
`/wagon-transfer-requests/${id}/close-short`,
|
||||
{ note },
|
||||
),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user