Distributed Order Management System (Routing)

The distributed order management system is the heart of the fulfillmenttools platform. In this part of our tutorial series we configure the DOMS for our fictional company LU.XY fashion.

As you might have seen in the detailed article, our distributed order management system (or short DOMS) helps you route orders. To find the optimal facility for fulfilling each order, there are fences (must criteria) and ratings (which facility is the best). Whereas in old times there were static rulesets, the fulfillmenttools platform enables them to be dynamic, choosing the best facility at the moment. On this page, you see how to configure the DOMS using the REST API. This can also be configured when logged in as an administrator via the Backoffice.

Fences Fences work a bit like filters as they filter out facilities that don't match the criteria that are defined. A detailed list of the standard options can be found here, you can also create your own criteria using custom fences. LU.XY wants to give their customers a great experience, so they want the service type ordered and the service type offered by the facility to match. They are connected to a marketplace which decides from which facility LU.XYcan fulfil. To have the DOMS handling that we need to enable these fences:

  • Service Type

  • Preselected facilities

Ratings After we fenced some facilities out, some facilities will be left. Without any ratings between those, the facility receiving the pick job would be picked randomly by the platform. To distribute the pickjob between the facilities, the DOMS allows us to rate each facility in terms of fulfilling that pickjob.

LU.XY wants to prefer the facility closest to the customer and with the fastest delivery speed. As the employees have KPIs based on the performance, the workload should also be balanced between the facilities. Additionally, they have categorised their stores into A stores and B stores. A stores perform very well with walk-in customers which is why they want to prefer the B stores for online orders. Therefore these ratings come into place:

  • Geodistance

  • Workload balancing

  • Custom fence based on the facility rating

We will get into those ratings into detail in a bit. To make every customer as happy as possible, they want to try out splitting orders, which means we also have to active this feature in the routing ruleset.

Apply the standard routing rules

Now we can set the other routing options. First, we should GET the current ruleset using the GET routing configuration endpoint:

