mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-07 17:45:42 +00:00
feat(train-scheduling): implement container movement between wagons
- Added functionality to move containers between wagons in the train scheduling system. - Introduced API endpoint and service method to handle container movement. - Updated component to support drag-and-drop for rearranging containers. - Enhanced to allow moving containers to other wagons via a context menu. - Implemented UI feedback for container movement actions, including loading states and success/error notifications. - Updated relevant types and constants to accommodate new container movement logic. - Added tests for the rule engine to ensure proper handling of hazardous bookings.
This commit is contained in:
@@ -5,6 +5,7 @@ import { Wagon } from '../wagons/entities/wagon.entity';
|
||||
import { WagonType } from '../wagon-types/entities/wagon-type.entity';
|
||||
import { TrainSetWagon } from '../train-sets/entities/train-set-wagon.entity';
|
||||
import { TrainSchedulingGlobalRules } from './entities/train-scheduling-global-rules.entity';
|
||||
import { WagonAllocationContainerItem } from '../train-schedules/entities/wagon-allocation-container-item.entity';
|
||||
import { WagonBookingAllocation } from '../train-schedules/entities/wagon-booking-allocation.entity';
|
||||
import { TrainSchedulingService } from './train-scheduling.service';
|
||||
|
||||
@@ -1081,4 +1082,143 @@ describe('TrainSchedulingService', () => {
|
||||
expect(html).not.toContain('EMPTY');
|
||||
});
|
||||
});
|
||||
|
||||
describe('moveContainerItem — staff rearrange', () => {
|
||||
const wagon1 = { id: 'w1', sequenceNo: 1, capacityTons: 61 };
|
||||
const wagon2 = { id: 'w2', sequenceNo: 2, capacityTons: 61 };
|
||||
const schedule = {
|
||||
id: 'sched-1',
|
||||
status: 'SCHEDULED',
|
||||
trainSet: { wagons: [wagon1, wagon2] },
|
||||
};
|
||||
|
||||
let sourceAlloc: Record<string, unknown>;
|
||||
let item: Record<string, unknown>;
|
||||
let itemRepo: { findOne: jest.Mock; update: jest.Mock; count: jest.Mock };
|
||||
let allocRepo: {
|
||||
find: jest.Mock;
|
||||
findOne: jest.Mock;
|
||||
create: jest.Mock;
|
||||
save: jest.Mock;
|
||||
update: jest.Mock;
|
||||
delete: jest.Mock;
|
||||
};
|
||||
let wagon2Allocs: Array<Record<string, unknown>>;
|
||||
|
||||
beforeEach(() => {
|
||||
sourceAlloc = {
|
||||
id: 'alloc-1',
|
||||
trainSetWagonId: 'w1',
|
||||
bookingId: 'b1',
|
||||
allocatedWeightTons: 20,
|
||||
loadType: 'CONTAINER',
|
||||
status: 'PLANNED',
|
||||
containerItems: [],
|
||||
};
|
||||
item = {
|
||||
id: 'item-1',
|
||||
wagonBookingAllocationId: 'alloc-1',
|
||||
positionOnWagon: 1,
|
||||
grossWeightTons: 20,
|
||||
containerType: { sizeFt: 20 },
|
||||
allocation: sourceAlloc,
|
||||
};
|
||||
sourceAlloc.containerItems = [item];
|
||||
wagon2Allocs = [];
|
||||
|
||||
trainSchedulesRepository.findByIdWithFullGraph.mockResolvedValue(schedule);
|
||||
itemRepo = {
|
||||
findOne: jest.fn().mockResolvedValue(item),
|
||||
update: jest.fn().mockResolvedValue(undefined),
|
||||
count: jest.fn().mockResolvedValue(0),
|
||||
};
|
||||
allocRepo = {
|
||||
find: jest.fn().mockImplementation(({ where }: { where: { trainSetWagonId: string } }) =>
|
||||
Promise.resolve(where.trainSetWagonId === 'w1' ? [sourceAlloc] : wagon2Allocs),
|
||||
),
|
||||
findOne: jest.fn().mockImplementation(({ where }: { where: { id?: string } }) =>
|
||||
Promise.resolve(where.id === 'alloc-1' ? { ...sourceAlloc } : null),
|
||||
),
|
||||
create: jest.fn((v: unknown) => v),
|
||||
save: jest.fn().mockImplementation((v: Record<string, unknown>) =>
|
||||
Promise.resolve({ ...v, id: 'alloc-new' }),
|
||||
),
|
||||
update: jest.fn().mockResolvedValue(undefined),
|
||||
delete: jest.fn().mockResolvedValue(undefined),
|
||||
};
|
||||
dataSource.getRepository.mockImplementation((entity: unknown) => {
|
||||
if (entity === WagonAllocationContainerItem) return itemRepo;
|
||||
if (entity === WagonBookingAllocation) return allocRepo;
|
||||
return { find: jest.fn().mockResolvedValue([]) };
|
||||
});
|
||||
dataSource.transaction.mockImplementation(
|
||||
async (fn: (m: unknown) => Promise<void>) =>
|
||||
fn({ getRepository: dataSource.getRepository }),
|
||||
);
|
||||
jest
|
||||
.spyOn(
|
||||
service as never as { getTrainScheduleById: (id: string) => Promise<unknown> },
|
||||
'getTrainScheduleById' as never,
|
||||
)
|
||||
.mockResolvedValue({ id: 'sched-1' } as never);
|
||||
});
|
||||
|
||||
it('rejects moves on a dispatched train', async () => {
|
||||
trainSchedulesRepository.findByIdWithFullGraph.mockResolvedValue({
|
||||
...schedule,
|
||||
status: 'DISPATCHED',
|
||||
});
|
||||
await expect(
|
||||
service.moveContainerItem('sched-1', 'item-1', { targetTrainSetWagonId: 'w2' }),
|
||||
).rejects.toThrow(BadRequestException);
|
||||
expect(dataSource.transaction).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects a target wagon that has no TEU room left', async () => {
|
||||
wagon2Allocs = [
|
||||
{
|
||||
id: 'alloc-2',
|
||||
trainSetWagonId: 'w2',
|
||||
bookingId: 'b2',
|
||||
allocatedWeightTons: 25,
|
||||
loadType: 'CONTAINER',
|
||||
containerItems: [{ id: 'item-40', containerType: { sizeFt: 40 } }],
|
||||
},
|
||||
];
|
||||
await expect(
|
||||
service.moveContainerItem('sched-1', 'item-1', { targetTrainSetWagonId: 'w2' }),
|
||||
).rejects.toThrow(/no room/);
|
||||
});
|
||||
|
||||
it('rejects a bulk-loaded target wagon', async () => {
|
||||
wagon2Allocs = [
|
||||
{
|
||||
id: 'alloc-2',
|
||||
trainSetWagonId: 'w2',
|
||||
bookingId: 'b2',
|
||||
allocatedWeightTons: 40,
|
||||
loadType: 'BULK',
|
||||
containerItems: [],
|
||||
},
|
||||
];
|
||||
await expect(
|
||||
service.moveContainerItem('sched-1', 'item-1', { targetTrainSetWagonId: 'w2' }),
|
||||
).rejects.toThrow(/bulk/);
|
||||
});
|
||||
|
||||
it('moves a container to an empty wagon and re-homes its allocation', async () => {
|
||||
await service.moveContainerItem('sched-1', 'item-1', { targetTrainSetWagonId: 'w2' });
|
||||
|
||||
// A new allocation for the booking was created on the target wagon…
|
||||
expect(allocRepo.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ trainSetWagonId: 'w2', bookingId: 'b1' }),
|
||||
);
|
||||
// …the container item now hangs off it…
|
||||
expect(itemRepo.update).toHaveBeenCalledWith('item-1', {
|
||||
wagonBookingAllocationId: 'alloc-new',
|
||||
});
|
||||
// …and the emptied source allocation was deleted, not left at 0 items.
|
||||
expect(allocRepo.delete).toHaveBeenCalledWith('alloc-1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user