booking operations and trains scheduling also allocations

This commit is contained in:
marshal
2026-06-10 00:48:32 +03:00
parent 675975bc08
commit 5774d7db9d
180 changed files with 13423 additions and 3877 deletions

View File

@@ -1,4 +1,5 @@
import { BaseEntity } from '@edr/api-common';
import { TrainScheduleStatus as TrainScheduleStatusEnum } from '@edr/types';
import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany, OneToOne } from 'typeorm';
import { Yard } from '../../rule-engine/entities/yard.entity';
@@ -7,11 +8,11 @@ import { TrainSet } from '../../train-sets/entities/train-set.entity';
import { TrainScheduleBooking } from './train-schedule-booking.entity';
export const TRAIN_SCHEDULE_STATUSES = [
'DRAFT',
'SCHEDULED',
'DISPATCHED',
'ARRIVED',
'CANCELLED',
TrainScheduleStatusEnum.Draft,
TrainScheduleStatusEnum.Scheduled,
TrainScheduleStatusEnum.Dispatched,
TrainScheduleStatusEnum.Arrived,
TrainScheduleStatusEnum.Cancelled,
] as const;
export type TrainScheduleStatus = (typeof TRAIN_SCHEDULE_STATUSES)[number];
@@ -57,6 +58,27 @@ export class TrainSchedule extends BaseEntity {
@Column({ name: 'status', type: 'varchar', length: 20, default: 'DRAFT' })
status!: TrainScheduleStatus;
@Column({ name: 'train_number', type: 'varchar', length: 20, nullable: true })
trainNumber?: string | null;
@Column({ name: 'direction', type: 'varchar', length: 10, nullable: true })
direction?: string | null;
@Column({ name: 'actual_departure_at', type: 'timestamptz', nullable: true })
actualDepartureAt?: Date | null;
@Column({ name: 'actual_arrival_at', type: 'timestamptz', nullable: true })
actualArrivalAt?: Date | null;
@Column({ name: 'prepared_by_user_id', type: 'uuid', nullable: true })
preparedByUserId?: string | null;
@Column({ name: 'checked_by_user_id', type: 'uuid', nullable: true })
checkedByUserId?: string | null;
@Column({ name: 'max_wagons', type: 'int', default: 53 })
maxWagons!: number;
@OneToMany(() => TrainScheduleBooking, (scheduleBooking) => scheduleBooking.trainSchedule)
scheduleBookings?: TrainScheduleBooking[];
}

View File

@@ -0,0 +1,53 @@
import { BaseEntity } from '@edr/api-common';
import { BulkPricingUnit } from '@edr/types';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { Booking } from '../../bookings/entities/booking.entity';
import { CargoType } from '../../rule-engine/entities/cargo-type.entity';
import { WagonBookingAllocation } from './wagon-booking-allocation.entity';
export const BULK_PRICING_UNITS = [
BulkPricingUnit.PerWagon,
BulkPricingUnit.PerTon,
BulkPricingUnit.PerItem,
] as const;
@Entity({ schema: 'freight', name: 'wagon_allocation_bulk_loads' })
@Index(['bookingId'])
export class WagonAllocationBulkLoad extends BaseEntity {
@Column({ name: 'wagon_booking_allocation_id', type: 'uuid', unique: true })
wagonBookingAllocationId!: string;
@ManyToOne(() => WagonBookingAllocation, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'wagon_booking_allocation_id' })
allocation?: WagonBookingAllocation;
@Column({ name: 'booking_id', type: 'uuid' })
bookingId!: string;
@ManyToOne(() => Booking)
@JoinColumn({ name: 'booking_id' })
booking?: Booking;
@Column({ name: 'cargo_type_id', type: 'uuid', nullable: true })
cargoTypeId?: string | null;
@ManyToOne(() => CargoType, { nullable: true, onDelete: 'SET NULL' })
@JoinColumn({ name: 'cargo_type_id' })
cargoType?: CargoType | null;
@Column({ name: 'cargo_description', type: 'text', nullable: true })
cargoDescription?: string | null;
@Column({ name: 'pricing_unit', type: 'varchar', length: 20, default: BulkPricingUnit.PerTon })
pricingUnit!: string;
@Column({ name: 'quantity', type: 'numeric', precision: 12, scale: 3, default: 0 })
quantity!: number;
@Column({ name: 'weight_tons', type: 'numeric', precision: 10, scale: 3, default: 0 })
weightTons!: number;
@Column({ name: 'truck_plate_number', type: 'varchar', length: 32, nullable: true })
truckPlateNumber?: string | null;
}

