Skip to content

OrderFlow Scripting Guide

Realtime Despatch Software Ltd

Document Version: 4.0.6

Document Built: 2020-10-12

This document and its content is copyright of Realtime Despatch Software Limited. All rights reserved.
You may not, except with our express written permission, distribute, publish or commercially exploit the content.
Any reproduction of part or all of the contents in any form is prohibited.

Courier Selection

Courier Selection

Courier Selection Script

A courier selection script is the means by which a courier and courier service can be automatically associated with a shipment. This association typically takes place at the point at which the shipment is received, but can also be done at a later stage in the order processing workflow.

Courier selection may be based on a number of factors, including any or all of the factors below (often a combination of these) * whether the shipment is international or domestic * the shipment weight * the customer's choice * the value of the order * the type of products in the shipment * the customer's expected service delivery

All of this information can be determined programatically in a Groovy Script using the OrderFlow API.

A Simple Example

The example below performs a courier selection based primarily on the customer's choice, but also on the shipment weight.

def customerchoice = value.deliverySuggestion.code;
def weight = value.weight;
def courierOptions = '';

if (customerchoice == null) {
    throw new IllegalStateException("Unable to determine courier choice.
        Please ensure 'deliverySuggestionCode' shipment attribute is set");
}

customerchoice = customerchoice.toLowerCase();

def courierReference;
def serviceCode;

if (customerchoice.contains("dpd")) {
    courierReference = "dpd";
    serviceCode = "dpd_classic";
}

if (customerchoice.contains("interlink")) {
    s
    if (weight <= 5) {
        courierReference = "dpd";
        serviceCode = "interlink_nextday";

    } else {
        courierReference = "dpd";
        serviceCode = "interlink_nextday_parcel";
    }
}

if (courierReference == null) {
    courierReference = "generic";
    serviceCode = "";
}

values.courierDeliveryInfo.courierReference=courierReference;
values.courierDeliveryInfo.serviceCode=serviceCode;
values.courierDeliveryInfo.courierOptions=courierOptions;

return null;

A few points to note:

  • the customer's choice is determined here using the Groovy expression value.deliverySuggestion.code. If none is set, the script will return an error.
  • the shipment weight is expected to be populated.
  • if no valid courier selection is made, the courier selection is returned

Scripting Context

Scripting Variables

value

Holds an instance of the Shipment for which the courier selection is being performed.

values

Holds an instance of the context map for the courier selection script. Doesn't typically get used directly, but does hold additional referencable data as described next.

values.courierDeliveryInfo

The values.courierDeliveryInfo holds an instance of rtd.courier.info.CourierDeliveryInfo. The fields in this class hold data which maps to the selected courier, service and options, as summarised in the table below.

Courier Delivery Info Fields

Name Description Maps To
courierReference The externalReference value for the selected courier for the shipment shipment.courier.externalReference
serviceCode The selected service code from the courier shipment.deliveryMethod.serviceCode
courierOptions A comma separated list of options shipment.deliveryMethod.courierOptions
mailFormat Mail format set for this shipment shipment.deliveryMethod.mailFormat
lineHaulSiteReference Only applies if line hauling is used. If shipment.lineHaulDestinationSite.externalReference

Note the 'Maps To' column above denotes the Groovy expression that can be used to extract from the shipment the value populated using the courier selection script.

Returning to the initial courier selection script example, we can see how the values.courierDeliveryInfo expression is populated.

values.courierDeliveryInfo.courierReference=courierReference;
values.courierDeliveryInfo.serviceCode=serviceCode;
values.courierDeliveryInfo.courierOptions=courierOptions;
return null;

Note that the return statement in the courier selection script is recommended for OrderFlow 3.8.2 and below in order to ensure that the courier selection result does not get interpreted as JSON text (as per a legacy implementation of the courier selection script that is no longer in widespread use).

Further Examples

The example below selects between domestic and international couriers and services depending upon destination and weight.

def courierReference;
def serviceCode;

def weight = value.weight;
if (weight == null) {
    throw new IllegalStateException("Unable to determine weight for shipment.");
}

def genericNonInternationalCountryCodes = ['UK','GB','IM','GG','JE','IE'];

def countryCode = value.implicitAddress.countryCode;
if (countryCode == null) {
    throw new IllegalStateException("Unable to determine country code for shipment.");
}

def international = !genericNonInternationalCountryCodes.contains(countryCode);

if (international) {
    courierReference = "royalmail_international_netdespatch";
    serviceCode = "IE1E"
}
else {
    if (weight <= 5) {

        courierReference = "royalmail_domestic_netdespatch";
        serviceCode = "TPN01N";

    }
    else {

        courierReference = "royalmail_domestic_netdespatch";
        serviceCode = "TPH01N";

    }
}

if (courierReference == null) {
        courierReference = "generic";
        serviceCode = "";
    }

values.courierDeliveryInfo.courierReference=courierReference;
values.courierDeliveryInfo.serviceCode=serviceCode;
values.courierDeliveryInfo.courierOptions='';

return null;

Useful Expressions

The following Groovy expression are useful when writing courier selection scripts.

Customer Choice

The OrderFlow convention is to receive the delivery suggestion via the shipment delivery suggestion code, as shown in the script below.

def customerchoice = value.deliverySuggestion.code;

Note that the customer choice can sometimes be mapped to an explicit choice in courier. On other occasions, it may contain information on the service level, or expected time to delivery. It tends to be very environment specific.

Country Code

A reliable way to get the ISO two character country code for a shipment is using the script below:

def customerchoice = value.implicitAddress.countryCode;

Note the use of implicitAddress; if a delivery address has been set at the shipment level, then this will be used. Otherwise, the order delivery address will be used.

Post Codes

The post code can retrieved in a similar way to the country code:

def customerchoice = value.implicitAddress.postCode;

The post code is often useful to identify shipments coming from more remote destinations on the British Isles, including the Channel Islands (Jersey, Guernsey), Isle of Man, as well as regions such as Northern Ireland and the Scottish Highlands. For some carriers, different rules and restrictions need to be applied for some of these shipments compared to those in more heavily populated regions of Great Britain.

Order Value

The total price for the order paid by the customer can be found using the following expression:

def price = value.orderItem.totalPrice.gross;

As the total gross price, it includes goods and shipping, as well as all taxes.

Shipping Charges

The shipping charges are set against the order in a similar way:

def shippingCharge = value.orderItem.shippingPrice.gross;

The amount paid by the customer for shipping may certainly affect the courier choice in some environments.

Priority

The shipment priority field is often used to influence courier choice.

def shippingCharge = value.priority;

The priority value is set on a numerical scale, with a higher value used for higher priority shipments. The possible priority values and gradations are not set in stone, and will vary according to need. It is certainly common to at least define a distinction between normal and high priority shipments.

Expected Delivery Date

On occasion, the front end eCommerce system may capture or record an expected delivery date by which the customer is expecting to receive their order.

If set, it can be obtained using the following expression:

def requestedDeliveryDate = value.requestedDeliveryDate;

Country Group

As of OrderFlow 4.2.0.1 the following expression can be used to determine whether the destination country is in a particular country group. This can be useful for scripts that will apply different courier selection for EU countries as opposed to countries outside of the EU.

See below for an example:

def countryLookup = values.countryLookup;
if (!countryLookup.isInCountryGroup('eu',value.implicitAddress.countryCode)) {
    options = 'international_only';
}

Unit Testing

A Note on Unit Testing

This section requires the that you have in place the OrderFlow integration and scripting environment. If you are interested in having this set up in your environment to enable you to write your own unit tests, please contact the Realtime Despatch support team.

For complex courier selection scripts, a accompanying unit test is highly recommended (and mandatory if implemented by Realtime Despatch Technical Staff).

Writing a unit test should generally be done at the same time or even before the courier selection script is written, in line with the principles of Test Driven Development.

Write Test

Unit testing of courier selection can be done using a unit test which extends BaseCourierSelectionScriptTest.

This test which defines a run(packageName, scriptName, shipment) method, which provides a convenient way of setting up the scripting context required for the courier selection script.

The packageName parameter is the package name containing the script. The scriptName is the name of the file containing the courier selection script. The shipment is an instance of rtd.domain.Shipment, which needs to be instantiated in code.

An example of a unit test is shown below:

/**
* Courier selection script test for 'orderflow.courier.selection.script'.
* 
* @author Phil Zoio
*/
public class OrderFlowCourierSelectionScriptTest extends BaseCourierSelectionScriptTest {

    private Shipment shipment;
    private OrderItem orderItem;

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        setupShipment();
    }

    /**
    * Do basic shipment setup
    */
    void setupShipment() {
        shipment = new Shipment();
        orderItem = new OrderItem();
        shipment.setOrderItem(orderItem);
        orderItem.getTotalPrice().setGross(100D);
    }

    void runSelection() {
        run("rtd.orderflow.courier", "orderflow.courier.selection.script", shipment);
    }

    public void testGBHighValue() throws Exception {

        shipment.getAddress().setCountryCode("GB");

        String expectedServiceCode = null;
        String expectedOptions = null;
        expectCourier("dpd", expectedServiceCode, expectedOptions);
    }

    public void testGBLowValue() throws Exception {

        orderItem.getTotalPrice().setGross(10D);
        shipment.getAddress().setCountryCode("GB");

        expectCourier("royalmail_tracked", "TPN01", "serviceOption=signature");
    }

    public void testEU() throws Exception {

        shipment.getAddress().setCountryCode("DE");

        String expectedServiceCode = null;
        String expectedOptions = null;
        expectCourier("ups", expectedServiceCode, expectedOptions);
    }

    public void testUS() throws Exception {

        shipment.getAddress().setCountryCode("US");

        String expectedServiceCode = null;
        String expectedOptions = null;
        expectCourier("ups_international", expectedServiceCode, expectedOptions);
    }

    void expectCourier(String expectedCourier, String expectedServiceCode, String expectedOptions) {
        runSelection();
        System.out.println(courierDeliveryInfo);
        assertEquals(expectedCourier, courierDeliveryInfo.getCourierReference());
        assertEquals(expectedServiceCode, courierDeliveryInfo.getServiceCode());
        assertEquals(expectedOptions, courierDeliveryInfo.getCourierOptions());
    }
}

