Mileages (fields format)

Fields, when you want to do it better

The fields endpoints and the fields data structure is useful when building the user experience of the expense interface. When creating a new expense (or mileage, or per-diem) the fields endpoint will inform you of all fields that are available for this expense type. If these input fields are mandatory, what data type they can receive, if it should be visible etc? This is helpful since organizations can differ in their configurations on what is mandatory or visible, and one market will be different from the other.

Your first mileage

Send the first initiating request to this endpoint you need to provide organizationId and expense type CarSpecification as path parameters. You will get something similar to this in the response (stripped since it is very long)

{
    "fields": [
        {
            "name": "Category",
            "property": "categoryId",
            "controlType": "LIST",
            "mandatory": false,
            "visible": true,
            "value": "8c797e62ae4200488f34bd372a180187",
            "errorText": null,
            "requiresUpdate": true,
            "url": "/me/organizations/8ab28cb36900876c016900cbcab4003e/expensetypes/CarSpecification/categories",
            "data": {
                "iconUrl": "https://stage-expense.findity.com/api/resources/8ab28c4f87b4e33901880b5f68870004",
                "id": "8c797e62ae4249488f34bd322a180187",
                "name": "Private car",
                "section": 5
            }
        },
        {
            "name": "Description",
            "property": "verification.description",
            "controlType": "TEXT",
            "mandatory": false,
            "visible": true,
            "value": null,
            "errorText": null,
        },
        {
            "name": "Travel date",
            "property": "verification.travelDate",
            "controlType": "DATE",
            "mandatory": false,
            "visible": true,
            "value": null,
            "errorText": null
        },
        {
            "name": "Route",
            "property": "verification.travelRoute",
            "controlType": "FIELD_GROUP",
            "mandatory": false,
            "visible": true,
            "value": null,
            "errorText": null,
            "requiresUpdate": true,
            "accessorIconUrl": "https://stage-expense.findity.com/api/images/icons/plus.svg",
            "accessorText": "Add travel route",
            "fields": [
                {
                    "name": "Route",
                    "property": "verification.route",
                    "controlType": "MILEAGE_ROUTE",
                    "mandatory": false,
                    "visible": true,
                    "value": null,
                    "errorText": null,
                    "requiresUpdate": true
                },
                {
                    "name": "Distance",
                    "property": "verification.distance",
                    "controlType": "DOUBLE",
                    "mandatory": false,
                    "visible": true,
                    "value": 0.0,
                    "errorText": null,
                    "numberOfDecimals": 2
                }
            ],
        },
        {
            "name": "Vehicle type",
            "property": "verification.carType",
            "controlType": "LIST",
            "mandatory": true,
            "visible": true,
            "value": "COMPANY_CAR_DIESEL",
            "errorText": null,
            "requiresUpdate": true,
            "url": "/me/organizations/8ab28cb36900800c088900cbcab4003e/categories/8c797e62ae4249488f38bd372a180187/cartypes",
            "data": {
                "iconUrl": null,
                "id": "COMPANY_CAR_DIESEL",
                "name": "Company car diesel",
                "section": null
            }
        },
        {
            "name": "Expense report",
            "property": "expenseReportId",
            "controlType": "LIST",
            "mandatory": false,
            "visible": true,
            "value": null,
            "errorText": null,
            "url": "/expensereports?processStatus=DRAFT&excludeCorporateCards=true&organizationId=8ab28cb36900899c016900acabb4004a"
        }
    ]
}

This structure is supposed to be design-driven and can be converted to a user interface that dynamically displays the correct fields. Below is an example of how the Findity app responds to the field payload when creating a new mileage.

The mileage view

The mileage view

You should also check out the include=meta.capabilities query parameter that will respond the available actions that can be performed on the expense. The capabilities can be different depending on the status of the expense, for instance, if it is submitted you can only access it in a read-only way.

