Trips Overview
The Trip system records transport operations: plan a route, start tracking, pause or resume, then complete. Each trip can optionally log GPS data from a linked device.
Status Machine
draft → active ↔ paused → completed
| Transition | Triggered by | Side effect |
|---|---|---|
draft → active | "Start Trip" | Sets started_at; saves start_latitude/start_longitude |
active → paused | "Pause Trip" | Saves history_latitude/history_longitude to status history entry |
paused → active | "Resume Trip" | Saves history_latitude/history_longitude to status history entry |
active → completed | "Complete Trip" | Sets ended_at; saves end_latitude/end_longitude |
paused → completed | "Complete Trip" | Sets ended_at; saves end_latitude/end_longitude |
completed → * | — | No further transitions allowed |
Trip Fields
| Field | Type | Description |
|---|---|---|
id | UUID | Trip ID |
name | string | Trip name |
description | string? | Optional description |
status | string | draft, active, paused, completed |
gps_logging_enabled | bool | Whether GPS points are linked to this trip |
device_id | UUID? | GPS device for logging |
category_id | UUID? | Transport category |
origin_id | UUID? | Sender location |
destination_id | UUID? | Receiver location |
weight_kg | float? | Payload weight in kg (display as tons: ÷ 1000) |
tags | string[] | Free-text tags |
notes | string? | Free-text notes |
start_latitude | float? | GPS latitude when trip was started |
start_longitude | float? | GPS longitude when trip was started |
end_latitude | float? | GPS latitude when trip was completed |
end_longitude | float? | GPS longitude when trip was completed |
started_at | timestamp? | Set on first activation |
ended_at | timestamp? | Set on completion |
REST Endpoints
| Method | Path | Permission | Description |
|---|---|---|---|
| GET | /v1/trips | trips:read | List trips (paginated, filterable by status) |
| GET | /v1/trips/{id} | trips:read | Get trip with resolved relations |
| POST | /v1/trips | trips:write | Create trip |
| PATCH | /v1/trips/{id} | trips:write | Update trip / trigger status transition |
| DELETE | /v1/trips/{id} | trips:delete | Delete trip (draft/completed only) |
| GET | /v1/trips/{id}/gps | trips:read | GPS data for trip (paginated) |
| GET | /v1/trips/{id}/history | trips:read | Trip status history (parity with GraphQL tripStatusHistory) |
Export is not a REST endpoint — it is a webapp-only Next.js feature. See Export.
GraphQL
trips(page, limit, status)→PaginatedTripstrip(id)→GqlTriptripGpsData(tripId, page, limit)→PaginatedGpsDatatripStatusHistory(tripId)→[GqlTripStatusHistory]createTrip(input)→GqlTripupdateTrip(id, input)→GqlTripdeleteTrip(id)→Boolean
GqlTrip includes resolved relations: category, origin, destination, device, gpsPointCount, and weightTons (computed as weight_kg / 1000).
Status Update with Coordinates
When changing trip status via the GraphQL updateTrip mutation, the following coordinate fields can be passed. The webapp sends them through the Next.js proxy route PATCH /api/trips/{id} using a status_action field (start / pause / resume / complete), which maps to updateTrip:
| Field (GraphQL input) | Saved to | When to use |
|---|---|---|
startLatitude / startLongitude | Trip record | On start action (origin GPS position) |
endLatitude / endLongitude | Trip record | On complete action (destination GPS position) |
historyLatitude / historyLongitude | Status history entry | On any status change (current GPS position for map badge) |
The coordinate source is typically the current GPS position of the linked device, or the geocoded coordinates of the origin/destination location. The history* fields fall back to start* or end* coordinates if not explicitly provided.
REST vs GraphQL: The Rust REST
PATCH /v1/trips/{id}accepts a directstatusfield plusstart_latitude/start_longitude/end_latitude/end_longitudeonly — it has nostatus_actionshorthand, nohistory_*fields, and does not writeTripStatusHistoryrows. Status-history entries (including pause/resume coordinates) are recorded only by the GraphQLupdateTripmutation, which is the path the webapp uses.
GPS Logging Integration
When gps_logging_enabled = true and the trip is active, every GPS point received for the linked device_id is automatically tagged with trip_id.
Constraint: Only one active trip per device may have gps_logging_enabled = true at a time. Attempting to activate a second one returns 409 Conflict.
Deletion Rules
| Status | Deletable |
|---|---|
draft | Yes |
active | No — complete first |
paused | No — complete first |
completed | Yes (NULLs trip_id on linked GPS data) |
Related
- Categories — transport type hierarchy
- Locations — sender/receiver with geocoding
- Templates — reusable trip blueprints
- Export — PDF, CSV, and TXT download
- GPS: Overview — GPS data ingestion
- GPS: Devices — device management
Trip View Page
The trip detail page (/trips/[id]) provides a read-only overview of a trip with:
- Route Map — MapRoute visualization of GPS data with start/end markers, auto-fit bounds
- Live Map — Active trips with GPS logging show a live map with LiveMarker (real-time GPS position), route polyline, start/end markers, and pause badges
- Pause Badges — Pause markers rendered on the route map at the GPS coordinates where each pause occurred, with a duration badge showing how long the trip was paused
- Live Tracking — WebSocket-based real-time GPS updates when trip is active with GPS logging
- Stats Cards — Duration, GPS points count, weight (kg/tons), status duration
- Route Info — Origin and destination locations
- Details — Category (with color), device, GPS logging status, tags
- Timeline — Status change history with timestamps (created, started, paused, resumed, completed)
- Notes — Read-only trip notes
- GPS Data Table — Collapsible, paginated table of GPS data points
- Status Actions — Start/Pause/Resume/Complete buttons (same as overview list)
- Export — PDF, CSV, and TXT download (see below)
Trip Exports
Export is available from the trip view page via the export button. Supported formats:
| Format | Content |
|---|---|
| Full trip report with route info, stats, timeline/status history, and notes | |
| CSV | Tabular trip data including GPS points and status history |
| TXT | Plain-text summary of trip details and timeline |
All export formats include the status change timeline. Export is a webapp-only feature implemented in the Next.js proxy route /api/trips/[id]/export?format=pdf|csv|txt, which composes the file from GraphQL (trip + tripGpsData + tripStatusHistory). There is no Rust REST export endpoint. See Export.
Trip Status History
Every status transition performed through the GraphQL updateTrip mutation (the path the webapp uses) is logged in the TripStatusHistory table.
Fields
| Field | Type | Description |
|---|---|---|
| id | UUID | Primary key |
| trip_id | UUID | Reference to Trip |
| old_status | string | Previous status |
| new_status | string | New status |
| changed_by | UUID? | User who changed the status |
| latitude | float? | GPS latitude at the time of status change |
| longitude | float? | GPS longitude at the time of status change |
| changed_at | timestamp | When the change occurred |
Coordinates are populated from history_latitude/history_longitude passed during the update request (falls back to start_latitude or end_latitude if not provided). This is used to display pause badges on the route map.
REST Endpoint
| Method | Path | Permission | Description |
|---|---|---|---|
| GET | /v1/trips/{id}/history | trips:read | Get status change history (parity with GraphQL tripStatusHistory) |
The webapp reaches this via the Next.js proxy route /api/trips/[id]/history.
GraphQL
query TripStatusHistory($tripId: ID!) {
tripStatusHistory(tripId: $tripId) {
id tripId oldStatus newStatus changedBy latitude longitude changedAt
}
}
Audit Events
All trip-related mutations are logged as audit events under the modules category:
| Event | Logged when |
|---|---|
trip_created | New trip created (including from template) |
trip_updated | Trip updated (fields or status change) |
trip_deleted | Trip deleted |
location_created | Location created |
location_updated | Location updated |
location_deleted | Location deleted |
trip_category_created | Category created |
trip_category_updated | Category updated |
trip_category_deleted | Category deleted |
trip_template_created | Template created |
trip_template_updated | Template updated |
trip_template_deleted | Template deleted |
Events are logged in both REST and GraphQL handlers using constants from heimdall_audit::events.
Save as Template
On the trip create page (/trips/create), users can:
- Load a template from a dropdown to pre-fill form fields
- Save the current form as a new template via the "Save as Template" button