fix issue and add consolidation

This commit is contained in:
Marshal
2026-08-21 23:16:06 +00:00
parent a339ea620e
commit 09deecd04c
21 changed files with 1464 additions and 264 deletions

View File

@@ -0,0 +1,104 @@
import { Booking } from '../bookings/entities/booking.entity';
import { BookingBatchService } from './booking-batch.service';
import { WagonStockLedger } from './wagon-stock-ledger.util';
/**
* smartBulkNeed math in isolation: the private helpers it touches
* (allowedDimsWithTypes) read only their arguments, so a bare prototype
* instance is enough — no Nest wiring.
*/
describe('BookingBatchService.smartBulkNeed', () => {
const service = Object.create(BookingBatchService.prototype) as BookingBatchService;
const call = (
booking: Booking,
stock: WagonStockLedger,
rank: Map<string, number>,
) =>
(
service as unknown as {
smartBulkNeed: (
b: Booking,
d: unknown,
s: WagonStockLedger,
l: { fromEdge: number; toEdge: number },
r: Map<string, number>,
) => { need: { wagons: number }; perType: Array<{ wagonTypeId: string; wagons: number }> } | null;
}
).smartBulkNeed(booking, wagonDims, stock, { fromEdge: 0, toEdge: 1 }, rank);
const nw5 = { id: 'wt-nw5', capacityTons: 70 };
const pw2 = { id: 'wt-pw2', capacityTons: 70 };
const perishable = {
id: 'cargo-perishable',
wagonTypes: [nw5, pw2],
tonsPerWagonMap: { [nw5.id]: 30, [pw2.id]: 20 },
};
const wagonDims = {
container: { lengthMeters: 14, tareWeightTons: 24, capacityTons: 70 },
bulk: { lengthMeters: 14, tareWeightTons: 24, capacityTons: 70 },
byWagonTypeId: new Map([
[nw5.id, { lengthMeters: 14, tareWeightTons: 24, capacityTons: 70 }],
[pw2.id, { lengthMeters: 14, tareWeightTons: 24, capacityTons: 70 }],
]),
};
const booking = (tons: number): Booking =>
({
id: 'b1',
reference: 'b1',
freightType: 'BULK',
cargoTotalWeightVgm: tons,
cargoTypeId: perishable.id,
cargoType: perishable,
bookingContainers: [],
}) as unknown as Booking;
// Containers compete for NW5 → NW5 rank 2, PW2 rank 1.
const contested = new Map([
[nw5.id, 2],
[pw2.id, 1],
]);
it('seats 695T as 10 PW2 (20T) + 17 NW5 (30T) = 27 wagons, PW2 first', () => {
const stock = new WagonStockLedger(
new Map([
[nw5.id, 18],
[pw2.id, 10],
]),
1,
);
const smart = call(booking(695), stock, contested);
expect(smart).not.toBeNull();
expect(smart!.need.wagons).toBe(27);
expect(smart!.perType).toEqual([
{ wagonTypeId: pw2.id, wagons: 10 },
{ wagonTypeId: nw5.id, wagons: 17 },
]);
});
it('returns null when the free stock cannot seat the whole booking', () => {
const stock = new WagonStockLedger(
new Map([
[nw5.id, 5],
[pw2.id, 10],
]),
1,
);
// 10×20 + 5×30 = 350T < 695T.
expect(call(booking(695), stock, contested)).toBeNull();
});
it('uncontested types fall back to biggest per-cargo take (fewest wagons)', () => {
const stock = new WagonStockLedger(
new Map([
[nw5.id, 10],
[pw2.id, 10],
]),
1,
);
const even = new Map([
[nw5.id, 1],
[pw2.id, 1],
]);
const smart = call(booking(60), stock, even);
expect(smart!.perType).toEqual([{ wagonTypeId: nw5.id, wagons: 2 }]);
});
});