View File

@@ -0,0 +1,54 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { BookingContainer } from '../../bookings/entities/booking-container.entity';
import { Container } from '../../container-management/entities/container.entity';
import { ContainerType } from '../../rule-engine/entities/container-type.entity';
import { WagonBookingAllocation } from './wagon-booking-allocation.entity';
@Entity({ schema: 'freight', name: 'wagon_allocation_container_items' })
@Index(['wagonBookingAllocationId'])
export class WagonAllocationContainerItem extends BaseEntity {
@Column({ name: 'wagon_booking_allocation_id', type: 'uuid' })
wagonBookingAllocationId!: string;
@ManyToOne(() => WagonBookingAllocation, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'wagon_booking_allocation_id' })
allocation?: WagonBookingAllocation;
@Column({ name: 'booking_container_id', type: 'uuid', nullable: true })
bookingContainerId?: string | null;
@ManyToOne(() => BookingContainer, { nullable: true, onDelete: 'SET NULL' })
@JoinColumn({ name: 'booking_container_id' })
bookingContainer?: BookingContainer | null;
@Column({ name: 'container_id', type: 'uuid', nullable: true })
containerId?: string | null;
@ManyToOne(() => Container, { nullable: true, onDelete: 'SET NULL' })
@JoinColumn({ name: 'container_id' })
container?: Container | null;
@Column({ name: 'container_number', type: 'varchar', length: 64, nullable: true })
containerNumber?: string | null;
@Column({ name: 'container_type_id', type: 'uuid', nullable: true })
containerTypeId?: string | null;
@ManyToOne(() => ContainerType, { nullable: true })
@JoinColumn({ name: 'container_type_id' })
containerType?: ContainerType | null;
@Column({ name: 'position_on_wagon', type: 'smallint', nullable: true })
positionOnWagon?: number | null;
@Column({ name: 'seal_number', type: 'varchar', length: 64, nullable: true })
sealNumber?: string | null;
@Column({ name: 'chassis_number', type: 'varchar', length: 64, nullable: true })
chassisNumber?: string | null;
@Column({ name: 'gross_weight_tons', type: 'numeric', precision: 10, scale: 3, nullable: true })
grossWeightTons?: number | null;
}

View File

@@ -1,8 +1,22 @@
import { BaseEntity } from '@edr/api-common';
import { Column, Entity, Index, JoinColumn, ManyToOne } from 'typeorm';
import { AllocationLoadType, AllocationStatus } from '@edr/types';
import { Column, Entity, Index, JoinColumn, ManyToOne, OneToMany } from 'typeorm';
import { Booking } from '../../bookings/entities/booking.entity';
import { TrainSetWagon } from '../../train-sets/entities/train-set-wagon.entity';
import { WagonAllocationContainerItem } from './wagon-allocation-container-item.entity';
export const ALLOCATION_LOAD_TYPES = [
AllocationLoadType.Container,
AllocationLoadType.Bulk,
] as const;
export const ALLOCATION_STATUSES = [
AllocationStatus.Planned,
AllocationStatus.Reserved,
AllocationStatus.Loaded,
AllocationStatus.Departed,
] as const;
@Entity({ schema: 'freight', name: 'wagon_booking_allocations' })
@Index(['trainSetWagonId', 'bookingId'])
@@ -23,4 +37,19 @@ export class WagonBookingAllocation extends BaseEntity {
@Column({ name: 'allocated_weight_tons', type: 'numeric', precision: 10, scale: 3 })
allocatedWeightTons!: number;
@Column({ name: 'load_type', type: 'varchar', length: 20, nullable: true })
loadType?: string | null;
@Column({ name: 'status', type: 'varchar', length: 20, default: 'PLANNED' })
status!: string;
@Column({ name: 'confirmed_at', type: 'timestamptz', nullable: true })
confirmedAt?: Date | null;
@Column({ name: 'confirmed_by_user_id', type: 'uuid', nullable: true })
confirmedByUserId?: string | null;
@OneToMany(() => WagonAllocationContainerItem, (item) => item.allocation)
containerItems?: WagonAllocationContainerItem[];
}

View File

