Skip to main content

Overview

The Movements API provides endpoints to query and synchronize shipment movements (loads) from McLeod TMS. Each movement represents a shipment with one or more stops (pickup and delivery locations). Database Tables:
  • movements - Main movement data (id, status, tractor_id, driver_ids, etc.)
  • movement_stops - Stop-level data (city, address, ETA, scheduled times, etc.)
Status Codes:
  • A - Available/Assigned
  • P - Progress (active/in-transit)
  • D - Delivered/Completed

Get Movements

GET /api/movements

Get movements with nested stops from the database.
status
string
default:"None"
Filter by movement status (e.g., P for Progress/active movements)
limit
integer
default:"100"
Maximum number of movements to return (1-1000)
Response:
[
  {
    "id": "762965",
    "order_id": null,
    "company_id": "TMS",
    "status": "P",
    "tractor_id": "735",
    "vehicle_id": "vcl_01D8ZQFGHVJ858NBF2Q7DV9MNC",
    "trailer_ids": "53668",
    "driver_ids": "M72323",
    "driver_names": "John Smith",
    "stops": [
      {
        "id": "zz1j4sp91v30iv8BIRAS02",
        "movement_id": 762965,
        "movement_sequence": 1,
        "city_name": "JONESBORO",
        "state": "AR",
        "zip_code": "72401",
        "address": "4000 moore road",
        "stop_type": "PU",
        "status": "D",
        "location_name": "Hytrol Conveyor DC",
        "contact_name": "CODY HALL",
        "latitude": "35.8423",
        "longitude": "-90.7043",
        "eta": "2025-09-12T01:27:00-05:00",
        "sched_arrive_early": "2025-09-11T12:00:00-05:00",
        "sched_arrive_late": "2025-09-11T12:00:00-05:00",
        "timing_status": "on_time",
        "timing_delta_minutes": -30
      }
    ]
  }
]
How it works:
  1. Query Movements: Calls get_movements() which queries the movements table with:
    • LEFT JOIN to tractors table to get Terminal vehicle_id
    • Filter to exclude brokered movements (brokerage != True)
    • Ordered by id DESC (newest first)
  2. Fetch Stops: For each movement, calls get_movement_stops_by_movement_id() to get associated stops from movement_stops table, ordered by movement_sequence
  3. Driver Name Enrichment: Uses DriverMcLeodMapper to fetch driver names from McLeod API:
    • Parses comma-separated driver_ids (McLeod IDs like “M72323”)
    • Calls McLeod API /v1/driver?id={driver_id} for each unique ID
    • Caches results in memory to avoid repeated API calls
    • Returns comma-separated driver_names (e.g., “John Smith, Jane Doe”)
  4. Timing Status Calculation: For each stop, calculates timing_status and timing_delta_minutes:
    • Compares eta to sched_arrive_early
    • Returns early, on_time, late, or unknown
    • Returns delta in minutes (negative = early, positive = late)
Field Mapping:
Response FieldDatabase ColumnSource
tractor_idmovements.__tractoridMcLeod TMS
vehicle_idtractors.vehicle_idTerminal API (via JOIN)
driver_idsmovements.__driveridsMcLeod TMS
driver_names-McLeod API (runtime lookup)
trailer_idsmovements.__traileridsMcLeod TMS

Sync Movements

POST /api/movements/sync

Sync in-progress movements from McLeod API to the database. Uses pagination to fetch all matching movements.
company_id
string
default:"None"
Ignored - McLeod filters by company based on API credentials
status
string
default:"P"
Movement status filter (e.g., P for Progress). Defaults to P if not specified.
limit
integer
default:"100"
Maximum movements to fetch per status (1-1000)
Response:
{
  "success": true,
  "message": "Movements synced successfully",
  "data": {
    "fetched": 125,
    "synced": 125,
    "errors": 0
  }
}
How it works:
  1. Pagination Loop: Fetches movements from McLeod API in batches of 50 (McLeodClient.get_movements())
    • Increments offset by 50 each batch
    • Continues until batch returns fewer than 50 movements (end of data)
  2. Parse & Validate: For each batch:
    • Parses raw JSON into Movement Pydantic models
    • Logs parsing errors but continues processing
  3. UPSERT to Database: Calls upsert_movements_batch():
    • Inserts or updates movements table using ON CONFLICT (id) DO UPDATE
    • For each movement, upserts associated stops to movement_stops table
    • Parses McLeod timestamp format (YYYYMMDDHHmmss-timezone) to datetime
  4. Tractor Location Fallback: After sync, calls TractorLocationService.update_for_movements_batch():
    • Uses stop coordinates as fallback location when Terminal GPS is unavailable
    • Updates tractors.latitude, tractors.longitude, tractors.last_location_provider='movement_stop'
McLeod API Call:
GET /ws/api/movement?status=P&offset=0&limit=50

Sync Delivered Movements

POST /api/movements/sync-delivered

Sync delivered movements (status=“D”) from McLeod API. Only updates movements that already exist in the database with status=“P” - prevents inserting new delivered movements that were never tracked.
company_id
string
default:"None"
Ignored - McLeod filters by company based on API credentials
limit
integer
default:"100"
Maximum movements to fetch (1-1000)
Response:
{
  "success": true,
  "message": "Delivered movements synced successfully",
  "data": {
    "fetched": 50,
    "filtered": 12,
    "synced": 12,
    "errors": 0
  }
}
How it works:
  1. Fetch Delivered Movements: Calls McLeod API with status=D:
    GET /ws/api/movement?status=D&offset=0&limit=100
    
  2. Get In-Progress IDs: Queries movements table for all movement IDs with status='P'
  3. Filter: Only keeps fetched movements whose ID exists in the in-progress set
    • filtered count shows how many matched
    • This prevents inserting delivered movements we never tracked as in-progress
  4. UPSERT: Updates matching movements from status='P' to status='D'
  5. No Location Update: Skips tractor location fallback since delivered movements are no longer active

Sync All Movements

POST /api/movements/sync-all

Convenience endpoint that syncs both in-progress (P) and delivered (D) movements in a single call.
company_id
string
default:"None"
Ignored - McLeod filters by company based on API credentials
limit
integer
default:"100"
Maximum movements to fetch per status (1-1000)
Response:
{
  "success": true,
  "message": "All movements synced successfully",
  "data": {
    "in_progress": {
      "fetched": 125,
      "synced": 125,
      "errors": 0
    },
    "delivered": {
      "fetched": 50,
      "filtered": 12,
      "synced": 12,
      "errors": 0
    }
  }
}
How it works:
  1. Calls sync_movements(status='P') to sync in-progress movements
  2. Calls sync_delivered_movements() to sync delivered movements
  3. Returns combined statistics from both operations

Health Check

GET /api/movements/health

Health check endpoint for the movements module. Response:
{
  "status": "healthy",
  "module": "movements"
}

Background Sync Services

In addition to manual sync endpoints, movements are also synced automatically by background services:

MovementChangeSyncService

  • Interval: Every hour (configurable via MOVEMENT_CHANGE_SYNC_INTERVAL_SECONDS)
  • Behavior: Uses McLeod’s changedAfterDate parameter to fetch only movements changed since last sync
  • Initial Run: Syncs movements from past 24 hours
  • Config: MOVEMENT_CHANGE_SYNC_ENABLED=true (default)

MovementFullSyncService

  • Interval: Every 30 minutes (configurable via MOVEMENT_FULL_SYNC_INTERVAL_SECONDS)
  • Behavior: Syncs all statuses (A, P, D) in full
  • Config: MOVEMENT_FULL_SYNC_ENABLED=true (default)