curl --location 'https://your.api.fulfillmenttools.com/api/configurations/routing' \
--header 'Authorization: Bearer <TOKEN>'
200 OK response containing the current rules
{
    "version": 13,
    "created": "2023-07-10T12:55:06.393Z",
    "globalRoutingConfiguration": {
        "defaultPrice": 10
    },
    "lastModified": "2024-01-30T10:05:20.077Z",
    "prioritizationRules": [],
    "routingRule": {
        "fences": [
            {
                "implementation": "FACILITY-BUSINESSTYPE",
                "active": false,
                "id": "07573113-10b1-44d9-b9ea-28c343684a32",
                "name": "Service type",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Facilities that do not support the service type required by an order are not taken into consideration when performing an order routing."
            },
            {
                "implementation": "STOCK-AVAILABILITY",
                "active": false,
                "id": "d98effb6-cf9f-4aeb-889f-9b1e876463ac",
                "name": "Complete stock",
                "supportedModes": [
                    "static",
                    "reactive"
                ],
                "activeMode": "static",
                "description": "Facilities that do not have all ordered items in stock are not taken into consideration when performing an order routing."
            },
            {
                "implementation": "FACILITY-CARRIERAVAILABILITY",
                "active": false,
                "id": "5e0d9990-331f-407c-9b50-d6ba73e2bdc0",
                "name": "Carrier availability",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Facilities that do not support a specific carrier which has been requested by a customer are not taken into consideration when performing an order routing."
            },
            {
                "implementation": "FACILITY-COUNTRY",
                "active": false,
                "id": "8a23fd25-9023-45bf-8aab-3c56c24ba2e5",
                "name": "Country borders",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Cross-country routing is prevented."
            },
            {
                "implementation": "FACILITY-PICKING-TIME-CAPACITY",
                "active": false,
                "id": "33c5ff6d-a0a1-4b91-be2c-a49c72d0d196",
                "name": "Facility capacity",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Facilities, for which the maximum amount of orders that can be fulfilled has been exceeded are not taken into consideration when performing an order routing."
            },
            {
                "implementation": "PRESELECTED-FACILITY",
                "active": true,
                "id": "a68c4f4e-dd31-4eb2-917b-d21f18150008",
                "name": "Preselected facilities",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Facilities that are not specified in a predefined list are not taken into consideration when performing an order routing."
            },
            {
                "implementation": "SAMEDAY-POSSIBLE",
                "active": false,
                "id": "204899bb-1247-45c2-a26f-aae4761b7ec5",
                "name": "Same Day delivery possible",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Facilities that cannot ensure a same day delivery are not taken into consideration when performing an order routing."
            }
        ],
        "ratings": [
            {
                "implementation": "STOCK-BALANCING",
                "active": false,
                "id": "de855bab-b318-4815-8a71-a26658ebd4e9",
                "name": "Stock balancing",
                "maxPenalty": 0,
                "description": "Facilities that have high stock levels of the ordered items are favored by this rating."
            },
            {
                "implementation": "GEO-DISTANCE",
                "active": false,
                "id": "07bceadb-95ed-49f0-aa3c-73816ba17fc4",
                "name": "Geodistance",
                "maxPenalty": 0,
                "description": "Facilities that are nearby a consumer are favored by this rating."
            },
            {
                "implementation": "TURNOVER",
                "active": false,
                "id": "bd9d5cb8-2d2b-4698-b7f2-7e2b67fe5c8e",
                "name": "Maximizing turnover",
                "maxPenalty": 0,
                "description": "Facilities that generate the highest turnover based on what the customer has ordered are favored by this rating."
            },
            {
                "implementation": "STOCK-AVAILABILITY",
                "active": false,
                "id": "a7eed71c-ee15-4f04-8d99-74cab2c31de8",
                "name": "Stock availability",
                "maxPenalty": 0,
                "description": "Facilities that have the highest amount of items in stock that are ordered by a customer are favored by this rating."
            },
            {
                "implementation": "WORKLOAD-BALANCING",
                "active": false,
                "id": "296b8469-2e54-440a-a793-c38514755394",
                "name": "Workload balancing",
                "maxPenalty": 0,
                "description": "Facilities that have a low workload while performing a routing decision are favored by this rating."
            },
            {
                "implementation": "MATCHING-BUSINESSTYPE",
                "active": false,
                "id": "c2207eb3-5cf5-491c-b4bd-d148d5e9b97b",
                "name": "Matching service type",
                "maxPenalty": 0,
                "description": "Facilities that have the same service type as requested in the order are favored by this rating."
            },
            {
                "implementation": "PREFER-STORE",
                "active": false,
                "id": "68f870f0-f379-466f-83d5-6ee565bf6506",
                "name": "Prefer stores",
                "maxPenalty": 0,
                "description": "Facilities that have the facility type store are favored by this rating."
            },
            {
                "implementation": "PREFER-WAREHOUSE",
                "active": false,
                "id": "ba888667-b678-4af4-bcad-eee92c92bea7",
                "name": "Prefer warehouses",
                "maxPenalty": 0,
                "description": "Facilities that have the facility type warehouse are favored by this rating."
            },
            {
                "implementation": "ZONE",
                "active": false,
                "id": "34975c54-7ee2-41cd-b0ce-e15d0fa2b2dc",
                "name": "Stocks in preferred zone",
                "maxPenalty": 0,
                "description": "Facilities where the ordered inventory is located in a preferred zone are given priority in routing."
            },
            {
                "implementation": "EXPIRY-DATE",
                "active": false,
                "id": "1d908d52-b1ac-42ba-a81b-23e66396f6f4",
                "name": "Expiration/Best-before date",
                "maxPenalty": 0,
                "description": "Facilities where demanded inventory has a shorter expiration date are given priority in routing. Markdowns are reduced as a result."
            },
            {
                "implementation": "CAPACITY",
                "active": false,
                "id": "8fa06ea4-eb88-473b-89bc-84c0382e97d0",
                "name": "Capacity",
                "maxPenalty": 0,
                "description": "Facilities where capacity for fulfillment is available in a timely manner are given priority in routing."
            },
            {
                "implementation": "DELIVERY-COSTS",
                "active": false,
                "id": "b134593a-6e90-4214-ad17-66014186b2ce",
                "name": "Lowest delivery fee",
                "maxPenalty": 0,
                "description": "TODO"
            },
            {
                "implementation": "DELIVERY-TIME",
                "active": false,
                "id": "22c92c9c-2766-41e7-a2df-f6299022bb9a",
                "name": "Fastest delivery speed",
                "maxPenalty": 0,
                "description": "TODO"
            }
        ],
        "orderSplit": {
            "active": false,
            "orderSplitType": "FIXED_COUNT"
        }
    }
}

