fix conflict

This commit is contained in:
yaschalew
2026-06-26 12:47:20 +03:00
72 changed files with 2060 additions and 444 deletions

View File

@@ -0,0 +1,24 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
/**
* Fix for fresh deployments: AddWarehouseInspection1750000000003 runs before
* the warehouse_inventory table exists, so it cannot add inspection_status.
*/
export class AddWarehouseInventoryInspectionStatusFix1791000000005 implements MigrationInterface {
private readonly table = 'freight.warehouse_inventory';
public async up(queryRunner: QueryRunner): Promise<void> {
if ((await queryRunner.hasTable(this.table)) && !(await queryRunner.hasColumn(this.table, 'inspection_status'))) {
await queryRunner.addColumn(
this.table,
new TableColumn({ name: 'inspection_status', type: 'varchar', length: '20', isNullable: true }),
);
}
}
public async down(queryRunner: QueryRunner): Promise<void> {
if ((await queryRunner.hasTable(this.table)) && (await queryRunner.hasColumn(this.table, 'inspection_status'))) {
await queryRunner.dropColumn(this.table, 'inspection_status');
}
}
}

View File

@@ -1,4 +1,4 @@
import { Module } from '@nestjs/common';
import { Module, forwardRef } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { BookingsModule } from '../bookings/bookings.module';
@@ -13,7 +13,7 @@ import { LastMileService } from './last-mile.service';
@Module({
imports: [
TypeOrmModule.forFeature([LastMile]),
BookingsModule,
forwardRef(() => BookingsModule),
VehiclesModule,
DriversModule,
NotificationsModule,

View File

@@ -15,6 +15,7 @@ import { TrainSchedulesModule } from '../train-schedules/train-schedules.module'
import { WagonType } from '../wagon-types/entities/wagon-type.entity';
import { WagonTypesModule } from '../wagon-types/wagon-types.module';
import { Wagon } from '../wagons/entities/wagon.entity';
import { WarehousesModule } from '../warehouses/warehouses.module';
import { TrainCheckpointEvent } from './entities/train-checkpoint-event.entity';
import { TrainSchedulingGlobalRules } from './entities/train-scheduling-global-rules.entity';
import { TrainCheckpointEventsRepository } from './train-checkpoint-events.repository';
@@ -44,6 +45,7 @@ import { NotificationsModule } from '../notifications/notifications.module';
WagonTypesModule,
TrainSetsModule,
TrainSchedulesModule,
forwardRef(() => WarehousesModule),
RuleEngineModule,
],
controllers: [TrainSchedulingController],

View File

@@ -147,6 +147,10 @@ describe('TrainSchedulingService', () => {
wagonAllocationBulkLoadsRepository as never,
trainCheckpointEventsRepository as never,
{} as never, // trainCompositionRemovalLogRepository
{
autoUnloadArrivedBookings: jest.fn(),
autoUnloadExportAtDjibouti: jest.fn(),
} as never,
);
const defaultFleetWagons = [

View File

@@ -95,6 +95,8 @@ import { TrainCheckpointEvent } from './entities/train-checkpoint-event.entity';
import { TrainCheckpointEventsRepository } from './train-checkpoint-events.repository';
import { RecordCheckpointDto } from './dto/record-checkpoint.dto';
import { RouteMilestone } from '../routes/entities/route-milestone.entity';
import { deriveTradeDirection } from '../../common/derive-trade-direction.util';
import { WarehouseInventoryService } from '../warehouses/warehouse-inventory.service';
import {
autoFillPlacements,
findMissingContainerNumberIssues,
@@ -167,6 +169,7 @@ export class TrainSchedulingService {
private readonly wagonAllocationBulkLoadsRepository: WagonAllocationBulkLoadsRepository,
private readonly trainCheckpointEventsRepository: TrainCheckpointEventsRepository,
private readonly trainCompositionRemovalLogRepository: TrainCompositionRemovalLogRepository,
private readonly warehouseInventoryService: WarehouseInventoryService,
private readonly configService?: ConfigService,
) {}
@@ -602,6 +605,74 @@ export class TrainSchedulingService {
return this.getTrainScheduleById(scheduleId);
}
private async runWarehouseArrivalAutomation(scheduleId: string) {
const [schedule]: Array<{
originCountry: string | null;
destinationCountry: string | null;
destinationCode: string | null;
destinationName: string | null;
}> = await this.dataSource.query(
`SELECT oy.country AS "originCountry",
dy.country AS "destinationCountry",
dy.code AS "destinationCode",
dy.name AS "destinationName"
FROM freight.train_schedules ts
LEFT JOIN freight.yards oy ON oy.id = ts.origin_station_id
LEFT JOIN freight.yards dy ON dy.id = ts.destination_station_id
WHERE ts.id = $1 AND ts.deleted_at IS NULL
LIMIT 1`,
[scheduleId],
);
if (!schedule) return { status: 'SKIPPED', reason: 'Train schedule not found' };
const direction = deriveTradeDirection(
{ country: schedule.originCountry },
{ country: schedule.destinationCountry },
);
try {
if (direction === 'IMPORT') {
return {
direction,
action: 'IMPORT_AUTO_UNLOAD',
status: 'COMPLETED',
result: await this.warehouseInventoryService.autoUnloadArrivedBookings(
scheduleId,
'SYSTEM_TRAIN_ARRIVAL',
),
};
}
if (direction === 'EXPORT' && this.isDjiboutiPortDestination(`${schedule.destinationCode ?? ''} ${schedule.destinationName ?? ''}`)) {
return {
direction,
action: 'EXPORT_DJIBOUTI_AUTO_UNLOAD',
status: 'COMPLETED',
result: await this.warehouseInventoryService.autoUnloadExportAtDjibouti(
scheduleId,
'SYSTEM_TRAIN_ARRIVAL',
),
};
}
return { direction, status: 'SKIPPED', reason: 'No warehouse arrival automation for this route' };
} catch (error) {
return {
direction,
status: 'FAILED',
reason: error instanceof Error ? error.message : String(error),
};
}
}
private isDjiboutiPortDestination(value: string | null | undefined): boolean {
const normalized = (value ?? '').toUpperCase();
return ['DJIBOUTI', 'DORALEH', 'DMP', 'DCT', 'NAGAD'].some((token) =>
normalized.includes(token),
);
}
async pinWagons(scheduleId: string, dto: PinWagonsDto) {
const schedule = await this.trainSchedulesRepository.findByIdWithFullGraph(scheduleId);
if (!schedule) {
@@ -653,7 +724,9 @@ export class TrainSchedulingService {
}
});
return this.getTrainScheduleById(scheduleId);
const detail = await this.getTrainScheduleById(scheduleId);
const warehouseAutomation = await this.runWarehouseArrivalAutomation(scheduleId);
return Object.assign(detail, { warehouseAutomation });
}
async finalizeSchedule(scheduleId: string) {

View File

@@ -1,4 +1,4 @@
import { Module } from '@nestjs/common';
import { Module, forwardRef } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ExchangeModule, ExchangeOptions } from '@edr/api-common';
import { TypeOrmModule } from '@nestjs/typeorm';
@@ -70,7 +70,7 @@ import { WarehousesService } from './warehouses.service';
]),
FilesModule,
InterchangeDocumentsModule,
LastMileModule,
forwardRef(() => LastMileModule),
ExchangeModule.forRootAsync({
inject: [ConfigService],
useFactory: (config: ConfigService): ExchangeOptions =>