mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-08 07:45:45 +00:00
- 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.
115 lines
3.9 KiB
TypeScript
115 lines
3.9 KiB
TypeScript
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
|
|
import { BaseEntity } from '@edr/api-common';
|
|
|
|
import { TrainCrewMember, TrainCrewRole } from './train-crew-member.entity';
|
|
|
|
/**
|
|
* Legacy fixed corridor segments.
|
|
*
|
|
* Kept only so historic rows written before segments became yard-to-yard still
|
|
* read back. New assignments carry `fromYardId`/`toYardId` instead: staff pick
|
|
* any two yards on the corridor, so a leg is no longer limited to the three
|
|
* spans the original design hard-coded.
|
|
*/
|
|
export enum CrewSegment {
|
|
INDODE_DIRE_DAWA = 'INDODE_DIRE_DAWA',
|
|
DIRE_DAWA_NAGAD = 'DIRE_DAWA_NAGAD',
|
|
FULL_CORRIDOR = 'FULL_CORRIDOR',
|
|
}
|
|
|
|
/** Driver duty role for one run (§2). Null for non-driving crew. */
|
|
export enum CrewDutyRole {
|
|
PRIMARY = 'PRIMARY',
|
|
ASSISTANT = 'ASSISTANT',
|
|
BENCH_RELIEF = 'BENCH_RELIEF',
|
|
}
|
|
|
|
export enum CrewAssignmentStatus {
|
|
PLANNED = 'PLANNED',
|
|
CONFIRMED = 'CONFIRMED',
|
|
COMPLETED = 'COMPLETED',
|
|
REMOVED = 'REMOVED',
|
|
}
|
|
|
|
/**
|
|
* One roster member assigned to one train schedule.
|
|
*
|
|
* `role` is snapshotted from the roster at assignment time: a member who later
|
|
* changes role must not silently rewrite the crew of a run that already
|
|
* departed. `dutyRole` and `segment` live here rather than on the roster
|
|
* because they are properties of THIS run — a driver who is Primary on one
|
|
* trip is Assistant on the next. Crew sizes are free-form: operations size each
|
|
* run to its own need rather than to a fixed pairing case.
|
|
*
|
|
* Duty stamps feed the §3 monthly overtime totals. Per the agreed scope the
|
|
* platform reports OT hours only; no salary is stored anywhere, and the payroll
|
|
* conversion stays with finance.
|
|
*/
|
|
@Entity({ schema: 'freight', name: 'train_crew_assignments' })
|
|
@Index(['trainScheduleId'])
|
|
@Index(['crewMemberId'])
|
|
@Index(['status'])
|
|
export class TrainCrewAssignment extends BaseEntity {
|
|
@Column({ name: 'train_schedule_id', type: 'uuid' })
|
|
trainScheduleId!: string;
|
|
|
|
@Column({ name: 'crew_member_id', type: 'uuid' })
|
|
crewMemberId!: string;
|
|
|
|
@ManyToOne(() => TrainCrewMember, { onDelete: 'RESTRICT' })
|
|
@JoinColumn({ name: 'crew_member_id' })
|
|
crewMember?: TrainCrewMember;
|
|
|
|
@Column({ name: 'role', type: 'varchar', length: 32 })
|
|
role!: TrainCrewRole;
|
|
|
|
@Column({ name: 'duty_role', type: 'varchar', length: 16, nullable: true })
|
|
dutyRole?: CrewDutyRole | null;
|
|
|
|
/** Legacy fixed segment — null on every assignment written since yard legs. */
|
|
@Column({ name: 'segment', type: 'varchar', length: 24, nullable: true })
|
|
segment?: CrewSegment | null;
|
|
|
|
/**
|
|
* The leg this driver works, as two yards on the corridor.
|
|
*
|
|
* Free-form on purpose: operations pick any yard as a handover point, so a
|
|
* crew change at Meiso or Feto is expressible without a code change. The
|
|
* schedule's own origin and destination bound what staff may choose.
|
|
*/
|
|
@Column({ name: 'from_yard_id', type: 'uuid', nullable: true })
|
|
fromYardId?: string | null;
|
|
|
|
@Column({ name: 'to_yard_id', type: 'uuid', nullable: true })
|
|
toYardId?: string | null;
|
|
|
|
/**
|
|
* Mandatory off-duty layover at Dire Dawa (§1.3). The document gives ~5 hours
|
|
* as a typical duration, not a rule, so nothing here enforces a length — the
|
|
* stamps are recorded and reported.
|
|
*/
|
|
@Column({ name: 'layover_start_at', type: 'timestamptz', nullable: true })
|
|
layoverStartAt?: Date | null;
|
|
|
|
@Column({ name: 'layover_end_at', type: 'timestamptz', nullable: true })
|
|
layoverEndAt?: Date | null;
|
|
|
|
/** Worked span for this run — accumulated monthly for the §3 OT calculation. */
|
|
@Column({ name: 'duty_start_at', type: 'timestamptz', nullable: true })
|
|
dutyStartAt?: Date | null;
|
|
|
|
@Column({ name: 'duty_end_at', type: 'timestamptz', nullable: true })
|
|
dutyEndAt?: Date | null;
|
|
|
|
@Column({
|
|
name: 'status',
|
|
type: 'varchar',
|
|
length: 16,
|
|
default: CrewAssignmentStatus.PLANNED,
|
|
})
|
|
status!: CrewAssignmentStatus;
|
|
|
|
@Column({ name: 'notes', type: 'text', nullable: true })
|
|
notes?: string | null;
|
|
}
|