globalRoutingConfiguration.defaultPrice: This is used for the maximizing turnover rating and should be a standard value for the industry, e.g. 500 EUR for tech, 100 EUR for fashion.

prioritizationRules: has no more impact today.

routingRule.fences: Here you can find all fences there are available without using customized ones. To active the ones we talked about earlier, we need the properties id, active and activeMode.

routingRule.ratings: Here you can find all fences support by default. The property maxPenalty describes the weighting that each rating has in the routing decision. For example a maxPenalty of 2 is not as important and has a lower weight than maxPenalty of 8. This could be any number. By default you can set this penalty in the frontend between 1 and 10, in the API higher values are possible.

orderSplit: Here you can either activate or deactive the split of orders.

In our case we want to activate the order split and the fences mentioned above. The weighting of the ratings would look like that:

  • GEO-DISTANCE 1

  • WORKLOAD-BALANCING 4

Set the desired the desired rules using the PATCH routing configuration endpoint,
curl --location --request PATCH 'https://your.api.fulfillmenttools.com/api/configurations/routing' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <TOKEN>' \
--data '{
  "version": 13,
  "actions": [
    {
      "action": "ModifyOrderSplit",
      "active":true
    },
    {
      "action":"ModifyFence",
      "id": "07573113-10b1-44d9-b9ea-28c343684a32",
      "active": true,
      "activeMode": "static"
    },
    {
        "action": "ModifyFence",
        "id": "a68c4f4e-dd31-4eb2-917b-d21f18150008",
        "active": true,
        "activeMode": "static"
    },
    {
        "action": "ModifyRating",
        "id": "07bceadb-95ed-49f0-aa3c-73816ba17fc4",
        "maxPenalty": 1
    },
    {
        "action": "ModifyRating",
        "id": "296b8469-2e54-440a-a793-c38514755394",
        "maxPenalty": 4
    }
  ]
}'
200 OK response containing the adjusted routing configuration with a new version
{
    "id": "routing",
    "version": 14,
    "created": "2023-07-10T12:55:06.393Z",
    "globalRoutingConfiguration": {
        "defaultPrice": 10
    },
    "lastModified": "2024-01-31T12:48:35.274Z",
    "prioritizationRules": [],
    "routingRule": {
        "fences": [
            {
                "implementation": "FACILITY-BUSINESSTYPE",
                "active": true,
                "id": "07573113-10b1-44d9-b9ea-28c343684a32",
                "name": "Service type",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Facilities that do not support the service type required by an order are not taken into consideration when performing an order routing."
            },
            {
                "implementation": "STOCK-AVAILABILITY",
                "active": false,
                "id": "d98effb6-cf9f-4aeb-889f-9b1e876463ac",
                "name": "Complete stock",
                "supportedModes": [
                    "static",
                    "reactive"
                ],
                "activeMode": "static",
                "description": "Facilities that do not have all ordered items in stock are not taken into consideration when performing an order routing."
            },
            {
                "implementation": "FACILITY-CARRIERAVAILABILITY",
                "active": false,
                "id": "5e0d9990-331f-407c-9b50-d6ba73e2bdc0",
                "name": "Carrier availability",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Facilities that do not support a specific carrier which has been requested by a customer are not taken into consideration when performing an order routing."
            },
            {
                "implementation": "FACILITY-COUNTRY",
                "active": false,
                "id": "8a23fd25-9023-45bf-8aab-3c56c24ba2e5",
                "name": "Country borders",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Cross-country routing is prevented."
            },
            {
                "implementation": "FACILITY-PICKING-TIME-CAPACITY",
                "active": false,
                "id": "33c5ff6d-a0a1-4b91-be2c-a49c72d0d196",
                "name": "Facility capacity",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Facilities, for which the maximum amount of orders that can be fulfilled has been exceeded are not taken into consideration when performing an order routing."
            },
            {
                "implementation": "PRESELECTED-FACILITY",
                "active": true,
                "id": "a68c4f4e-dd31-4eb2-917b-d21f18150008",
                "name": "Preselected facilities",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Facilities that are not specified in a predefined list are not taken into consideration when performing an order routing."
            },
            {
                "implementation": "SAMEDAY-POSSIBLE",
                "active": false,
                "id": "204899bb-1247-45c2-a26f-aae4761b7ec5",
                "name": "Same Day delivery possible",
                "supportedModes": [
                    "static"
                ],
                "activeMode": "static",
                "description": "Facilities that cannot ensure a same day delivery are not taken into consideration when performing an order routing."
            }
        ],
        "ratings": [
            {
                "implementation": "STOCK-BALANCING",
                "active": false,
                "id": "de855bab-b318-4815-8a71-a26658ebd4e9",
                "name": "Stock balancing",
                "maxPenalty": 0,
                "description": "Facilities that have high stock levels of the ordered items are favored by this rating."
            },
            {
                "implementation": "GEO-DISTANCE",
                "active": false,
                "id": "07bceadb-95ed-49f0-aa3c-73816ba17fc4",
                "name": "Geodistance",
                "maxPenalty": 1,
                "description": "Facilities that are nearby a consumer are favored by this rating."
            },
            {
                "implementation": "TURNOVER",
                "active": false,
                "id": "bd9d5cb8-2d2b-4698-b7f2-7e2b67fe5c8e",
                "name": "Maximizing turnover",
                "maxPenalty": 0,
                "description": "Facilities that generate the highest turnover based on what the customer has ordered are favored by this rating."
            },
            {
                "implementation": "STOCK-AVAILABILITY",
                "active": false,
                "id": "a7eed71c-ee15-4f04-8d99-74cab2c31de8",
                "name": "Stock availability",
                "maxPenalty": 0,
                "description": "Facilities that have the highest amount of items in stock that are ordered by a customer are favored by this rating."
            },
            {
                "implementation": "WORKLOAD-BALANCING",
                "active": false,
                "id": "296b8469-2e54-440a-a793-c38514755394",
                "name": "Workload balancing",
                "maxPenalty": 4,
                "description": "Facilities that have a low workload while performing a routing decision are favored by this rating."
            },
            {
                "implementation": "MATCHING-BUSINESSTYPE",
                "active": false,
                "id": "c2207eb3-5cf5-491c-b4bd-d148d5e9b97b",
                "name": "Matching service type",
                "maxPenalty": 0,
                "description": "Facilities that have the same service type as requested in the order are favored by this rating."
            },
            {
                "implementation": "PREFER-STORE",
                "active": false,
                "id": "68f870f0-f379-466f-83d5-6ee565bf6506",
                "name": "Prefer stores",
                "maxPenalty": 0,
                "description": "Facilities that have the facility type store are favored by this rating."
            },
            {
                "implementation": "PREFER-WAREHOUSE",
                "active": false,
                "id": "ba888667-b678-4af4-bcad-eee92c92bea7",
                "name": "Prefer warehouses",
                "maxPenalty": 0,
                "description": "Facilities that have the facility type warehouse are favored by this rating."
            },
            {
                "implementation": "ZONE",
                "active": false,
                "id": "34975c54-7ee2-41cd-b0ce-e15d0fa2b2dc",
                "name": "Stocks in preferred zone",
                "maxPenalty": 0,
                "description": "Facilities where the ordered inventory is located in a preferred zone are given priority in routing."
            },
            {
                "implementation": "EXPIRY-DATE",
                "active": false,
                "id": "1d908d52-b1ac-42ba-a81b-23e66396f6f4",
                "name": "Expiration/Best-before date",
                "maxPenalty": 0,
                "description": "Facilities where demanded inventory has a shorter expiration date are given priority in routing. Markdowns are reduced as a result."
            },
            {
                "implementation": "CAPACITY",
                "active": false,
                "id": "8fa06ea4-eb88-473b-89bc-84c0382e97d0",
                "name": "Capacity",
                "maxPenalty": 0,
                "description": "Facilities where capacity for fulfillment is available in a timely manner are given priority in routing."
            },
            {
                "implementation": "DELIVERY-COSTS",
                "active": false,
                "id": "b134593a-6e90-4214-ad17-66014186b2ce",
                "name": "Lowest delivery fee",
                "maxPenalty": 0,
                "description": "TODO"
            },
            {
                "implementation": "DELIVERY-TIME",
                "active": false,
                "id": "22c92c9c-2766-41e7-a2df-f6299022bb9a",
                "name": "Fastest delivery speed",
                "maxPenalty": 0,
                "description": "TODO"
            }
        ],
        "orderSplit": {
            "active": true,
            "orderSplitType": "FIXED_COUNT",
            "activeForSameDay": false,
            "fixedCountConfiguration": {
                "maxSplitCount": 1
            }
        }
    }
}

