Merge pull request #1383 from Tria-plc/freight_feature/usermanagement

Freight feature/usermanagement
This commit is contained in:
marshal
2026-08-22 02:33:08 +03:00
committed by GitHub
3 changed files with 1492 additions and 829 deletions

View File

@@ -1536,4 +1536,72 @@ describe('BookingBatchService — physical wagon-type gate', () => {
// Those 16 are now held, so the next booking in the pass cannot re-take them.
expect(stock.availableFor([NW5], WHOLE_LEG)).toBe(0);
});
it('sizes a capped-bulk partial on ONE type at the cargo cap, not the 70T rating', async () => {
const svc = service();
const inner = internals(svc);
(inner as { isSplitEligible: unknown }).isSplitEligible = () => true;
const dims = { lengthMeters: 14, tareWeightTons: 24, capacityTons: 70 };
(inner as unknown as { loadWagonDims: unknown }).loadWagonDims = async () => ({
container: dims,
bulk: dims,
byWagonTypeId: new Map([
[NW5, dims],
[PW2, dims],
]),
});
const tryPartial = jest
.fn()
.mockResolvedValue({ wagons: 16, weightTons: 864, lengthMeters: 224 });
(inner as { tryPartialOffer: unknown }).tryPartialOffer = tryPartial;
const stock = mixedStock();
const candidate = {
id: 'schedule-1',
budget: {
legOf: () => WHOLE_LEG,
remainingFor: () => ({ wagons: 20, weightTons: 99_999, lengthMeters: 99_999 }),
subtract: jest.fn(),
},
armed: false,
stock,
};
const booking = {
id: 'b2',
reference: 'BK-2',
originYardId: 'a',
destinationYardId: 'b',
freightType: 'BULK',
cargoTotalWeightVgm: 695,
cargoType: {
id: 'cargo-perishable',
wagonTypes: [
{ id: NW5, capacityTons: 70 },
{ id: PW2, capacityTons: 70 },
],
tonsPerWagonMap: { [NW5]: 30, [PW2]: 20 },
},
bookingContainers: [],
} as unknown as Booking;
const offered = await inner.maybeOfferPartial(
booking,
false,
[candidate],
{ wagons: 24, weightTons: 1400, lengthMeters: 336 },
[NW5, PW2],
);
expect(offered).toBe(true);
// Room capped to the 16 NW5 that exist (biggest capped take), and the seat
// carries the 30T cargo cap — never the wagon's raw 70T rating.
expect(tryPartial.mock.calls[0][2]).toMatchObject({ wagons: 16 });
expect(tryPartial.mock.calls[0][4]).toMatchObject({
wagonTypeId: NW5,
perWagon: { capacityTons: 30 },
});
// Only the seated type is held; the PW2s stay free for bulk-only cargo.
expect(stock.availableFor([NW5], WHOLE_LEG)).toBe(0);
expect(stock.availableFor([PW2], WHOLE_LEG)).toBe(4);
});
});

View File

@@ -2831,7 +2831,6 @@ export class BookingBatchService implements OnModuleInit {
wagonTypeIds: string[] = [],
): Promise<boolean> {
if (!this.isSplitEligible(booking, isPair)) return false;
const wagonDims = await this.loadWagonDims();
// PER_TON bulk partials are sized on ONE concrete wagon type at the
// cargo's per-wagon cap — sizing on the first type's raw 70T rating
// offered tonnage the wagons could never carry (Perishable caps at
@@ -2846,6 +2845,7 @@ export class BookingBatchService implements OnModuleInit {
booking.freightType === "BULK" &&
!perItemBulk &&
wagonTypeIds.length > 0;
const wagonDims = cappedBulk ? await this.loadWagonDims() : null;
const target = candidates
.map((c) => {
const leg = c.budget.legOf(booking.originYardId, booking.destinationYardId);
@@ -2856,7 +2856,7 @@ export class BookingBatchService implements OnModuleInit {
// them NW5" into an offer for 16 — the customer pays for 16 and the
// other 4 leave as the usual remainder booking, instead of paying for
// 20 and stalling at allocation on wagon 17.
if (cappedBulk) {
if (cappedBulk && wagonDims) {
const best = this.allowedDimsWithTypes(booking, wagonDims)
.filter((o): o is { wagonTypeId: string; dims: PerWagonDims } =>
o.wagonTypeId != null,

2249
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff