add lashing surcharge for cargo types with hasLashing flag

add lashing surcharge for cargo types with hasLashing flag
This commit is contained in:
Marshal
2026-07-17 23:25:53 +00:00
parent 6467173c76
commit 3a1a08b1e1
59 changed files with 1919 additions and 140 deletions

View File

@@ -6,6 +6,8 @@ import {
listConfigBookingWindows,
groupBookingsIntoBoardWindows,
computeImportWindowTimes,
computeExportWindowTimes,
bookingCloseCutoff,
type BoardWindowConfig,
} from './batch-window.util';
@@ -360,3 +362,101 @@ describe('computeImportWindowTimes — immediate open inside the window day', ()
expect(t.windowOpensAt.getTime()).toBe(now.getTime());
});
});
// Booking-close offset: a configured offset pulls the window close earlier than
// departure by that many minutes, separately for import and export.
describe('bookingCloseCutoff — departure offset', () => {
const departure = new Date('2026-07-10T13:00:00.000Z'); // 16:00 EAT Jul 10
it('returns departure unchanged when no offset is set', () => {
expect(bookingCloseCutoff(departure, 'IMPORT', {}).toISOString()).toBe(
departure.toISOString(),
);
expect(
bookingCloseCutoff(departure, 'EXPORT', {
importCloseOffsetMinutes: 180,
}).toISOString(),
).toBe(departure.toISOString());
});
it('a non-positive offset is treated as no offset', () => {
expect(
bookingCloseCutoff(departure, 'IMPORT', {
importCloseOffsetMinutes: 0,
}).toISOString(),
).toBe(departure.toISOString());
expect(
bookingCloseCutoff(departure, 'IMPORT', {
importCloseOffsetMinutes: -5,
}).toISOString(),
).toBe(departure.toISOString());
});
it('import 3-hour offset: 16:00 EAT departure → cutoff 13:00 EAT (14:00 → 3h before)', () => {
// Departure 16:00 EAT (13:00 UTC), 3h offset → 13:00 EAT = 10:00 UTC.
const cutoff = bookingCloseCutoff(departure, 'IMPORT', {
importCloseOffsetMinutes: 180,
});
expect(cutoff.toISOString()).toBe('2026-07-10T10:00:00.000Z');
});
it('export 1-day offset: Jul-10 16:00 EAT departure → cutoff Jul-9 16:00 EAT', () => {
const cutoff = bookingCloseCutoff(departure, 'EXPORT', {
exportCloseOffsetMinutes: 1440,
});
// Jul 9 16:00 EAT = Jul 9 13:00 UTC.
expect(cutoff.toISOString()).toBe('2026-07-09T13:00:00.000Z');
});
it('import and export offsets are independent', () => {
const cfg = {
importCloseOffsetMinutes: 180,
exportCloseOffsetMinutes: 1440,
};
expect(bookingCloseCutoff(departure, 'IMPORT', cfg).toISOString()).toBe(
'2026-07-10T10:00:00.000Z',
);
expect(bookingCloseCutoff(departure, 'EXPORT', cfg).toISOString()).toBe(
'2026-07-09T13:00:00.000Z',
);
// DOMESTIC uses the import offset.
expect(bookingCloseCutoff(departure, 'DOMESTIC', cfg).toISOString()).toBe(
'2026-07-10T10:00:00.000Z',
);
});
});
describe('window-time computation honours the close offset', () => {
it('export closes at departure offset, not departure', () => {
// Departs Jul 10 16:00 EAT (13:00 UTC), lead 24h, 24-hour desk, 1-day offset.
const departure = new Date('2026-07-10T13:00:00.000Z');
const { windowClosesAt } = computeExportWindowTimes(departure, {
exportBookingLeadHours: 48,
windowOpenHour: 8,
windowCloseHour: 8, // 24-hour desk
exportCloseOffsetMinutes: 1440,
});
// Jul 9 16:00 EAT = Jul 9 13:00 UTC.
expect(windowClosesAt.toISOString()).toBe('2026-07-09T13:00:00.000Z');
});
it('import close is capped at the cutoff (departure offset)', () => {
// Round-the-clock desk, opens 05 Jul 12:00 EAT, 24h duration would run to
// 06 Jul 12:00; departure 06 Jul 08:00 EAT (05:00 UTC) with a 2-hour offset →
// cutoff 06 Jul 06:00 EAT = 03:00 UTC.
const departure = new Date('2026-07-06T05:00:00.000Z');
const now = new Date('2026-07-05T09:00:00.000Z');
const { windowClosesAt } = computeImportWindowTimes(
departure,
{
importWindowLeadDays: 3,
windowOpenHour: 8,
windowCloseHour: 8, // 24-hour desk (no office-hour cap)
windowDurationHours: 24,
importCloseOffsetMinutes: 120,
},
now,
);
expect(windowClosesAt.toISOString()).toBe('2026-07-06T03:00:00.000Z');
});
});