@@ -1,7 +1,7 @@
import { BaseRepository } from '@edr/api-common';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DeepPartial, EntityManager, In, Repository } from 'typeorm';
import { TrainScheduleBooking } from './entities/train-schedule-booking.entity';
@@ -13,4 +13,38 @@ export class TrainScheduleBookingsRepository extends BaseRepository<TrainSchedul
) {
super(repository);
}
private repo(manager?: EntityManager) {
return manager ? manager.getRepository(TrainScheduleBooking) : this.repository;
}
async createMany(
records: DeepPartial<TrainScheduleBooking>[],
manager?: EntityManager,
): Promise<TrainScheduleBooking[]> {
if (!records.length) return [];
const repo = this.repo(manager);
return repo.save(repo.create(records));
}
async deleteByScheduleAndBooking(
trainScheduleId: string,
bookingId: string,
manager?: EntityManager,
): Promise<void> {
await this.repo(manager).delete({ trainScheduleId, bookingId });
}
async existsForBooking(bookingId: string, manager?: EntityManager): Promise<boolean> {
const count = await this.repo(manager).count({ where: { bookingId } });
return count > 0;
}
findByBookingIds(bookingIds: string[], manager?: EntityManager): Promise<TrainScheduleBooking[]> {
if (!bookingIds.length) return Promise.resolve([]);
return this.repo(manager).find({
where: { bookingId: In(bookingIds) },
select: { id: true, bookingId: true, trainScheduleId: true },
});
}
}

View File

@@ -3,22 +3,38 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { TrainScheduleBooking } from './entities/train-schedule-booking.entity';
import { TrainSchedule } from './entities/train-schedule.entity';
import { WagonAllocationBulkLoad } from './entities/wagon-allocation-bulk-load.entity';
import { WagonAllocationContainerItem } from './entities/wagon-allocation-container-item.entity';
import { WagonBookingAllocation } from './entities/wagon-booking-allocation.entity';
import { TrainScheduleBookingsRepository } from './train-schedule-bookings.repository';
import { TrainSchedulesRepository } from './train-schedules.repository';
import { WagonAllocationBulkLoadsRepository } from './wagon-allocation-bulk-loads.repository';
import { WagonAllocationContainerItemsRepository } from './wagon-allocation-container-items.repository';
import { WagonBookingAllocationsRepository } from './wagon-booking-allocations.repository';
@Module({
imports: [TypeOrmModule.forFeature([TrainSchedule, TrainScheduleBooking, WagonBookingAllocation])],
imports: [
TypeOrmModule.forFeature([
TrainSchedule,
TrainScheduleBooking,
WagonBookingAllocation,
WagonAllocationContainerItem,
WagonAllocationBulkLoad,
]),
],
providers: [
TrainSchedulesRepository,
TrainScheduleBookingsRepository,
WagonBookingAllocationsRepository,
WagonAllocationContainerItemsRepository,
WagonAllocationBulkLoadsRepository,
],
exports: [
TrainSchedulesRepository,
TrainScheduleBookingsRepository,
WagonBookingAllocationsRepository,
WagonAllocationContainerItemsRepository,
WagonAllocationBulkLoadsRepository,
],
})
export class TrainSchedulesModule {}

View File

@@ -1,9 +1,9 @@
import { BaseRepository } from '@edr/api-common';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { EntityManager, Repository } from 'typeorm';
import { TrainSchedule } from './entities/train-schedule.entity';
import { TrainSchedule, TrainScheduleStatus } from './entities/train-schedule.entity';
@Injectable()
export class TrainSchedulesRepository extends BaseRepository<TrainSchedule> {
@@ -13,4 +13,48 @@ export class TrainSchedulesRepository extends BaseRepository<TrainSchedule> {
) {
super(repository);
}
private repo(manager?: EntityManager) {
return manager ? manager.getRepository(TrainSchedule) : this.repository;
}
findByIdWithFullGraph(id: string, manager?: EntityManager): Promise<TrainSchedule | null> {
return this.repo(manager).findOne({
where: { id },
relations: {
route: true,
trainSet: {
locomotive: true,
wagons: {
wagonType: true,
physicalWagon: true,
allocations: {
booking: { company: true, bookingContainers: { containerType: true } },
containerItems: true,
},
},
},
originStation: true,
destinationStation: true,
scheduleBookings: {
booking: {
company: true,
originYard: true,
destinationYard: true,
bookingContainers: { containerType: true },
cargoType: true,
},
},
},
});
}
async updateStatus(
id: string,
status: TrainScheduleStatus,
extra?: Partial<TrainSchedule>,
manager?: EntityManager,
): Promise<void> {
await this.repo(manager).update(id, { status, ...extra } as never);
}
}