Set facility rating

The facility rating is done using a tag for the rating which is patched to the facility. Therefore, we first need to create a tag prior to link that rating tag to a facility. For achieving that, we can POST the tag to the API's endpoint:

curl --location 'https://your.api.fulfillmenttools.com/api/tags' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <TOKEN>' \
--data '{
  "id": "facilityRating",
  "allowedValues": [
    "A",
    "B"
  ]
}'

The response should be 201 CREATED response reading back the provided information:

{
    "id": "facilityRating",
    "allowedValues": [
        "A",
        "B"
    ],
    "version": 1,
    "created": "2024-01-30T14:58:24.468Z",
    "lastModified": "2024-01-30T14:58:24.468Z"
}

Now we can add this tag to a facility using the facility endpoint with a PATCH call:

curl --location --request PATCH 'https://yoyr.api.fulfillmenttools.com/api/facilities/<facilityId>' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <TOKEN>' \
--data '{
    "version": 3,
    "actions": [
        {
            "action": "ModifyFacility",
            "tags": [
                {
                    "id": "facilityRating",
                    "value": "B"
                }
            ]
        }
    ]
}'
200 OK response containing the whole facility object:
{
    "name": "LU.XY Fashion flagship store",
    "address": {
        "companyName": "lu.xy fashion GmbH",
        "country": "DE",
        "postalCode": "60316",
        "city": "Frankfurt",
        "street": "Sandweg",
        "houseNumber": "61",
        "phoneNumbers": [
            {
                "value": "+49 69 580775",
                "type": "PHONE",
                "label": "Landline"
            }
        ],
        "emailAddresses": [
            {
                "value": "lu.xy.flagship.FRA1@example.com",
                "recipient": "LU.XY Frankfurt 1"
            }
        ],
        "resolvedCoordinates": {
            "lon": 8.69702981904918,
            "lat": 50.1203720202354
        },
        "resolvedTimeZone": {
            "offsetInSeconds": 3600,
            "timeZoneId": "Europe/Berlin",
            "timeZoneName": "W. Europe Standard Time"
        }
    },
    "locationType": "STORE",
    "tenantFacilityId": "1",
    "status": "ONLINE",
    "services": [
        {
            "type": "SHIP_FROM_STORE"
        },
        {
            "type": "PICKUP"
        }
    ],
    "pickingTimes": {
        "monday": [
            {
                "start": {
                    "hour": 8,
                    "minute": 0
                },
                "end": {
                    "hour": 17,
                    "minute": 0
                },
                "capacity": 20
            }
        ],
        "tuesday": [
            {
                "start": {
                    "hour": 8,
                    "minute": 0
                },
                "end": {
                    "hour": 17,
                    "minute": 0
                },
                "capacity": 20
            }
        ],
        "wednesday": [
            {
                "start": {
                    "hour": 8,
                    "minute": 0
                },
                "end": {
                    "hour": 17,
                    "minute": 0
                },
                "capacity": 20
            }
        ],
        "thursday": [
            {
                "start": {
                    "hour": 8,
                    "minute": 0
                },
                "end": {
                    "hour": 17,
                    "minute": 0
                },
                "capacity": 20
            }
        ],
        "friday": [
            {
                "start": {
                    "hour": 8,
                    "minute": 0
                },
                "end": {
                    "hour": 17,
                    "minute": 0
                },
                "capacity": 15
            }
        ],
        "saturday": [
            {
                "start": {
                    "hour": 8,
                    "minute": 0
                },
                "end": {
                    "hour": 17,
                    "minute": 0
                },
                "capacity": 10
            }
        ]
    },
    "fulfillmentProcessBuffer": 240,
    "capacityEnabled": false,
    "pickingMethods": null,
    "created": "2023-11-14T15:26:43.165Z",
    "lastModified": "2024-01-30T15:06:30.562Z",
    "version": 4,
    "id": "d286e108-698b-4f6c-97b7-21f090f17e46",
    "tags": [
        {
            "id": "facilityRating",
            "value": "B"
        }
    ]
}

