Merge pull request #1434 from Tria-plc/freight_feature/usermanagement

Freight feature/usermanagement
This commit is contained in:
marshal
2026-08-28 10:34:31 +03:00
committed by GitHub
32 changed files with 1762 additions and 646 deletions

View File

@@ -941,6 +941,52 @@ export const api = {
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
loadScheduleBookingWagon: endpoint<
{ scheduleId: string; bookingId: string; allocationId: string },
import("@/types/trainScheduling").WagonLoadResult
>(
"train-scheduling",
"booking-wagon-load",
({ scheduleId, bookingId, allocationId }) =>
trainSchedulingService.loadScheduleBookingWagon(scheduleId, bookingId, allocationId),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
unloadScheduleBookingWagon: endpoint<
{ scheduleId: string; bookingId: string; allocationId: string },
import("@/types/trainScheduling").WagonLoadResult
>(
"train-scheduling",
"booking-wagon-unload",
({ scheduleId, bookingId, allocationId }) =>
trainSchedulingService.unloadScheduleBookingWagon(scheduleId, bookingId, allocationId),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
bookingWagons: endpoint<
{ bookingId: string },
import("@/types/trainScheduling").BookingWagonRow[]
>(
"train-scheduling",
"booking-wagons",
({ bookingId }) => trainSchedulingService.bookingWagons(bookingId),
({ bookingId }) => ["train-scheduling", "booking-wagons", bookingId],
),
cancelRemainingWagons: endpoint<
{ bookingId: string; scheduleId: string; reason: string; edrFault?: boolean },
unknown
>(
"train-scheduling",
"cancel-remaining-wagons",
({ bookingId, scheduleId, reason, edrFault }) =>
trainSchedulingService.cancelRemainingWagons(bookingId, { scheduleId, reason, edrFault }),
undefined,
() => TRAIN_SCHEDULING_INVALIDATIONS,
),
intercityBookings: endpoint<
void,
import("@/types/trainScheduling").IntercityRideAlongRow[]
@@ -2245,11 +2291,14 @@ export const api = {
seedComposition,
),
removeWagon: endpoint<{ id: string; wagonId: string }, TrainComposition>(
removeWagon: endpoint<
{ id: string; wagonId: string; reason?: string },
TrainComposition
>(
"train-builder",
"removeWagon",
({ id, wagonId }) =>
trainBuilderService.removeWagon(id, wagonId).then((r) => r.data),
({ id, wagonId, reason }) =>
trainBuilderService.removeWagon(id, wagonId, reason).then((r) => r.data),
undefined,
() => TRAIN_BUILDER_WAGON_INVALIDATIONS,
seedComposition,
@@ -2276,43 +2325,6 @@ export const api = {
({ 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",

View File

@@ -311,6 +311,11 @@ export interface TrainHistoryEntry {
actor: string | null;
/** Set when the change came from a trip (schedule); null = train-builder edit. */
scheduleReference: string | null;
/**
* Why the wagon left the consist — required for a detach / maintenance move
* on a SCHEDULED run. Null for trip events and unscheduled builder edits.
*/
reason: string | null;
occurredAt: string;
}
@@ -436,37 +441,20 @@ export const trainBuilderService = {
}),
assignWagons: (id: string, wagonIds: string[]) =>
apiClient.post<TrainComposition>(`${BASE}/${id}/wagons`, { wagonIds }),
removeWagon: (id: string, wagonId: string) =>
apiClient.delete<TrainComposition>(`${BASE}/${id}/wagons/${wagonId}`),
/** `reason` is required by the API while the train is on a SCHEDULED run. */
removeWagon: (id: string, wagonId: string, reason?: string) =>
apiClient.delete<TrainComposition>(`${BASE}/${id}/wagons/${wagonId}`, {
data: reason ? { reason } : undefined,
}),
/** Detach a wagon and move it to MAINTENANCE status. */
/** `note` is the maintenance reason — recorded with the train it came off. */
sendWagonToMaintenance: (id: string, wagonId: string, note?: string) =>
apiClient.post<TrainComposition>(`${BASE}/${id}/wagons/${wagonId}/maintenance`, {
note,
}),
/** Requests to detach a wagon from a SCHEDULED train, newest first. */
/** Detach/maintenance audit rows of a SCHEDULED-run 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. */

View File

@@ -11,6 +11,8 @@ import type {
BookingWindow,
AssignBookingsPayload,
BookingLoadResult,
BookingWagonRow,
WagonLoadResult,
BookingUnloadResult,
CompositionRemovalEntry,
DocReviewAlert,
@@ -510,6 +512,48 @@ export const trainSchedulingService = {
return unwrap(response.data);
},
loadScheduleBookingWagon: async (
scheduleId: string,
bookingId: string,
allocationId: string,
): Promise<WagonLoadResult> => {
const response = await client.post<WagonLoadResult>(
URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_WAGON_LOAD(scheduleId, bookingId, allocationId),
{},
);
return unwrap(response.data);
},
unloadScheduleBookingWagon: async (
scheduleId: string,
bookingId: string,
allocationId: string,
): Promise<WagonLoadResult> => {
const response = await client.post<WagonLoadResult>(
URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_WAGON_UNLOAD(scheduleId, bookingId, allocationId),
{},
);
return unwrap(response.data);
},
bookingWagons: async (bookingId: string): Promise<BookingWagonRow[]> => {
const response = await client.get<BookingWagonRow[]>(
URL_CONSTANTS.TRAIN_SCHEDULING.BOOKING_WAGONS(bookingId),
);
return unwrap(response.data);
},
cancelRemainingWagons: async (
bookingId: string,
payload: { scheduleId: string; reason: string; edrFault?: boolean },
): Promise<unknown> => {
const response = await client.post(
URL_CONSTANTS.TRAIN_SCHEDULING.CANCEL_REMAINING_WAGONS(bookingId),
payload,
);
return unwrap(response.data);
},
listIntercityBookings: async (): Promise<
import("@/types/trainScheduling").IntercityRideAlongRow[]
> => {