"meta": {
  "capabilities": {
    "canBeAddedToReport": true,
    "canBeDeleted": true,
    "canBeEdited": true,
    "canBeSentIn": true
  }

Let's post some mileage data

The same endpoint you used to initialize (GET) the mileage, you can also send a PUT request with new field values or changes. The main purpose of this is to make sure that changes reflect the business logic, for instance, a certain category might have some fields as mandatory or not visible, and other categories might not. Keep an eye on the field named requiresUpdate, this indicates if a change to this section needs a PUT-sent to get an updated response of the field structure. The category field below is a good example that will require an update if it is changed.

   {
        "name": "Category",
        "property": "categoryId",
        "controlType": "LIST",
        "visible": true,
        "value": "8c797e62ae4249488f34bd372a180187",
        "requiresUpdate": true,
        "url": "/me/organizations/8ab28cb36900800c016900cbcab4003e/expensetypes/CarSpecification/categories",
        "data": {
            "iconUrl": "https://stage-expense.findity.com/api/resources/8ab28c4f87b4e81901880b5f68870004",
            "id": "8c797e62ae4249488f34bd372a180187",
            "name": "Private car"
        }
    },

Category

If you only have one category, well then it is easy as we will suggest it to you in the first request you make.

If you have multiple categories, then you want to offer the user to choose from a list which can be retrieved from this endpoint.

Description

Best practice and for clarity it makes sense to let the user add a description of the mileage. This is a TEXT input field

Travel date

The date needs to be entered in ISO 8601 standard format with time as UTC, for example, 2024-12-10T23:00:00Z

Route

A route section is a field group, and we use them when we have a set of fields that belong tightly together. This is how we present the route field group in the Findity app.

The Findity app and field group

The Findity app and field group

You need to provide at least a start origin and an end destination, with optional via points in the way. See this article for more info how to construct this section of the fields payload. Set the route fields and the distance.

You can skip the map and routes if you only want to use distance. Toggle the Manually enter distance field, this will hide the route section. And yup, distance is entered in kilometers.

{
  "name": "Manually enter distance",
  "property": "verification.manualDistance",
  "controlType": "SWITCH",
  "mandatory": false,
  "visible": true,
  "value": true,
  "errorText": null,
  "error": null,
  "custom": false,
  "requiresUpdate": true
},
  {
    "name": "Distance",
    "property": "verification.distance",
    "controlType": "DOUBLE",
    "mandatory": false,
    "visible": true,
    "value": 18.0,
    "errorText": null,
    "error": null,
    "custom": false,
    "numberOfDecimals": 2
  },

If the organization uses the carbon footprint reporting functionality, the petrol type needs to be entered. Available list options can be found by calling the URL.

{
  "name": "Fuel type",
  "property": "verification.tollCostFuelType",
  "controlType": "LIST",
  "mandatory": false,
  "visible": true,
  "value": null,
  "errorText": null,
  "error": null,
  "custom": false,
  "requiresUpdate": true,
  "url": "/me/organizations/8ab28cb36900800c016900cbcab4003e/fueltypes"
},

Save it

To save the mileage, this endpoint is to be used. You take the fields structure from the previous response and send a post request. You need to provide these tree query parameters, format=fields&organizationId={{orgId}}&expenseType=CarSpecification.

If all is okay then you will hopefully get a 201 response and a similar payload as listed below, which contains an id of the newly created mileage expense.

{
    "id": "676f442c34f742d49171587a6ebc6edb",
    "status": "NORMAL",
    "processStatus": "DRAFT",
    "meta": {
        "categoryIconUrl": "https://stage-expense.findity.com/api/resources/8ab28c4f87b4e81901880b5f68870004"
    },
    "personId": "8ab28cb4670c400301670c48cbb7001b",
    "dateCreated": "2024-12-12T16:16:56Z",
    "lastUpdated": "2024-12-12T16:16:56Z",
    "expenseDate": "2024-12-12T14:00:00Z",
    "organizationId": "8ab28cb36900800c016900cbcab4003e",
    "categoryId": "8c797e62ae4249488f34bd372a180187",
    "verification": {
        "id": "9591e9dbef7940519e5d7cb040f826a9",
        "type": "CarSpecification",
        "customFields": [
            {
                "fieldDefinitionId": "3bcb17ce9aee4b29a31bd2ab396c3b8c",
                "value": "1"
            },
            {
                "fieldDefinitionId": "8ab28c4e79f203b90179f20c95f10001"
            },
            {
                "fieldDefinitionId": "8ab28c4e7b87dd1a017b95a9219b005e",
                "value": "2"
            },
            {
                "fieldDefinitionId": "8ab28cb36900800c016900e1d8e403bd"
            },
            {
                "fieldDefinitionId": "e5ff3f61238a4533b9c049497917fba5"
            }
        ],
        "distance": 18.0,
        "distanceUnit": "KM",
        "travelDate": "2024-12-12T14:00:00Z",
        "carType": "PRIVATE_CAR",
        "description": "My first customer trip 2024",
        "destination": "Sundsvalls sporthall, Universitetsallén, Sundsvall, Sweden - Lillkågeträsk, Sweden",
        "roundTrip": true,
        "manualDistance": false,
        "tripDetails": {
            "distanceInTransport": 0.0,
            "distanceOnGravel": 0.0,
            "numberOfPassengers": 0,
            "distanceWithTrailer": 0.0,
            "distanceWithCaravan": 0.0,
            "distanceWithHeavyCaravan": 0.0,
            "distanceWithDog": 0.0,
            "tollCost": 0.0,
            "carRegistrationNumber": ""
        },
        "route": [
            {
                "place": "Sundsvalls sporthall, Universitetsallén, Sundsvall, Sweden",
                "latitude": "62.3945952",
                "longitude": "17.2908799"
            },
            {
                "place": "Lillkågeträsk, Sweden",
                "latitude": "64.9912853",
                "longitude": "20.6792041"
            }
        ],
        "passengers": [],
        "tollCostFuelType": "HYBRID"
    },
    "reimbursementCurrency": "SEK"
}

If the save goes south

If a mandatory field is missing, a format is wrong or something else validates wrong, we will respond with the same fields structure and in the bottom listing one or more validation errors. And a 400 Bad request response code. The property will also

    "validationErrors": [
        {
            "checkType": "BLOCKER",
            "errorCode": "REQUIRED_FIELD",
            "errorMessage": "Required field: Project",
            "property": "e5ff3f61238a4533b9c049497917fba5"
        }
    ]

Normally the validation error refers to a problem with a specific field, you can use the error field to highlight the field to inform the user that something is wrong with a particular field and also present the errorText for additional clarification.

    {
        "name": "Project",
        "property": "e5ff3f61238a4533b9c049497917fba5",
        "controlType": "TEXT",
        "mandatory": true,
        "visible": true,
        "value": null,
        "errorText": "Required field: Project",
        "error": "REQUIRED_FIELD",
        "custom": true
    },