This needs to be done for every facility.

Configuring the DOMS toolkit (custom rules)

For some cases, the routing configuration the fulfillmenttools platform offers by default does not match exactly the customer's needs. To give you the best routing experience possible and offer even more routing rulesets, we introduced the DOMS toolkit. In the case of LU.XY fashion, we need a rating for certain facilities. This can be done using the mentioned toolkit.

DOMS Toolkit The DOMS toolkit compares two entities of this list:

  • order

  • facility

  • carrierconnection

The left side is usually an order, the right side might be something else. The toolkit then looks for certain values in the left entity and then rates the existance of certain properties in the other entity.

In LU.XY's case we want all orders to be compared, therefore we need a property which is always present. As every order contains an ID, we expect that value not to be an empty string. For the facility, we need the facilityRating tag set above to match the value B in order to give all facilities rated as B-stores a rating. fulfillmenttools recommends 40 in that case to give this the highest priority.

Create a custom rating with POST to the toolkit rating endpoint
curl --location 'https://your.api.fulfillmenttools.com/api/configurations/routing/toolkit/ratings' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <TOKEN>' \
--data-raw '{
    "entity1": "ORDER",
    "entity2": "FACILITY",
    "active": true,
    "name": "Prefer B Stores",
    "nameLocalized": {
        "de_DE": "Bevorzuge B-Filialen",
        "en_UK": "Prefer B-Stores"
    },
    "description": "Prefer B Stores for all orders",
    "rule": {
        "operator": "EQUALS",
        "leftPart": {
            "predicates": [
                {
                    "entityOperator": "VALUE_NOT_EQUALS",
                    "expectedValue": " ",
                    "propertyPath": "$.id"
                }
            ]
        },
        "rightPart": {
            "predicates": [
                {
                    "entityOperator": "EVERY_VALUE_EQUALS",
                    "expectedValue": "B",
                    "propertyPath": "$.tags[?(@.id === \"FACILITYRATING\")].value"
                }
            ]
        }
    },
    "maxPenalty": 10
}'
201 CREATED response containing the object with ID, version and timestamp added
{
    "entity1": "ORDER",
    "entity2": "FACILITY",
    "active": true,
    "name": "Prefer B Stores",
    "nameLocalized": {
        "de_DE": "Bevorzuge B-Filialen",
        "en_UK": "Prefer B-Stores"
    },
    "description": "Prefer B Stores for all orders",
    "rule": {
        "operator": "EQUALS",
        "leftPart": {
            "predicates": [
                {
                    "entityOperator": "VALUE_NOT_EQUALS",
                    "expectedValue": " ",
                    "propertyPath": "$.id"
                }
            ],
            "predicateConnector": "AND"
        },
        "rightPart": {
            "predicates": [
                {
                    "entityOperator": "EVERY_VALUE_EQUALS",
                    "expectedValue": "B",
                    "propertyPath": "$.tags[?(@.id === \"FACILITYRATING\")].value"
                }
            ],
            "predicateConnector": "AND"
        }
    },
    "maxPenalty": 10,
    "created": "2024-01-31T14:18:52.255Z",
    "lastModified": "2024-01-31T14:18:52.255Z",
    "version": 1,
    "id": "c1104460-4bd2-4082-b4b1-53d1b2787171"
}

