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}/export | trips:read | Export trip (?format=pdf, csv, or txt) |
GraphQL
trips(page, limit, status)→PaginatedTripstrip(id)→GqlTriptripGpsData(tripId, page, limit)→PaginatedGpsDatatripExportData(tripId)→GqlTripExportcreateTrip(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 updateTrip (GraphQL) or PATCH /v1/trips/{id} (REST with status_action), the following coordinate fields can be passed:
| Field | Saved to | When to use |
|---|---|---|
start_latitude / start_longitude | Trip record | On start action (origin GPS position) |
end_latitude / end_longitude | Trip record | On complete action (destination GPS position) |
history_latitude / history_longitude | 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.
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. Available via REST (GET /v1/trips/{id}/export?format=pdf|csv|txt) or GraphQL (tripExportData(tripId)).
Trip Status History
Every status transition 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 | /api/trips/:id/history | trips:read | Get status change 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