mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 11:55:42 +00:00
feat: enhance train scheduling and contract management features
- Added StationWorkControls to manage loading/unloading phases in TrainScheduleV2DetailPage. - Implemented API endpoints for recording station work and managing wagon detach requests. - Updated contract templates to include Ethiopian customs handling options. - Enhanced shipment forms to collect customs clearing agent details for without-customs bookings. - Introduced NUMBER_OF_WAGONS as a unit of measure for bulk cargo, allowing customers to specify wagon counts. - Improved validation for customs clearing agent information in shipment forms. - Updated various components and services to accommodate new features and ensure data integrity.
This commit is contained in:
@@ -241,6 +241,7 @@ import {
|
||||
type TrainComposition,
|
||||
type UpdateTrainDetailsPayload,
|
||||
type UsedTrainNumbers,
|
||||
type WagonDetachRequestRow,
|
||||
} from "./trainBuilder.service";
|
||||
import {
|
||||
trainSchedulingService,
|
||||
@@ -897,6 +898,24 @@ export const api = {
|
||||
({ scheduleId }) => ["train-scheduling", "yard-work", scheduleId],
|
||||
),
|
||||
|
||||
recordStationWork: endpoint<
|
||||
{
|
||||
scheduleId: string;
|
||||
yardId: string;
|
||||
phase: "loading" | "unloading";
|
||||
edge: "start" | "end";
|
||||
at?: string;
|
||||
},
|
||||
import("@/types/trainScheduling").StationWorkPhaseLog
|
||||
>(
|
||||
"train-scheduling",
|
||||
"station-work",
|
||||
({ scheduleId, yardId, phase, edge, at }) =>
|
||||
trainSchedulingService.recordStationWork(scheduleId, yardId, phase, edge, at),
|
||||
undefined,
|
||||
() => TRAIN_SCHEDULING_INVALIDATIONS,
|
||||
),
|
||||
|
||||
loadScheduleBooking: endpoint<
|
||||
{ scheduleId: string; bookingId: string },
|
||||
import("@/types/trainScheduling").BookingLoadResult
|
||||
@@ -2248,6 +2267,51 @@ export const api = {
|
||||
seedComposition,
|
||||
),
|
||||
|
||||
// Key derives to ["train-builder", "detachRequests", input] — the shared
|
||||
// TRAIN_BUILDER.ROOT invalidation refreshes it after every consist edit.
|
||||
detachRequests: endpoint<{ id: string }, WagonDetachRequestRow[]>(
|
||||
"train-builder",
|
||||
"detachRequests",
|
||||
({ id }) => trainBuilderService.detachRequests(id).then((r) => r.data),
|
||||
),
|
||||
|
||||
createDetachRequest: endpoint<
|
||||
{ id: string; wagonId: string; action: "DETACH" | "MAINTENANCE"; reason: string },
|
||||
WagonDetachRequestRow
|
||||
>(
|
||||
"train-builder",
|
||||
"createDetachRequest",
|
||||
({ id, wagonId, action, reason }) =>
|
||||
trainBuilderService.createDetachRequest(id, wagonId, { action, reason }).then((r) => r.data),
|
||||
undefined,
|
||||
() => TRAIN_BUILDER_INVALIDATIONS,
|
||||
),
|
||||
|
||||
approveDetachRequest: endpoint<
|
||||
{ id: string; requestId: string; note?: string },
|
||||
TrainComposition
|
||||
>(
|
||||
"train-builder",
|
||||
"approveDetachRequest",
|
||||
({ id, requestId, note }) =>
|
||||
trainBuilderService.approveDetachRequest(id, requestId, note).then((r) => r.data),
|
||||
undefined,
|
||||
() => TRAIN_BUILDER_WAGON_INVALIDATIONS,
|
||||
seedComposition,
|
||||
),
|
||||
|
||||
rejectDetachRequest: endpoint<
|
||||
{ id: string; requestId: string; note: string },
|
||||
TrainComposition
|
||||
>(
|
||||
"train-builder",
|
||||
"rejectDetachRequest",
|
||||
({ id, requestId, note }) =>
|
||||
trainBuilderService.rejectDetachRequest(id, requestId, note).then((r) => r.data),
|
||||
undefined,
|
||||
() => TRAIN_BUILDER_INVALIDATIONS,
|
||||
),
|
||||
|
||||
reorderWagons: endpoint<{ id: string; wagonIds: string[] }, TrainComposition>(
|
||||
"train-builder",
|
||||
"reorderWagons",
|
||||
|
||||
@@ -34,7 +34,12 @@ export interface ContractTemplate {
|
||||
* Null for intercity — domestic movements have no customs leg.
|
||||
*/
|
||||
withCustoms?: boolean | null;
|
||||
/** The five seeded container templates — cannot be deleted. */
|
||||
/**
|
||||
* Bulk templates only: the with-customs variant restricted to Ethiopian-side
|
||||
* clearing (Djibouti stays with the client).
|
||||
*/
|
||||
ethiopianCustomsOnly?: boolean | null;
|
||||
/** The seeded container templates — cannot be deleted. */
|
||||
isSystem: boolean;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
@@ -45,6 +50,8 @@ export interface CreateContractTemplatePayload {
|
||||
tradeDirection: BulkTemplateDirection;
|
||||
/** Omitted for INTERCITY — the API rejects the flag there. */
|
||||
withCustoms?: boolean;
|
||||
/** Ethiopian-side clearing only; requires withCustoms: true. */
|
||||
ethiopianCustomsOnly?: boolean;
|
||||
name?: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
@@ -386,6 +386,22 @@ export interface UpdateScheduleWagonYardsPayload {
|
||||
|
||||
export type UpdateScheduleWagonYardsResult = ScheduleWagonYards & { warnings: string[] };
|
||||
|
||||
/** One detach/maintenance approval request — pending or decided (audit trail). */
|
||||
export interface WagonDetachRequestRow {
|
||||
id: string;
|
||||
wagonId: string;
|
||||
wagonNumber: string;
|
||||
action: "DETACH" | "MAINTENANCE";
|
||||
reason: string;
|
||||
status: "PENDING" | "APPROVED" | "REJECTED";
|
||||
requestedById: string | null;
|
||||
requestedBy: string | null;
|
||||
requestedAt: string;
|
||||
decidedBy: string | null;
|
||||
decidedAt: string | null;
|
||||
decisionNote: string | null;
|
||||
}
|
||||
|
||||
export const trainBuilderService = {
|
||||
list: (filters: BuiltTrainListFilters = {}) =>
|
||||
apiClient.get<BuiltTrainListResponse>(`${BASE}${toQuery(filters)}`),
|
||||
@@ -428,6 +444,29 @@ export const trainBuilderService = {
|
||||
apiClient.post<TrainComposition>(`${BASE}/${id}/wagons/${wagonId}/maintenance`, {
|
||||
note,
|
||||
}),
|
||||
/** Requests to detach a wagon from a SCHEDULED train, newest first. */
|
||||
detachRequests: (id: string) =>
|
||||
apiClient.get<WagonDetachRequestRow[]>(`${BASE}/${id}/detach-requests`),
|
||||
/** File a detach/maintenance approval request (reason required). */
|
||||
createDetachRequest: (
|
||||
id: string,
|
||||
wagonId: string,
|
||||
payload: { action: "DETACH" | "MAINTENANCE"; reason: string },
|
||||
) =>
|
||||
apiClient.post<WagonDetachRequestRow>(
|
||||
`${BASE}/${id}/wagons/${wagonId}/detach-requests`,
|
||||
payload,
|
||||
),
|
||||
/** Approve a pending request — executes the detach immediately. */
|
||||
approveDetachRequest: (id: string, requestId: string, note?: string) =>
|
||||
apiClient.post<TrainComposition>(`${BASE}/${id}/detach-requests/${requestId}/approve`, {
|
||||
note,
|
||||
}),
|
||||
/** Reject a pending request — a note explaining why is required. */
|
||||
rejectDetachRequest: (id: string, requestId: string, note: string) =>
|
||||
apiClient.post<TrainComposition>(`${BASE}/${id}/detach-requests/${requestId}/reject`, {
|
||||
note,
|
||||
}),
|
||||
reorderWagons: (id: string, wagonIds: string[]) =>
|
||||
apiClient.post<TrainComposition>(`${BASE}/${id}/reorder-wagons`, { wagonIds }),
|
||||
/** Park the train indefinitely — only allowed with no active schedule. */
|
||||
|
||||
@@ -471,6 +471,23 @@ export const trainSchedulingService = {
|
||||
return unwrap(response.data);
|
||||
},
|
||||
|
||||
/** Start/end (or correct, via `at`) a station's loading/unloading time window. */
|
||||
recordStationWork: async (
|
||||
scheduleId: string,
|
||||
yardId: string,
|
||||
phase: "loading" | "unloading",
|
||||
edge: "start" | "end",
|
||||
at?: string,
|
||||
): Promise<import("@/types/trainScheduling").StationWorkPhaseLog> => {
|
||||
const response = await client.post<
|
||||
import("@/types/trainScheduling").StationWorkPhaseLog
|
||||
>(
|
||||
URL_CONSTANTS.TRAIN_SCHEDULING.STATION_WORK(scheduleId, yardId, phase, edge),
|
||||
at ? { at } : {},
|
||||
);
|
||||
return unwrap(response.data);
|
||||
},
|
||||
|
||||
loadScheduleBooking: async (
|
||||
scheduleId: string,
|
||||
bookingId: string,
|
||||
|
||||
Reference in New Issue
Block a user