The store ratings and routing weights are:

  • A stores: 20

  • B stores: 40

  • C stores: 60

Every custom rule needs it's own API call.

We have now successfull configured the routing rules for LU.XY fashion. Please keep in mind that DOMS can be a complex topic, especially when you're at the start of your integration. If you need any help, your professional services representative is always happy assisting you with the routing configuration.

Use Case Examples

Scenario 1: Routing orders to a facility located in the same area as the customer

In this scenario we received the task of routing orders based on the user's location. Our customer has a very specific use case, where the order from customers located in the zip codes 50xxx and 51xxx should be routed to facilities located in the same areas (both areas are valid regardless of which zip is used).

The first step would be to like in the following example:

Create a fence by using/api/configurations/routing/toolkit/fences
curl -sSL -X POST 'https://your.api.fulfillmenttools.com/api/configurations/routing/toolkit/fences' \
  --header 'Authorization: Bearer <TOKEN>' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "entity1": "ORDER",
    "entity2": "FACILITY",
    "active": true,
    "name": "Fence assign orders based on user ZIP",
    "nameLocalized": {
        "de_DE": "Fence ordnet Aufträge anhand der Postleitzahl des Kunden"
    },
    "description": "",
    "order":0,
    "rule": {
        "operator": "EQUALS",
        "leftPart": {
            "predicateConnector": "OR",
            "predicates": [
                {
                    "entityOperator": "VALUE_EQUALS",
                    "expectedValue": "50",
                    "propertyPath": "$.consumer.addresses[0].postalCode",
                    "transformation": "SUBSTRING",
                    "transformationArgs": [0,2]
                },
          {
                    "entityOperator": "VALUE_EQUALS",
                    "expectedValue": "51",
                    "propertyPath": "$.consumer.addresses[0].postalCode",
                    "transformation": "SUBSTRING",
                    "transformationArgs": [0,2]
                },
            ]
        },
        "rightPart": {
            "predicateConnector": "OR",
            "predicates": [
                {
                    "entityOperator": "VALUE_EQUALS",
                    "expectedValue": "50",
                    "propertyPath": "$.address.postalCode",
                    "transformation": "SUBSTRING",
                    "transformationArgs": [0,2]
                },
                 {
                    "entityOperator": "VALUE_EQUALS",
                    "expectedValue": "51",
                    "propertyPath": "$.address.postalCode",
                    "transformation": "SUBSTRING",
                    "transformationArgs": [0,2]
                },
            ]
        }
    }
}'

