mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 15:06:32 +00:00
feat(tours): plan a multi-day tour as one route split into stages
A hut-to-hut hike or a paddle trip over several days is now planned in the Tours planner as one tour: the whole route is planned as before, and stops along it are marked as stage ends (MDT-01). The stages are read off that one route in its order, never kept as copies, so a change inside a stage changes the whole tour and every figure on it. - A Day tour / Multi-day tour switch beside the tour type; a stop's menu ends a stage there or lets it go on. Back to a day tour removes every stage end after a confirmation, as one step undo brings back. - The list of points is headed per stage with its distance and moving time; a stop that ends a stage carries a moon at its number, on the map too. - The overview lists the stages from stop to stop with distance, climb, descent and time (hiking rule per stage, paddle estimate per stage, the router's time shared by distance otherwise), marks where each ends across the elevation profile, and a stage of more than a day on the move warns. - A stage end a change cannot keep (its stop removed, moved to an end or made a shaping point) is dropped and said so, with undo, never moved to another point. - A multi-day tour may take a day a stage, so its time limit grows with its stages; the tour keeps its one type, portages included. - The list and the MCP tools show the stage count and where stages end. Stage ends are a flag on the waypoint (migration 046000); a tour without one is a day tour, as every tour saved before.
This commit is contained in:
@@ -24,6 +24,7 @@ export class TourWaypoints {
|
||||
protected: number & Opt = 0;
|
||||
opening_hours?: string | null;
|
||||
poi_category?: string | null;
|
||||
stage_end: number & Opt = 0;
|
||||
}
|
||||
|
||||
export const TourWaypointsImportance = {
|
||||
@@ -51,6 +52,7 @@ export const TourWaypointsSchema = defineEntity({
|
||||
expression: 'stay_minutes IS NULL OR (stay_minutes >= 0 AND stay_minutes <= 1440)',
|
||||
},
|
||||
{ name: 'tour_waypoints_shaping_check', expression: 'shaping IN (0, 1)' },
|
||||
{ name: 'tour_waypoints_stage_end_check', expression: 'stage_end IN (0, 1)' },
|
||||
],
|
||||
properties: {
|
||||
id: p.integer().primary(),
|
||||
@@ -72,5 +74,6 @@ export const TourWaypointsSchema = defineEntity({
|
||||
protected: p.integer().default(0),
|
||||
opening_hours: p.text().nullable(),
|
||||
poi_category: p.text().nullable(),
|
||||
stage_end: p.integer().default(0),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -20,4 +20,5 @@ export interface TourWaypointsTable {
|
||||
protected: InsertOptional<number>;
|
||||
opening_hours: InsertOptional<string | null>;
|
||||
poi_category: InsertOptional<string | null>;
|
||||
stage_end: InsertOptional<number>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
/**
|
||||
* MDT-01: a stop of a tour can end a stage, so one route planned whole splits
|
||||
* into the days of a multi-day tour, each stage running from one such stop to
|
||||
* the next. 0 or 1; every waypoint saved before ends none, and a tour without
|
||||
* one stays a day tour, as it was.
|
||||
*/
|
||||
export class Migration20200101046000_a_tour_waypoint_can_end_a_stage extends Migration {
|
||||
override name = 'Migration20200101046000_a_tour_waypoint_can_end_a_stage';
|
||||
|
||||
override up(): void {
|
||||
this.addSql(
|
||||
`ALTER TABLE tour_waypoints ADD COLUMN stage_end INTEGER NOT NULL DEFAULT 0 CHECK (stage_end IN (0, 1))`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ export interface TourWaypointRow {
|
||||
opening_hours?: string | null;
|
||||
/** PLN-03: the kind of place the point was taken in from along the tour; absent on a point set by hand. */
|
||||
poi_category?: TourPoiCategory | null;
|
||||
/** MDT-01: a stop where a stage of a multi-day tour ends; absent where the route goes on. */
|
||||
stage_end?: boolean;
|
||||
}
|
||||
|
||||
/** The row as stored, the fixed leg still JSON text. */
|
||||
@@ -52,6 +54,7 @@ interface StoredWaypoint {
|
||||
protected: number;
|
||||
opening_hours: string | null;
|
||||
poi_category: string | null;
|
||||
stage_end: number;
|
||||
}
|
||||
|
||||
/** The columns a waypoint is read with, as the stored row names them. */
|
||||
@@ -71,6 +74,7 @@ const WAYPOINT_COLUMNS = [
|
||||
'protected',
|
||||
'opening_hours',
|
||||
'poi_category',
|
||||
'stage_end',
|
||||
] as const;
|
||||
|
||||
/** A stored kind the contract knows; one it does not (dropped since) reads as none. */
|
||||
@@ -100,6 +104,7 @@ function readWaypoint(row: StoredWaypoint): TourWaypointRow {
|
||||
...(row.protected ? { protected: true } : {}),
|
||||
...(opening_hours ? { opening_hours } : {}),
|
||||
...(poi_category ? { poi_category } : {}),
|
||||
...(row.stage_end ? { stage_end: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -196,6 +201,7 @@ export class TourWaypointsRepository extends TrekRepository<TourWaypoints> {
|
||||
protected: w.protected ? 1 : 0,
|
||||
opening_hours: w.opening_hours ?? null,
|
||||
poi_category: w.poi_category ?? null,
|
||||
stage_end: w.stage_end ? 1 : 0,
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -66,6 +66,8 @@ export interface TourListRow {
|
||||
/** ACT-01: the first and last time on an imported track's points; NULL without times. */
|
||||
track_started_at: string | null;
|
||||
track_ended_at: string | null;
|
||||
/** MDT-01: how many of its stops end a stage; 0 for a day tour. */
|
||||
stage_ends: number;
|
||||
}
|
||||
|
||||
/** LIB-02: a tour of another trip as the take-over list reads it, with the trip it is in. */
|
||||
@@ -314,6 +316,12 @@ export class ToursRepository extends TrekRepository<Tours> {
|
||||
eb
|
||||
.exists(eb.selectFrom('tour_waypoints as tw').select('tw.id').whereRef('tw.place_id', '=', 'p.id'))
|
||||
.as('has_waypoints'),
|
||||
eb
|
||||
.selectFrom('tour_waypoints as ts')
|
||||
.select((count) => count.fn.countAll<number>().as('n'))
|
||||
.whereRef('ts.place_id', '=', 'p.id')
|
||||
.where('ts.stage_end', '=', 1)
|
||||
.as('stage_ends'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -879,11 +887,12 @@ function toLibraryRow<R extends Omit<TourLibraryRow, 'has_waypoints'> & { has_wa
|
||||
}
|
||||
|
||||
function toListRow<
|
||||
R extends Omit<TourListRow, 'planned' | 'has_waypoints' | 'has_recorded_track' | 'route_missing'> & {
|
||||
R extends Omit<TourListRow, 'planned' | 'has_waypoints' | 'has_recorded_track' | 'route_missing' | 'stage_ends'> & {
|
||||
planned: unknown;
|
||||
has_waypoints: unknown;
|
||||
has_recorded_track: unknown;
|
||||
route_missing: unknown;
|
||||
stage_ends: unknown;
|
||||
},
|
||||
>(r: R): TourListRow {
|
||||
return {
|
||||
@@ -892,5 +901,7 @@ function toListRow<
|
||||
has_waypoints: Number(r.has_waypoints),
|
||||
has_recorded_track: Number(r.has_recorded_track),
|
||||
route_missing: Number(r.route_missing),
|
||||
// Postgres counts in a bigint, which its driver hands back as text.
|
||||
stage_ends: Number(r.stage_ends),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user