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, ): Array<{ code: string; wagons: number }> => ( service as unknown as { wagonBreakdownFor: ( b: Booking, d: typeof wagonDims, s: Map, c: Map, ) => 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([]); }); });