The interpretation of this Fence would be:

Orders from zip code areas beginning with 50 or 51 must be routed to facilities in zip code areas 50 or 51.

Some details in the example:

  • The Fence rule consists of two parts, the left part processes the information contained in the order (entity1), and the right part processes the information contained in the facility (entity2). Left part and right part are connected with the operator EQUALS, which means that if the result of evaluating the left part is true then the result of evaluating the right part has to evaluate to true, and analogously when the left side evaluates to false

  • The left part consists of two predicates connected with the OR predicate evaluator. The predicates are similar, as they take the value of the postalCode property of the consumer's address.

  • Both predicates have a transformation, which means that after getting the value of the postalCode, and before evaluating the predicate, this postal code is transformed by using the SUBSTRING transformation. In this case, the first two digits are extracted.

  • Then, for this extracted values, they are compared by using the entity operator, in this case VALUE_EQUALS. This would be interpreted as, "if the extracted value equals the expected value, then this predicate is evalued to true".

  • After evaluating the two left-side predicates individually, they are evaluated as a whole using the predicate operator OR.

  • Analogously, the right-hand side of the rule is evaluated. The result of evaluating the right side and the left side are evaluated again by using the rule operator EQUALS.

Suppose we have an incoming order for an user that is located in an area with postal code 51379, and the fence is evaluating a Facility located in an area with postal code 51355. When evaluating this combination from order and facility with the fence specified above would be:

LEFT PARTRULE OPERATOR (EQUALS)RIGHT PART

LEFT PART PREDICATE 1 EXTRACTION

51379

51355

RIGHT PART PREDICATE 1 EXTRACTION

