Batch Selection
Batch Selection
Batch Selection Script
The batch selection script is the script which is used to determine which shipments get picked together, and presented to the packer, in a group.
The batch selection script determines the type of the shipment batch, which in turn defines the grouping mechanism.
For more details on shipment batching, see the 'Batching' chapter in the OrderFlow Advanced Concepts Guide.
Batching Strategies
A wide range of strategies may be employed to determine the batch type of a shipment:
- whether the order is single or multiline, or even the number of lines
- the courier
- the priority (e.g urgent vs normal)
- products with a particular set of characteristics
- order lines being picked from a particular area in the warehouse
The actual logic to be used will depend on business requirements, which may in turn either be driven by commitments to the end customer, or a drive for more efficient warehousing operations.
Batch Types
At a technical rather than business level, the purpose of the batch selection script is to identify the appropriate batch type to be used. Available batch types can be found from the Setup -> Batch Types menu, as shown below.
The identifier for the batch types is in the first column, e.g. singleline
, multiline
.
A valid batch selection script will apply business rules to determine one of the available batch types.
If the batch type needed to implement the required business logic is not present, part of the batch selection implementation task will be to create the new batch type(s).
A Simple Example
The example below is a script which implements very simple logic to select the multiline
batch type for multiline shipments, and the singleline
batch type for single line shipments.
if (value.orderLineCount > 1) return 'multiline'; else return 'singleline';
Note that the return value for the batch selection script is important; the script needs to return the identifier for the required batch type.
Scripting Context
The following scripting variables are available for use in a batch selection script.
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.populators
Holds a reference to populators that can be used to further populate data already in the scripting context.
Consider for example, the shipment. The product associated with the first order line in the shipment can be found for each order line using code such as the following:
def orderLine = shipment.orderLines.iterator().next(); def product = orderLine.product;
However not all fields in the products are automatically populated for efficiency reasons. For example, product attributes would not be directly referencable.
In order to get access to product attributes, you would need to fully populate the product, using the following script.
def orderLine = shipment.orderLines.iterator().next(); def product = orderLine.product; def populatedProduct = values.populators['product'].populate(product);
Note that the populator defined above will not necessarily be available in all scripting environments, but will be available for batch selection.
Useful Expressions
The following expressions and snippets are useful in batch selection scripts:
Priority
The following expression will retrieve the priority of the shipment, which can be used to make decisions on the shipment's urgency.
def priority = value.priority;
Courier
The courier and service reference are often used in batch selection scripts where shipments to go out with particular couriers need to be picked together.
def courier = value.courier?.externalReference; def serviceCode = value.deliveryMethod?.serviceCode;
Line and Item Count
The following expressions can be used to determine whether a shipment has multiple lines or even multiple items. (A shipment with a single line with a quantity of 2 is considered multi-item.)
def multiLine = (value.orderLineCount > 1); def multiQuantity = value.orderItemCount > 1;
Address Information
The following expressions retrieve the country code and post code for a shipment, using the implicitAddress
expression for generality.
The post code value is often used for shipments in more remote destinations on the British Isles.
def countryCode = value.implicitAddress?.countryCode?.toUpperCase(); def postCode = value.implicitAddress?.postCode?.toUpperCase();
A More Complex Example
The example below returns batch types according the following logic:
Batch Types
Batch | Priority | Quantity | Destination |
---|---|---|---|
priority-singleitem | High | Single item | GB and the Channel Islands |
standard-singleitem | Standard | Single item | GB and the Channel Islands |
priority-multiitem | High | Multi-item | GB and the Channel Islands |
standard-multiitem | Standard | Multi-item | GB and the Channel Islands |
priority-singleitem-de | High | Single item | Germany |
standard-singleitem-de | Standard | Single item | Germany |
priority-multiitem-de | High | Multi-item | Germany |
standard-multiitem-de | Standard | Multi-item | Germany |
priority-singleitem-eu | High | Single item | Rest of EU |
standard-singleitem-eu | Standard | Single item | Rest of EU |
priority-multiitem-eu | High | Multi-item | Rest of EU |
standard-multiitem-eu | Standard | Multi-item | Rest of EU |
A script which implements this logic is show below:
def multiQuantity = value.orderItemCount > 1; def countryCode = value.implicitAddress?.countryCode?.toUpperCase(); def domesticCountryCodes = ['GB','UK','JE','GG','IM']; def priority = shipment.priority; def suffix = ''; if (countryCode == 'DE') { suffix = '-de'; } else if (!domesticCountryCodes.contains(countryCode)) { suffix = '-eu'; } if (priority >= 10) { if (multiQuantity) { return 'priority-multiitem'+suffix; } else { return 'priority-singleitem'+suffix; } } else { if (multiQuantity) { return 'standard-multiitem'+suffix; } else { return 'standard-singleitem'+suffix; } }
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 batch 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 batch selection is written, in line with the principles of Test Driven Development.
Write Test
Unit testing of batch selection can be done using a unit test which extends BaseBatchSelectionScriptTest
.
public class OrderFlowBatchSelectionScriptTest extends BaseBatchSelectionScriptTest { private Address address; protected String getScriptPackageName() { String packageName = "rtd.orderflow.batch"; return packageName; } @Override protected void setUp() throws Exception { super.setUp(); address = new Address(); address.setLine1("line1"); shipment.setAddress(address); } public void testGBShipment() throws Exception { address.setCountryCode("GB"); shipment.getDeliveryMethod().setServiceCode("first"); shipment.addOrderLine(newOrderLine()); expect("orderflow-singleitem", shipment, "orderflow.batch.selection.script"); shipment.addOrderLine(newOrderLine()); expect("orderflow-multiitem", shipment, "orderflow.batch.selection.script"); } public void testDEShipment() throws Exception { address.setCountryCode("De"); shipment.addOrderLine(newOrderLine()); expect("orderflow-singleitem-de", shipment, "orderflow.batch.selection.script"); shipment.addOrderLine(newOrderLine()); expect("orderflow-multiitem-de", shipment, "orderflow.batch.selection.script"); } public void testEUShipment() throws Exception { address.setCountryCode("fr"); shipment.addOrderLine(newOrderLine()); expect("orderflow-singleitem-eu", shipment, "orderflow.batch.selection.script"); shipment.addOrderLine(newOrderLine()); expect("orderflow-multiitem-eu", shipment, "orderflow.batch.selection.script"); } private void expect(String expected, Shipment shipment, String scriptFile) { assertEquals(expected, run(scriptFile, shipment, expected, false)); } private String run(final String scriptFile, Shipment shipment, String expected, boolean highVolume) { Map<String,Object> parameters = new HashMap<String, Object>(); parameters.put("expected", expected); parameters.put("highVolume", highVolume); return run(scriptFile, shipment, parameters); } }
The BaseBatchSelectionScriptTest
defines a run(scriptFile, shipment, parameters)
method which is useful
for setting up the scripting context for the batch selection script.
Note that the run()
method returns the batch selection returned from the batch selection script, which can be compared with
an expected result.
The responsibility of the script developer is to return identify all of the scenarios that need to be covered in the selection script, and ensure that the correct value is returned for each scenario.
Write Script
The script developer will then write a script for which all of the tests pass. The script corresponding to the above unit test is shown below:
def multiLine = (value.orderLineCount > 1); def multiQuantity = value.orderItemCount > 1; def countryCode = value.implicitAddress?.countryCode?.toUpperCase(); def domesticCountryCodes = ['GB','UK','JE','GG','IM']; def suffix = ''; if (countryCode == 'DE') { suffix = '-de'; } else if (!domesticCountryCodes.contains(countryCode)) { suffix = '-eu'; } if (multiLine || multiQuantity) { return 'orderflow-multiitem'+suffix; } return 'orderflow-singleitem'+suffix;
IDE Setup
A screenshot showing these in an Eclipse IDE project environment is shown below.
Note the use of the test package naming convention, which follows the along the lines: