mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 09:00:57 +00:00
109 lines
3.5 KiB
TypeScript
109 lines
3.5 KiB
TypeScript
import { BookingBatchService } from './booking-batch.service';
|
||
import { Booking } from '../bookings/entities/booking.entity';
|
||
|
||
/**
|
||
* The intercity ride-along board shows WHICH wagon types a booking takes from
|
||
* the train it is being placed on ("3 × N35 + 1 × PW2"), not just how many
|
||
* wagons. Because the split is drawn against that schedule's own stock, the
|
||
* same booking must read differently on a different train.
|
||
*/
|
||
describe('BookingBatchService — intercity wagon breakdown', () => {
|
||
const N35 = 'wagon-type-n35';
|
||
const PW2 = 'wagon-type-pw2';
|
||
|
||
const dims = (capacityTons: number) => ({
|
||
capacityTons,
|
||
lengthMeters: 14,
|
||
tareWeightTons: 20,
|
||
});
|
||
|
||
const wagonDims = {
|
||
bulk: dims(60),
|
||
container: dims(60),
|
||
byWagonTypeId: new Map([
|
||
[N35, dims(60)],
|
||
[PW2, dims(20)],
|
||
]),
|
||
};
|
||
|
||
const codes = new Map([
|
||
[N35, 'N35'],
|
||
[PW2, 'PW2'],
|
||
]);
|
||
|
||
/**
|
||
* 400 break-bulk items weighing 800t — 2t per item. On a 60t N35 that is 30
|
||
* items per wagon (14 wagons); on a 20t PW2, 10 items (40 wagons).
|
||
*/
|
||
const perItemBooking = {
|
||
id: 'booking-1',
|
||
freightType: 'BULK',
|
||
cargoTotalWeightVgm: 400,
|
||
bulkTotalWeightTons: 800,
|
||
bookingContainers: [],
|
||
cargoType: {
|
||
wagonTypes: [{ id: N35 }, { id: PW2 }],
|
||
itemsPerWagonMap: {},
|
||
},
|
||
} as unknown as Booking;
|
||
|
||
const service = Object.create(
|
||
BookingBatchService.prototype,
|
||
) as BookingBatchService;
|
||
|
||
const breakdown = (
|
||
booking: Booking,
|
||
stock: Map<string, number>,
|
||
): Array<{ code: string; wagons: number }> =>
|
||
(
|
||
service as unknown as {
|
||
wagonBreakdownFor: (
|
||
b: Booking,
|
||
d: typeof wagonDims,
|
||
s: Map<string, number>,
|
||
c: Map<string, string>,
|
||
) => Array<{ code: string; wagons: number }>;
|
||
}
|
||
)
|
||
.wagonBreakdownFor(booking, wagonDims, stock, codes)
|
||
.map(({ code, wagons }) => ({ code, wagons }));
|
||
|
||
it('takes the highest-capacity type first when the train stocks plenty', () => {
|
||
const rows = breakdown(perItemBooking, new Map([[N35, 50], [PW2, 50]]));
|
||
expect(rows).toEqual([{ code: 'N35', wagons: 14 }]);
|
||
});
|
||
|
||
it('falls back to the smaller type for the remainder when the big one runs short', () => {
|
||
// Only 10 of the 14 N35s the booking wants — the rest rides PW2s. Ten N35s
|
||
// carry 10/14 of the booking, leaving 4/14, which needs ceil(40 × 4/14) PW2s.
|
||
const rows = breakdown(perItemBooking, new Map([[N35, 10], [PW2, 50]]));
|
||
expect(rows[0]).toEqual({ code: 'N35', wagons: 10 });
|
||
expect(rows[1].code).toBe('PW2');
|
||
expect(rows[1].wagons).toBeGreaterThan(0);
|
||
});
|
||
|
||
it('reads differently on a train that stocks only the small type', () => {
|
||
const rows = breakdown(perItemBooking, new Map([[PW2, 60]]));
|
||
expect(rows).toEqual([{ code: 'PW2', wagons: 40 }]);
|
||
});
|
||
|
||
it('honours the configured items-per-wagon fit over raw tonnage', () => {
|
||
// Floor space binds before weight: an N35 physically holds 20 of these
|
||
// items even though 30 would fit by weight → 20 wagons, not 14.
|
||
const floorBound = {
|
||
...perItemBooking,
|
||
cargoType: {
|
||
wagonTypes: [{ id: N35 }],
|
||
itemsPerWagonMap: { [N35]: 20 },
|
||
},
|
||
} as unknown as Booking;
|
||
expect(breakdown(floorBound, new Map([[N35, 50]]))).toEqual([
|
||
{ code: 'N35', wagons: 20 },
|
||
]);
|
||
});
|
||
|
||
it('returns nothing when the train stocks none of the allowed types', () => {
|
||
expect(breakdown(perItemBooking, new Map([['other-type', 30]]))).toEqual([]);
|
||
});
|
||
});
|