LEFT PART PREDICATE 1 TRANSFORMATION

51

51

RIGHT PART PREDICATE 1 TRANSFORMATION

LEFT PART PREDICATE 1 EXPECTED VALUE

50

50

RIGHT PART PREDICATE 1 EXPECTED VALUE

LEFT PART PREDICATE 1 EVALUATE EQUALS

FALSE

FALSE

RIGHT PART PREDICATE 1 EVALUATE EQUALS

LEFT PART PREDICATE 2 EXTRACTION

51379

51355

RIGHT PART PREDICATE 2 EXTRACTION

LEFT PART PREDICATE 2 TRANSFORMATION

51

51

RIGHT PART PREDICATE 2 TRANSFORMATION

LEFT PART PREDICATE 2 EXPECTED VALUE

51

51

RIGHT PART PREDICATE 2 EXPECTED VALUE

LEFT PART PREDICATE 2 EVALUATE EQUALS

TRUE

TRUE

RIGHT PART PREDICATE 2 EVALUATE EQUALS

EVALUATE BOTH PREDICATES WITH OR

TRUE OR FALSE -> TRUE

TRUE OR FALSE -> TRUE

EVALUATE BOTH PREDICATES WITH OR

TRUE or TRUE -> TRUE

Result of the Rule evaluation

TRUE

Result of the Rule evaluation

and the facility will be retained as candidate for the routing, because the fence evaluated to true.

Scenario 2: Directly compare values of the entity on the left with the entity on the right

In this scenario we will ignore the evaluation of the left side and the right side of the rule, to perform a direct comparison of values of the entity on the left with the right entity, to handle the following scenario:

Orders with order line items containing a tag with id "CATEGORY" should be routed to facilities that have the same category. Available categories are "DANGEROUS_GOODS" and "SAFE_GOODS".

Create a fence in our system by using the following request
curl -sSL -X POST 'https://your.api.fulfillmenttools.com/api/configurations/routing/toolkit/fences' \
  --header 'Authorization: Bearer <TOKEN>' \
  --header 'Content-Type: application/json' \
  --data-raw '{
  "entity1": "ORDER",
  "entity2": "FACILITY",
  "active": true,
  "name": "Fence ensure respects the product category",
  "comparisonRule": {
    "predicateConnector": "AND",
    "predicates": [
      {
        "entityOperator": "RIGHT_CONTAINS_LEFT",
        "leftPropertyPath": "$.orderLineItems[*].tags[?(@.id === \"CATEGORY\")].value",
        "rightPropertyPath": "$.tags[?(@.id === \"CATEGORY\")].value"
      }
    ]
  },
  "order": 0
}'

Suppose we have an incoming order with an order line item that has the tag {id:"CATEGORY",value:"DANGEROUS_GOODS"}, and we have to facilities, (1) the first which has the tag SAFE_GOODS and (2) the second which has the tag DANGEROUS_GOODS

  • When evaluating the Fence for the (1) first facility, the following will happen:

    • the fence will consider the order and take the value of the orderLineItems.tags[*].value, in this case, the value "[DANGEROUS_GOODS]".

    • the fence will consider the facility take the value of the tag "tags[*].value", in this case for facility (1) it is "[SAFE_GOODS]".

    • then it will apply the entity operator "RIGHT_CONTAINS_LEFT". Because the value "[SAFE_GOODS]" does not contains "[DANGEROUS GOODS]", the fence will evaluate to false, and this facility will be discarded.

  • When evaluating the Fence for the (2) second facility, the following will happen:

    • the fence will consider the order and take the value of the orderLineItems.tags[*].value, in this case, the value "[DANGEROUS_GOODS]".

    • the fence will consider the facility take the value of the tag "tags[*].value", in this case for facility (1) it is "[DANGEROUS_GOODS]".

    • then it will apply the entity operator "RIGHT_CONTAINS_LEFT". Because the value "[DANGEROUS_GOODS]" is contained in "[DANGEROUS GOODS]", the fence will evaluate to true, and this facility will be retained as candidate.

Note: In case the order has many order line items containing many different tags, then all of them will be evaluated and compared with the values from the right part.

Last updated