Note that in writing the unit test, your responsibilities are:

  • identify a scenario that needs to be tested. For this, define the outcome, through a call to the expectCourier(String expectedCourier, String expectedServiceCode, String expectedOptions) method above.
  • set up the shipment instance so that it meets to preconditions for testing the outcome.

You will want to repeat this process for each of the outcomes to be tested, in order that all outcomes are adequately tested.

Write Script

You will then need to write the script in a way that satisfies the outcome. The example script tested using the code above:

def delivery=values.courierDeliveryItem
def countrycode = value.implicitAddress.countryCode;
def euCountries = ['BE','BG','CZ','DK','DE','EE','IE','EL','ES','FR','IT','CY',
                    'LV','LT','LU','HU','MT','NL','AT','PL','PT','RO','SI','SK','FI','SE']
def rmDomestic  =  ['GB','UK'];

def courierReference = null;
def serviceCode = null;
def courierOptions = null;

if (euCountries.contains(countrycode)) {
    courierReference = 'ups';
}
else if (rmDomestic.contains(countrycode)) {
    if (value.orderItem.implicitTotalPrice.gross > 20) {
        courierReference = 'dpd';
    }
    else {
        courierReference='royalmail_tracked';
        serviceCode='TPN01';
        courierOptions='serviceOption=signature';
    }
} // Test to capture all country codes not specifically listed in euCountries and rmDomestic
else {
    courierReference = 'ups_international';
}

values.courierDeliveryInfo.courierReference=courierReference;
values.courierDeliveryInfo.serviceCode=serviceCode;
values.courierDeliveryInfo.courierOptions=courierOptions;
IDE Setup

A screenshot showing these in an Eclipse IDE project environment is shown below.

Courier Selection

Note the use of the test package naming convention, which follows the along the lines: ... In line with this convention, the test and script itself a re contained in the courier subpackage.

MetaPack Booking Code

Documentation to be added.

Hypaship Delivery Group

Documentation to be added.