import { JsonSchema } from '@redocly/ui';

# Sending planning data as OTM5 file
To make sending planning data a lot easier, we make use of our [universal planning inbox](integration.md).
The inbox is a single API which is able to receive planning data in different formats.
One of the formats we are able to consume is JSON and specifically in the [OTM5 standard](https://otm5.opentripmodel.org/).
Underneath you will find more information on how to arrange your OTM5 file. 
Let's get started.

## A look at the basic format
We are going to take you through the data structure, step by step. Note that this is about _inserting_ or _updating_ trips,
cancelations are also supported but explained later. There are also variations on the 'normal' trip that come afterwards as
well. Let's take a look at the objects we will be using for the default trip planning first:

```json
{
  "id": "2a8af6c6-e4d5-5dfb-9532-e8b17c3ffd88",
  "name": "2021-32-2-888",
  "externalAttributes": {},
  "actors": [],
  "actions": []
}
```
```xml
<JsonSchema
    schema={{
        type: 'object',
        description: 'A representation of a trip',
        properties: {
            id: {
                type: 'uuid',
                description: 'Universally Unique Identifier to the trip.',
                example: '2a8af6c6-e4d5-5dfb-9532-e8b17c3ffd88'
            },
            name: {
                type: 'string',
                description: 'Display name of this trip as typically seen in the URL in the Simacan domain, usually unique within a single day.',
                example: '2021-32-2-888'
            },
            externalAttributes: {
                type: 'object',
                description: 'Any attributes related to this trip.',
                example: []
            },
            actors: {
                type: 'array',
                description: 'All actors that are involved in this trip, and typically have access to at least its planning.',
                example: {}
            },
            actions: {
                type: 'array',
                description: 'All actions that are involved in this trip.',
                example: {}
            }
        }
    }}
/>
```
## Trip planning ruleset
We have implemented a few rules, to which an OTM5 planning should adhere, to make it a validated and valuable planning.

### Trip level
- A trip is required to have a `name` (display name), as shown in the basic structure
- A trip is required to have `actions`: at least two stops are necessary.
- A trip is required to have `actors`, a shipper and a carrier; this would look like the following:

```json
   "actors": [
    {
      "associationType": "attributeRestriction",
      "restriction": {
        "externalAttributes": {
          "carrierId": "<my-carrier>"
        }
      },
      "entityType": "actor",
      "roles": ["carrier"]
    },
    {
      "associationType": "attributeRestriction",
      "restriction": {
        "externalAttributes": {
          "shipperId": "<my-shipper>"
        }
      },
      "entityType": "actor",
      "roles": ["shipper"]
    }
]
```

- A trip optionally has a `shift` and/or `tripId` within the `externalAttributes` object. More attributes can be provided
  and will be saved in the Control Tower as _customer data_. This data serves no purpose except for filtering
  trips that have the same values for the same keys.

```json
  "externalAttributes": {
    "tripId": "some-unique-id",
    "shift": "Evening shift",
    "some-custom-field": "some-custom-value"
  }
```

### Stop level
A stop is a member of the actions array

- A stop is required to have a `location`, `startTime` and an `endTime`.
- A stop can have multiple `load` and `unload` actions; these actions have a `consignment`.
- A stop optionally has a `name`, `remark` and `sequenceNr`.
- A stop optionally has a `stopId` within an `externalAttribute`, this identifier can be used for updating this stop. If none
  is present the UUID of the stop will be used.
- A stop optionally has time constraints, wich in code looks like:
```json
  {
    "id": "48c484f7-54ad-4209-b251-44abef26f6b0",
    "value": {
        "startTime": "2021-03-12T10:00:00Z",
        "endTime": "2021-03-12T11:00:00Z",
        "type": "timeWindowsConstraint"
      }
    }
  }
```

this replaced the older, but still supported version:

```json
  {
    "id": "48c484f7-54ad-4209-b251-44abef26f6b0",
    "value": {
      "and": [
        {
          "startDateTime": "2021-03-12T10:00:00Z",
          "type": "startDateTimeConstraint"
        },
        {
          "endDateTime": "2021-03-12T11:00:00Z",
          "type": "endDateTimeConstraint"
        }
      ],
      "type": "andConstraint"
    }
  }
```

### Consignment level
- A `load`/`unload` action has an inline `consignment`, a `startTime` and an `endTime`
- A consignment has a `type` (free text), this is mapped to what is known in the SCT as the _goodsFlowType_.
- A consignment optionally has `externalAttributes` such as `consignmentId`, `orderId`, `userId`, `trackTraceCode`. Other attributes are ignored.
- A consignment optionally has `goods`. A good has a quantity and optionally a productType, barCode en remark
- A complete consignment will more or less have the following structure in code (depending on what fields you fill of course):

```json
{
  "id": "a556da93-46f3-4df5-8269-263bb337fde4",
  "externalAttributes": {
    "orderId": "<order id>",
    "userId": "<user id>",
    "trackTraceCode": "<t&t code>"
  },
  "type": "Frozen goods",
  "goods": [
    {
      "entity": {
        "id": "b74d1b1a-905a-42dd-91be-1fe77f443467",
        "remark": "Be careful not to melt it!",
        "barCode": "123456789",
        "quantity": 3,
        "productType": "Ice cream",
        "type": "items"
      },
      "associationType": "inline"
    }
  ]
}
```

### Location level

Within Simacan we deal with two types of locations:
- _managed_ locations, those that are reused for different trips repeatedly. Managed locations also are visible in Master Data, can have manual geofences,
  and first/last mile guidance.
- _unmanaged_ locations, those are only of interst in one planning and thus are not visible in Master Data. These locations do not have manual geofences,
  and no first or last mile guidance.

Both types of locations can be send using OTM5. Whenever a location is already in our system (managed) you can refer to it based on its ID by using
a 'restriction'.

**1.** A location as restriction, only uses a locationId, will only work if the location is already known within Simacan:

```json
"location": {
  "associationType": "attributeRestriction",
  "restriction": {
    "externalAttributes": {
      "locationId": "someId"
    }
  },
  "entityType": "location"
}
```

Whenever a location is unknown (unmanaged) or needs to be updated (both managed and unmanaged) you can provide a location 'inline':

**2a.** A location as a full address requires a `street`, `postalCode`, `city`, `country`, `name` and optional a `houseNumber` and `houseNumberExtension`. An
inline location also has a type which can be `customer`, `store`, `warehouse`, or `operationalBase`. Note that the first stop in a trip **always**
needs to be on a `warehouse` or `operationalBase`.

```json
   "location": {
       "entity": {
         "id": "a1463169-8e75-4c0c-8d4f-fbeb408f53aa",
         "geoReference": {
           "name": "Simacan",
           "street": "Valutaboulevard",
           "houseNumber": "16",
           "postalCode": "1234AB",
           "city": "Amersfoort",
           "type": "addressGeoReference"
         },
         "type": "customer"
       },
       "associationType": "inline"
   }
```

- An inline location optionally has lat/lon-coordinates:

```json
{
  "id": "440fffc6-3d2f-4cb2-9b98-93f0f1a87f3a",
  "geoReference": {
    "lat": 52.370216,
    "lon": 4.895168,
    "type": "latLonPointGeoReference"
  },
  "administrativeReference": {
    "name": "Simacan",
    "street": "Valutaboulevard",
    "houseNumber": "16",
    "postalCode": "1234AB",
    "city": "Amersfoort"
  },
  "type": "warehouse"
}
```

- A location optionally has `contactDetails`:
```json
    "contactDetails": [
      {
        "value": "Simacan",
        "type": "name"
      },
      {
        "value": "0612345678",
        "type": "phone"
      },
      {
        "value": "sim@can.com",
        "type": "email"
      }
    ]
```

**2b.** Lastly, it is possible to provide an 'address-less' location, that only consists of a name and coordinates. These locations are always unmanged:
```json
     "location": {
       "entity": {
         "id": "13022ee7-6c11-42fd-866b-5c10256a77a3",
         "name": "Location on some road",
         "geoReference": {
           "lat": 51.9445,
           "lon": 4.369,
           "type": "latLonPointGeoReference"
         },
         "type": "customer"
       },
       "associationType": "inline"
     },
```

## Variations
The above trip information is the most common type of trip in Simacan. However there are variations on this trip possible.

### Cancelations
It is also possible to cancel a trip with OTM5. This message contains the trip ID, the actors for that trip, and the status 'cancelled':

```json
{
  "id": "b4c9036f-8d97-4aab-ac25-34da94976849",
  "status": "cancelled",
  "actors": [
    {
      "restriction": {
        "externalAttributes": {
          "carrierId": "carrier"
        }
      },
      "entityType": "actor",
      "roles": [ "carrier" ],
      "associationType": "attributeRestriction"
    },
    {
      "restriction": {
        "externalAttributes": {
          "shipperId": "shipper"
        }
      },
      "entityType": "actor",
      "roles": [ "shipper" ],
      "associationType": "attributeRestriction"
    }
  ]
}
```

### Consignor & consignee
A 'regular' trip has one shipper and one carrier. However, there are also trips that have a single carrier, but consignments are delivered from and to
various different actors. Such trips are known as _groupage trips_. These trips differ from regular trips because the shipper actor is not present, but
instead a _consignee_ actor (buyer of the goods) and a _consignor_ actor (seller of the goods) are present on _each_ consignment. Note that the consignor
and consignee can be the same actor:

The trip actor(s) thus look like this:
```json
   "actors": [
    {
      "associationType": "attributeRestriction",
      "restriction": {
        "externalAttributes": {
          "carrierId": "<my-carrier>"
        }
      },
      "entityType": "actor",
      "roles": ["carrier"]
    }
]
```

And a consignment can look like this:
```json
{
  "id": "a556da93-46f3-4df5-8269-263bb337fde4",
  "externalAttributes": {
    "orderId": "<order id>",
    "userId": "<user id>",
    "trackTraceCode": "<t&t code>"
  },
  "actors": [
    {
      "associationType": "inline",
      "entity": {
        "name": "<receiving-party>"
      },
      "roles": [ "consignor", "consignee" ]
    }
  ],
  "type": "Frozen goods",
  "goods": [
    {
      "entity": {
        "id": "b74d1b1a-905a-42dd-91be-1fe77f443467",
        "remark": "Be careful not to melt it!",
        "barCode": "123456789",
        "quantity": 3,
        "productType": "Ice cream",
        "type": "items"
      },
      "associationType": "inline"
    }
  ]
}
```

It is also possible to split the actor into two:
```json
  "actors": [
    {
      "associationType": "inline",
      "entity": {
        "name": "consignor-name"
      },
      "roles": [ "consignor"]
    },
    {
      "associationType": "inline",
      "entity": {
        "name": "consignee-name"
      },
      "roles": [ "consignee"]
    },
  ]
```

### Trip vehicle data
Regardless if you provide a regular trip or a groupage trip, in both cases it is also possible to provide vehicle data.
The vehicle data should at least contain _either_ a licensePlate or an external ID (or both). On top of that it is possible,
but not required to provide a vehicle type and fuel type:
```json
    "vehicle": {
      "entity": {
        "id": "3de444dd-81cf-4e69-b27b-82e92e0dbda3",
        "externalAttributes": {
          "vehicleId": "my-external-id"
        },
        "vehicleType": "truck",
        "fuel": "Euro95",
        "licensePlate": "AA-12-BB"
      },
      "associationType": "inline"
    }
```

If you provide a vehicle it is also possible to provide driver and/or trailer. You can add a driver by adding an actor to the trip (on top of the
shipper and carrier):
```json
   "actors": [
    {
      "associationType": "attributeRestriction",
      "restriction": {
        "externalAttributes": {
          "carrierId": "<my-carrier>"
        }
      },
      "entityType": "actor",
      "roles": ["carrier"]
    },
    {
      "associationType": "attributeRestriction",
      "restriction": {
        "externalAttributes": {
          "shipperId": "<my-shipper>"
        }
      },
      "entityType": "actor",
      "roles": ["shipper"]
    },
    {
      "associationType": "attributeRestriction",
      "restriction": {
        "externalAttributes": {
          "driverId": "<name of the driver>"
        }
      },
      "entityType": "actor",
      "roles": ["driver"]
    }
]
```

Lastly, it is possible to attach a trailer by adding an `attachTransportEquipment` action on the first stop, before or after loading and unloading:
```json
   {
    "entity": {
      "id": "028ad890-38fb-44d8-a0d3-304f0dbe8e31",
      "transportEquipment": {
        "entity": {
          "id": "2fbe0dfe-c15a-4cee-a807-f462318d6f35",
          "licensePlate": "00-AAA-0"
        },
        "associationType": "inline"
      },
      "startTime": "2021-06-28T04:00:00Z",
      "endTime": "2021-06-28T04:01:00Z",
      "actionType": "attachTransportEquipment"
    },
    "associationType": "inline"
   }
```


## Full message example
Combining the ruleset above and taking into account required and optional fields, we can compose the following message.
This is an example message, with three stops, all filled with dummy values. You could copy and paste and replace this with your own data.

```json
{
  "id": "8aef6638-3828-45b6-9391-3e2882512345",
  "name": "OTM5 Trip Example",
  "externalAttributes": {
    "tripId": "25/04/21|OTM5-EXAMPLE-TRIP"
  },
  "actors": [{
    "associationType": "attributeRestriction",
    "restriction": {
      "externalAttributes": {
        "carrierId": "bbf1e201-c1a3-4205-a496-04a7e6f12345"
      }
    },
    "entityType": "actor",
    "roles": ["carrier"]
  },
  {
    "associationType": "attributeRestriction",
    "restriction": {
    "externalAttributes": {
      "shipperId": "08d6740f-0f4c-484f-8c0b-6c69f0d54321"
      }
  },
    "entityType": "actor",
    "roles": ["shipper"]
  }
],
  "actions": [{
    "entity": {
      "id": "66f041f1-cd50-4c0f-9c1a-dea046909876",
      "name": "S",
      "externalAttributes": {
        "stopId": "25/04/21|OTM5-EXAMPLE-TRIP-start"
      },
      "lifecycle": "planned",
      "location": {
        "associationType": "attributeRestriction",
        "restriction": {
          "externalAttributes": {
            "locationId": "HUB Amsterdam"
          }
        },
        "entityType": "location"
      },
      "startTime": "2021-04-25T17:36:00Z",
      "endTime": "2021-04-25T17:51:00Z",
      "actions": [{
        "entity": {
          "id": "e86bd334-f62c-45fe-8a8b-661cde187654",
          "consignment": {
            "entity": {
              "id": "6d0931ef-f9a1-4267-be7c-758aef154321",
              "type": "Products to load",
              "goods": [{
                "entity": {
                  "id": "3ddcac3b-c349-4e08-bab5-f39925145678",
                  "barCode": "x-0006",
                  "quantity": 12,
                  "productType": "Newspaper",
                  "type": "items"
                },
                "associationType": "inline"
              }, {
                "entity": {
                  "id": "86511ae4-8956-4df3-b3c5-0c0efc355555",
                  "barCode": "x-0012",
                  "quantity": 1,
                  "productType": "Book",
                  "type": "items"
                },
                "associationType": "inline"
              }, {
                "entity": {
                  "id": "7a6f6039-2f01-40a7-a557-6f2dbf022222",
                  "barCode": "x-0013",
                  "quantity": 1,
                  "productType": "Champagne",
                  "type": "items"
                },
                "associationType": "inline"
              }]
            },
            "associationType": "inline"
          },
          "startTime": "2021-04-25T17:36:00Z",
          "endTime": "2021-04-25T17:51:00Z",
          "actionType": "load"
        },
        "associationType": "inline"
      }],
      "constraint": {
        "entity": {
          "id": "be406065-048e-40c6-b212-0d0561188888",
          "value": {
            "startTime": "2021-04-25T17:35:00Z",
            "endTime": "2021-04-25T18:35:00Z",
            "type": "timeWindowsConstraint"
          }   
        },
        "associationType": "inline"
      },
      "actionType": "stop"
    },
    "associationType": "inline"
  }, {
    "entity": {
      "id": "1716d72d-d480-4035-9b19-5a92bfc88888",
      "name": "1",
      "externalAttributes": {
        "stopId": "25/04/21|OTM5-EXAMPLE-TRIP-SOME-STOP"
      },
      "lifecycle": "planned",
      "remark": "Doorbell is broken, please knock on door, place on doormat when not home",
      "location": {
        "entity": {
          "id": "dff6de51-a24c-4f8b-8a12-ea8205577777",
          "name": "Shirley",
          "geoReference": {
            "lat": 52.32686,
            "lon": 4.88846,
            "type": "latLonPointGeoReference"
          },
          "type": "customer",
          "administrativeReference": {
            "name": "Shirley",
            "street": "Nieuw Herlaer 2",
            "postalCode": "1083 BD",
            "city": "Amsterdam",
            "country": "NL"
          },
          "contactDetails": [{
            "value": "Shirley",
            "type": "firstName"
          }, {
            "value": "Shirley de Boer",
            "type": "name"
          }, {
            "value": "+31612345678",
            "type": "phone"
          }, {
            "value": "shirleydeboer@shotmail.com",
            "type": "email"
          }]
        },
        "associationType": "inline"
      },
      "startTime": "2021-04-25T18:01:00Z",
      "endTime": "2021-04-25T18:03:00Z",
      "actions": [{
        "entity": {
          "id": "3f992c74-867d-41f0-a9db-794ffa364444",
          "lifecycle": "planned",
          "consignment": {
            "entity": {
              "id": "a359a0f6-d695-45d6-9fa8-afcfb7233333",
              "externalAttributes": {
                "orderId": "6028716",
                "userId": "599271",
                "trackTraceCode": "602CLV05",
                "orderType": "Subscriptions"
              },
              "type": "OVERIG",
              "goods": [{
                "entity": {
                  "id": "86511ae4-8956-4df3-b3c5-0c0efc355555",
                  "barCode": "x-0012",
                  "quantity": 1,
                  "productType": "Book",
                  "type": "items"
                },
                "associationType": "inline"
              }, {
                "entity": {
                  "id": "3ddcac3b-c349-4e08-bab5-f39925145678",
                  "barCode": "x-0006",
                  "quantity": 1,
                  "productType": "Newspaper",
                  "type": "items"
                },
                "associationType": "inline"
              }]
            },
            "associationType": "inline"
          },
          "startTime": "2021-04-25T18:01:00Z",
          "endTime": "2021-04-25T18:03:00Z",
          "actionType": "unload"
        },
        "associationType": "inline"
      }],
      "constraint": {
        "entity": {
          "id": "6d6e0ab3-9ae0-4041-96bf-047861332222",
          "value": {
            "startTime": "2021-04-25T18:00:00Z",
            "endTime": "2021-04-25T19:00:00Z",
            "type": "timeWindowsConstraint"
          }   
        },
        "associationType": "inline"
      },
      "actionType": "stop"
    },
    "associationType": "inline"
  }, {
    "entity": {
      "id": "7b77e80d-527b-4700-8239-8d077fd99999",
      "name": "2",
      "externalAttributes": {
        "stopId": "25/04/21|OTM5-EXAMPLE-TRIP-SOME-STOP-2"
      },
      "lifecycle": "planned",
      "remark": "Leave at neighbours when not at home.",
      "location": {
        "entity": {
          "id": "ece51c99-0a6c-458d-ab44-91c77549bbbb",
          "name": "Martin",
          "geoReference": {
            "lat": 52.31945,
            "lon": 4.88408,
            "type": "latLonPointGeoReference"
          },
          "type": "customer",
          "administrativeReference": {
            "name": "Martin",
            "street": "Aanloop 3",
            "postalCode": "1183SZ",
            "city": "Amstelveen",
            "country": "NL"
          },
          "contactDetails": [{
            "value": "Martin",
            "type": "firstName"
          }, {
            "value": "Martin Golf",
            "type": "name"
          }, {
            "value": "+31687654321",
            "type": "phone"
          }, {
            "value": "martingolf@igoogle.nl",
            "type": "email"
          }]
        },
        "associationType": "inline"
      },
      "startTime": "2021-04-25T18:04:00Z",
      "endTime": "2021-04-25T18:06:00Z",
      "actions": [{
        "entity": {
          "id": "79597251-0aad-4401-9e34-e6367ef4ffff",
          "lifecycle": "planned",
          "consignment": {
            "entity": {
              "id": "e5f0528c-32b8-4a57-af15-57c297db4444",
              "externalAttributes": {
                "orderId": "6021923",
                "userId": "199283",
                "trackTraceCode": "60ABC04",
                "orderType": "Subscriptions"
              },
              "type": "OVERIG",
              "goods": [{
                "entity": {
                  "id": "7a6f6039-2f01-40a7-a557-6f2dbf022222",
                  "barCode": "x-0013",
                  "quantity": 1,
                  "productType": "Champagne",
                  "type": "items"
                },
                "associationType": "inline"
              }]
            },
            "associationType": "inline"
          },
          "startTime": "2021-04-25T18:04:00Z",
          "endTime": "2021-04-25T18:06:00Z",
          "actionType": "unload"
        },
        "associationType": "inline"
      }],
      "constraint": {
        "entity": {
          "id": "30918d21-763c-4287-bc85-b6f2436f2222",
          "value": {
            "startTime": "2021-04-25T18:00:00Z",
            "endTime": "2021-04-25T19:00:00Z",
            "type": "timeWindowConstraint"
          }
        },
        "associationType": "inline"
      },
      "actionType": "stop"
    },
    "associationType": "inline"
  }]
}
```

## Updating your planning
Now that you have seen the appropriate format, you should also be aware that it is possible to update a planning
that you sent to us before. It is important that sent messages always contains a full state, so a full planning.
Any IDs we receive, will always be matched to any previous data, wherever possible.
A trip id (UUID) used, that remained the same matched to a previous trip ID, will update the known trip. 
A new trip id (UUID) will secondly be checked on externalAttributes>tripId. If the UUID and the tripId don't match 
with a previous id, the new trip ID will be considered as a new trip. 
Stop IDs that remain the same, will be considered as the previous sent stop or changes to the previous sent stop.
New stop IDs are considered as new stops. Removed stop IDs will be marked as cancelled stops.
