feat(train-crew): implement crew assignment functionality

- Added TrainCrewAssignment module with controller and service for managing crew assignments.
- Integrated TrainCrewAssignmentService into TrainSchedulingService to ensure crew readiness before train dispatch.
- Updated TrainScheduling module to include TrainCrewModule for dependency injection.
- Introduced new permissions for assigning train crew in freight permissions registry.
- Enhanced front-end ScheduleCrewPage to allow assignment of crew members to train schedules, including validation and UI for adding/removing drivers and support crew.
- Created trainCrewAssignment.service to handle API interactions for crew assignments.
This commit is contained in:
marshalyordanos
2026-09-06 10:29:22 +03:00
parent 63ccb060c6
commit c5c8712c5b
16 changed files with 2324 additions and 8 deletions

View File

@@ -0,0 +1,104 @@
import { api as apiClient } from '../auth/http';
import type { TrainCrewMember, TrainCrewRole } from './trainCrew.service';
export type CrewDutyRole = 'PRIMARY' | 'ASSISTANT' | 'BENCH_RELIEF';
/** A yard on the schedule's route, ordered along the corridor. */
export interface CorridorYard {
id: string;
label: string;
country: string;
displayOrder: number;
}
export interface CrewAssignment {
id: string;
trainScheduleId: string;
crewMemberId: string;
role: TrainCrewRole;
dutyRole?: CrewDutyRole | null;
fromYardId?: string | null;
toYardId?: string | null;
status: string;
crewMember?: TrainCrewMember;
}
/** What the consist and its cargo demand (§1.2), detected server-side. */
export interface CrewDemand {
hasBadOrderWagon: boolean;
badOrderWagonLabels: string[];
hasReeferCargo: boolean;
reeferSources: string[];
hasHazmatCargo: boolean;
hazmatSources: string[];
hasBreakBulkCargo: boolean;
breakBulkSources: string[];
hasLivestockCargo: boolean;
livestockSources: string[];
}
export interface CrewRequirement {
role: TrainCrewRole;
/** Hard floor — 0 unless the cargo or consist forces someone aboard. */
min: number;
/** The count §1.2 suggests. A hint only; nothing enforces it. */
typical: number;
reason: string;
}
export interface CrewValidation {
complete: boolean;
issues: Array<{ code: string; message: string }>;
runType: 'SHORT_RUN' | 'LONG_RUN' | null;
}
export interface ScheduleCrewResponse {
scheduleId: string;
assignments: CrewAssignment[];
/** Yards a driver leg may use — bounded by the schedule's own endpoints. */
corridorYards: CorridorYard[];
demand: CrewDemand;
requirements: {
technician: CrewRequirement;
specialized: CrewRequirement[];
};
validation: CrewValidation;
}
export interface SaveCrewPayload {
assignments: Array<{
crewMemberId: string;
role: TrainCrewRole;
dutyRole?: CrewDutyRole;
fromYardId?: string;
toYardId?: string;
}>;
}
const base = (scheduleId: string) => `/train-schedules/${scheduleId}/crew`;
export const trainCrewAssignmentService = {
get: (scheduleId: string) =>
apiClient.get<ScheduleCrewResponse>(base(scheduleId)),
eligibleDrivers: (scheduleId: string, fromYardId: string, toYardId: string) =>
apiClient.get<TrainCrewMember[]>(
`${base(scheduleId)}/eligible-drivers?fromYardId=${fromYardId}&toYardId=${toYardId}`,
),
corridorYards: (scheduleId: string) =>
apiClient.get<CorridorYard[]>(`${base(scheduleId)}/corridor-yards`),
save: (scheduleId: string, payload: SaveCrewPayload) =>
apiClient.put<CrewValidation>(base(scheduleId), payload),
};
export const DUTY_ROLE_OPTIONS: Array<{ value: CrewDutyRole; label: string }> = [
{ value: 'PRIMARY', label: 'Primary Driver' },
{ value: 'ASSISTANT', label: 'Assistant Driver' },
{ value: 'BENCH_RELIEF', label: 'Bench/Relief Driver' },
];
export const dutyRoleLabel = (dutyRole: CrewDutyRole): string =>
({
PRIMARY: 'Primary Driver',
ASSISTANT: 'Assistant Driver',
BENCH_RELIEF: 'Bench/Relief Driver',
})[dutyRole];