/** * BULK B2 — break-bulk PER_ITEM wagon math on the CW4 fleet (70 T capacity). * * Cargo from seed-bulk-items.sql: * E2E_IMP_AUTO automobiles — items-per-wagon floor of 4 * E2E_IMP_MACHINE machinery — no floor, tonnage-only fallback * * Three verdicts of the wagon-demand rule, end to end: * BA1 16 autos @2.5 T (40 T) → the floor binds: 4 wagons (tonnage said 1) * BA2 12 machines @20 T (240 T) → tonnage binds: 3/wagon → 4 wagons * BA3 216 autos (540 T) → exactly 54 wagons: FULL from one booking, no split */ import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { closeDb, gateway } from "./client"; import { bookBulkItemsReady, bookingRow, closeBookingWindow, completeDocReview, createSchedule, departureAt, eatDayStr, endPaymentPhase, ensureCorridorRoute, extendPayWindow, expectWagonType, forceWindowOpen, payViaGateway, pollAllocations, pollBookingStatus, pollWindow, releaseUnpaidHolds, resetCorridorDay, seedTenantContracts, } from "./flows"; const DEPARTURE = departureAt(41); const BOOKING_DAY = eatDayStr(DEPARTURE); const FULL_DEPARTURE = departureAt(42); const FULL_DAY = eatDayStr(FULL_DEPARTURE); const STAMP = String(Date.now()); describe("bulk b2: PER_ITEM floor vs tonnage wagon math", () => { const booking = new Map(); let contracts: Map; let scheduleId: string; beforeAll(async () => { await gateway.reset(); await releaseUnpaidHolds(); await ensureCorridorRoute(); await resetCorridorDay(DEPARTURE); await resetCorridorDay(FULL_DEPARTURE); contracts = await seedTenantContracts( STAMP, ["BA1", "BA2", "BA3"].map((suffix) => ({ suffix, freight: "BULK" as const })), ); scheduleId = ( await createSchedule({ departure: DEPARTURE, kind: "bulk", locoPair: ["LOCO-IMP-15", "LOCO-IMP-16"], }) ).id; await forceWindowOpen(scheduleId, 60); booking.set( "BA1", await bookBulkItemsReady({ contractId: contracts.get("BA1")!, cargoCode: "E2E_IMP_AUTO", items: 16, tons: 40, scheduledDate: BOOKING_DAY, }), ); booking.set( "BA2", await bookBulkItemsReady({ contractId: contracts.get("BA2")!, cargoCode: "E2E_IMP_MACHINE", items: 12, tons: 240, scheduledDate: BOOKING_DAY, }), ); }, 1_800_000); afterAll(closeDb); it("the 4-per-wagon floor binds for 16 autos → 4 CW4 wagons, not 1", async () => { await closeBookingWindow(scheduleId); await completeDocReview(scheduleId); for (const suffix of ["BA1", "BA2"]) { await pollBookingStatus(booking.get(suffix)!, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]); } await extendPayWindow(scheduleId, [booking.get("BA1")!, booking.get("BA2")!]); await payViaGateway(booking.get("BA1")!); await pollAllocations(booking.get("BA1")!, 4); await expectWagonType(booking.get("BA1")!, "CW4", 4); }); it("machinery has no floor — 12 items @20 T take 4 wagons on tonnage alone", async () => { await payViaGateway(booking.get("BA2")!); await pollAllocations(booking.get("BA2")!, 4); await expectWagonType(booking.get("BA2")!, "CW4", 4); }); it("216 autos = exactly 54 wagons: FULL from one break-bulk booking, no split", async () => { const fullSchedule = await createSchedule({ departure: FULL_DEPARTURE, kind: "bulk", locoPair: ["LOCO-IMP-25", "LOCO-IMP-26"], }); await forceWindowOpen(fullSchedule.id, 45); const ba3 = await bookBulkItemsReady({ contractId: contracts.get("BA3")!, cargoCode: "E2E_IMP_AUTO", items: 216, tons: 540, scheduledDate: FULL_DAY, }); await closeBookingWindow(fullSchedule.id); await completeDocReview(fullSchedule.id); await pollBookingStatus(ba3, ["SELECTED_FOR_BATCH", "AWAITING_PAYMENT"]); await payViaGateway(ba3); await pollAllocations(ba3, 54); expect((await bookingRow(ba3)).is_split, "BA3 rides whole, not split").not.toBe(true); await endPaymentPhase(fullSchedule.id); await pollWindow( fullSchedule.id, (s) => s.booking_window_status === "FULL" && s.window_phase === "DONE", "FULL + DONE", ); }, 900_000); });