Files
edr-platform/apps/edr-freight-api/src/modules/train-crew/entities/train-crew-assignment.entity.ts
marshalyordanos c5c8712c5b 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.
2026-09-06 10:29:22 +03:00

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;
}