View File

@@ -0,0 +1,36 @@
import { BaseRepository } from '@edr/api-common';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DeepPartial, EntityManager, In, Repository } from 'typeorm';
import { WagonAllocationBulkLoad } from './entities/wagon-allocation-bulk-load.entity';
@Injectable()
export class WagonAllocationBulkLoadsRepository extends BaseRepository<WagonAllocationBulkLoad> {
constructor(
@InjectRepository(WagonAllocationBulkLoad)
repository: Repository<WagonAllocationBulkLoad>,
) {
super(repository);
}
private repo(manager?: EntityManager) {
return manager
? manager.getRepository(WagonAllocationBulkLoad)
: this.repository;
}
async createMany(
items: DeepPartial<WagonAllocationBulkLoad>[],
manager?: EntityManager,
): Promise<WagonAllocationBulkLoad[]> {
if (!items.length) return [];
const repo = this.repo(manager);
return repo.save(repo.create(items));
}
async deleteByAllocationIds(allocationIds: string[], manager?: EntityManager): Promise<void> {
if (!allocationIds.length) return;
await this.repo(manager).delete({ wagonBookingAllocationId: In(allocationIds) });
}
}

View File

@@ -0,0 +1,36 @@
import { BaseRepository } from '@edr/api-common';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { DeepPartial, EntityManager, In, Repository } from 'typeorm';
import { WagonAllocationContainerItem } from './entities/wagon-allocation-container-item.entity';
@Injectable()
export class WagonAllocationContainerItemsRepository extends BaseRepository<WagonAllocationContainerItem> {
constructor(
@InjectRepository(WagonAllocationContainerItem)
repository: Repository<WagonAllocationContainerItem>,
) {
super(repository);
}
private repo(manager?: EntityManager) {
return manager
? manager.getRepository(WagonAllocationContainerItem)
: this.repository;
}
async createMany(
items: DeepPartial<WagonAllocationContainerItem>[],
manager?: EntityManager,
): Promise<WagonAllocationContainerItem[]> {
if (!items.length) return [];
const repo = this.repo(manager);
return repo.save(repo.create(items));
}
async deleteByAllocationIds(allocationIds: string[], manager?: EntityManager): Promise<void> {
if (!allocationIds.length) return;
await this.repo(manager).delete({ wagonBookingAllocationId: In(allocationIds) });
}
}

View File

@@ -1,7 +1,7 @@
import { BaseRepository } from '@edr/api-common';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DeepPartial, EntityManager, Repository } from 'typeorm';
import { WagonBookingAllocation } from './entities/wagon-booking-allocation.entity';
@@ -13,4 +13,43 @@ export class WagonBookingAllocationsRepository extends BaseRepository<WagonBooki
) {
super(repository);
}
private repo(manager?: EntityManager) {
return manager ? manager.getRepository(WagonBookingAllocation) : this.repository;
}
async createMany(
records: DeepPartial<WagonBookingAllocation>[],
manager?: EntityManager,
): Promise<WagonBookingAllocation[]> {
if (!records.length) return [];
const repo = this.repo(manager);
return repo.save(repo.create(records));
}
findByScheduleId(trainScheduleId: string, manager?: EntityManager): Promise<WagonBookingAllocation[]> {
return this.repo(manager)
.createQueryBuilder('allocation')
.innerJoin('allocation.trainSetWagon', 'wagon')
.innerJoin('wagon.trainSet', 'trainSet')
.innerJoin('trainSet.trainSchedule', 'schedule')
.where('schedule.id = :trainScheduleId', { trainScheduleId })
.leftJoinAndSelect('allocation.booking', 'booking')
.getMany();
}
async deleteByTrainSetId(trainSetId: string, manager?: EntityManager): Promise<string[]> {
const allocations = await this.repo(manager)
.createQueryBuilder('allocation')
.innerJoin('allocation.trainSetWagon', 'wagon')
.where('wagon.train_set_id = :trainSetId', { trainSetId })
.select(['allocation.id'])
.getMany();
const ids = allocations.map((a) => a.id);
if (ids.length) {
await this.repo(manager).delete(ids);
}
return ids;
}
}