mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
feat(roadtrip): a flight, train or ferry booking seams the drive at its terminals
The road trip routed straight from one stop to the next, whatever stood between them: a flight booked for the afternoon became an eight-hour drive on the map, with the hotel reached by road from a town the traveller had flown out of (issue #2428). The day plan has treated such bookings as carriers for a while, and the drive now reads them the same way. A flight, train, ferry, cruise or bus with located terminals is a seam. Its two terminals are seated among the day's stops where the day plan lists the booking, as stops of their own: no number, no stay, no kind, not draggable, and an id below every assignment and every automatic night, because they stand for none. The road ends at the departure terminal and starts again at the arrival one; the leg between them is the ride, in the booking's own mode, with the timetable's minutes and no distance, and no router is ever asked for it. The departure terminal is pinned a check-in ahead of the timetable and left on the timetable's minute, so a drive that cannot make the airport in time is late the way a missed pin is. A ride that lands on a later day seats its arrival at the start of that day; with connected days the join between the two is the ride, and with daily travel times set the ride runs on the booking's clock through the night, with no day end placed on a flight. The map draws the ride as the booking's own arc, the one the day plan already draws, beside the roads. The day's distance and driving time count the roads only, the range budget spends nothing on a ride, and after one the car cannot take it starts afresh. The corridor search leaves the ride out and drops a hit under the flight path; a via cannot be filed against a terminal, and other ways of driving are offered for the road into a departure terminal only. The server plan reads the same three tables and seats the same stops, so calculate_roadtrip answers the same as the rail. Closes #2428
This commit is contained in:
@@ -116,3 +116,71 @@ describe('a visit end time on the road trip', () => {
|
||||
expect(calculated.days[0].stops[1].leaveAt).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('a booking the traveller rides (#2428)', () => {
|
||||
function withFlight(dayId: number, tripId: number, over: { end_day_id?: number; dep?: string; arr?: string } = {}) {
|
||||
const result = db
|
||||
.prepare(
|
||||
`INSERT INTO reservations (trip_id, title, type, day_id, end_day_id, reservation_time, reservation_end_time)
|
||||
VALUES (?, ?, 'flight', ?, ?, ?, ?)`,
|
||||
)
|
||||
.run(tripId, 'LH 2020 HAM → MUC', dayId, over.end_day_id ?? dayId, over.dep ?? '13:20', over.arr ?? '14:30');
|
||||
const id = Number(result.lastInsertRowid);
|
||||
const endpoint = db.prepare(
|
||||
'INSERT INTO reservation_endpoints (reservation_id, role, sequence, name, code, lat, lng) VALUES (?, ?, ?, ?, ?, ?, ?)',
|
||||
);
|
||||
endpoint.run(id, 'from', 0, 'Hamburg Airport', 'HAM', 53.63, 9.99);
|
||||
endpoint.run(id, 'to', 1, 'Munich Airport', 'MUC', 48.35, 11.78);
|
||||
return id;
|
||||
}
|
||||
|
||||
it('seats the terminals in the day and never asks the router for the ride', async () => {
|
||||
const { user, trip, visits, plans } = setup();
|
||||
db.prepare("UPDATE day_assignments SET assignment_time = '10:00' WHERE id = ?").run(visits[1].id);
|
||||
const day = db.prepare('SELECT day_id FROM day_assignments WHERE id = ?').get(visits[0].id) as { day_id: number };
|
||||
const flightId = withFlight(day.day_id, trip.id);
|
||||
|
||||
const { context, calculated } = await plans.calculate(trip.id, user.id);
|
||||
|
||||
expect(context.carriers).toHaveLength(1);
|
||||
expect(context.carriers[0].endpoints).toHaveLength(2);
|
||||
const card = calculated.days[0];
|
||||
// Behind Lueneburg (10:00), before the untimed Celle: the day plan's own seat.
|
||||
expect(card.stops.map((s) => s.carrier?.role ?? s.name)).toEqual(['Hamburg', 'Lueneburg', 'departure', 'arrival', 'Celle']);
|
||||
const departure = card.stops[2];
|
||||
expect(departure.carrier).toMatchObject({ reservationId: flightId, type: 'flight', code: 'HAM', at: '13:20' });
|
||||
expect(departure.assignmentId).toBeLessThan(-2_000_000_000);
|
||||
// The ride is a leg of the booking's minutes with no road under it; the roads to
|
||||
// and from the terminals were routed like any other leg.
|
||||
expect(card.legs.map((l) => [l?.mode, l?.duration])).toEqual([
|
||||
['driving', 3600],
|
||||
['driving', 3600],
|
||||
['flight', 70 * 60],
|
||||
['driving', 3600],
|
||||
]);
|
||||
expect(card.legs[2]?.distance).toBe(0);
|
||||
// Reached an hour ahead of the flight, off on the timetable, landed on the timetable.
|
||||
expect(card.schedule.entries.map((e) => [e.arrival, e.departure])).toEqual([
|
||||
['09:00', '09:00'],
|
||||
['10:00', '10:30'],
|
||||
['12:20', '13:20'],
|
||||
['14:30', '14:30'],
|
||||
['15:30', '16:00'],
|
||||
]);
|
||||
// The day's figures are the drive's: three roads of 60 km, no flight in them.
|
||||
expect(card.distance).toBe(180_000);
|
||||
expect(calculated.totalStops).toBe(3);
|
||||
});
|
||||
|
||||
it('lets a booking without located terminals fall through, and ignores a hire car', async () => {
|
||||
const { user, trip, visits, plans } = setup();
|
||||
const day = db.prepare('SELECT day_id FROM day_assignments WHERE id = ?').get(visits[0].id) as { day_id: number };
|
||||
db.prepare("INSERT INTO reservations (trip_id, title, type, day_id) VALUES (?, 'Hire car', 'car', ?)").run(trip.id, day.day_id);
|
||||
db.prepare("INSERT INTO reservations (trip_id, title, type, day_id, reservation_time) VALUES (?, 'Somewhere', 'train', ?, '11:00')").run(trip.id, day.day_id);
|
||||
|
||||
const { context, calculated } = await plans.calculate(trip.id, user.id);
|
||||
|
||||
expect(context.carriers.map((c) => c.type)).toEqual(['train']);
|
||||
expect(calculated.days[0].stops.every((s) => !s.carrier)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user