Prime Penguin uses a simple expression language to route orders, or to type orders as B2B or DTC. It allows you to write powerful, but yet simple statements about orders that are either true or false and determines how Prime Penguin functions. For example, you could add the following filter to a warehouse in the order routing…
shippingAddress.countryCode = "SE"
… and only orders for Sweden would be sent to the given warehouse.
Or for a more complex example, if you would like to make sure a warehouse only receives an order if it is for the Stockholm City (as determined by postal codes) and the warehouse has stock to fulfill the order, it would look like this:
number_in_range(to_digits(shippingAddress.zip), 11100, 11199) AND has_stock()
Note that the “AND” combines two expressions, so that the entire filter is only true if both the postal code is in the right range and the warehouse has stock. The line breaks are for readability only and does not actually matter.
Expression Overview
Each filter is an expression which is a fancy programming term to say that it results in a value, and the value needed for filters should be a true or false value. For example, the following expression can be used as a filter:
contactEmail = "mylittlepony@test.com"
If an order has the field contactEmail and that field equals “mylittlepony@test.com”, then the expression (and the filter) evaluates to true. But if the order has any other email address as the contactEmail then it would evaluate to false.
Expression can work for numbers as well, for example, the following expression is true if an order has more than or equal to 4 lines items:
items_count() >= 4
You can combine small expressions using “AND” or “OR”:
contactEmail = "mylittlepony@test.com" OR items_count() >=4
The above filter would be true either if the contact email of the order is “mylittlepony@test.com” or if it has more or equal to 4 items.
Finally, you can group expressions using parenthesis, and use the operator “NOT” to express negations:
NOT contactEmail = "mylittlepony@test.com" AND (shippingAddress.countryCode = "SE" or items_count() < 4)
The above filter would be true only if the contact email is not “mylittlepony@test.com” and either the country is Sweden or the order has less than 4 lines items.
Fields, functions and operators
There are three distinct things that makes up an expression:
- Fields: You can access the value of any field in the order, for example contactEmail as shown above.
- Functions: These are special constructs that can either help you manipulate the field data, or find extra data. For example:
- items_count() – This function returns the number of line items in the order
- string_starts_with(string, prefix) – This function checks if a string starts with a specific prefix
- Operators: These are mostly used to compare a field value against a precondition. For example:
- = – Returns true if the values on both side of the “=” are equal
- > – Returns true if the number on the left side is creater than that on the right
Data types
In the order fields and in the expressions you will be dealing with three kinds of data:
- string – This is a set of characters, e.g. a name or an address part
- number – This is a number, that may or may not have decimal places, e.g. the number of line items in an order
- boolean – This is a fancy name for something that is either true or false
- set – This is a list of strings, e.g. all SKU’s from an order
There is also a special kind of string:
- date – This is a string that can be interpreted as a date, or even a date and time. It will always use so called ISO 8601 format (see “Date format” below).
Negations
You can test for negative expressions, simply by adding “NOT” in front of the expression. So for example, the following expression would match every order that is not shipped to Sweden:
NOT shippingAddress.countryCode = "SE"
Another way of doing the same thing would be to use the “not equals” operator (see “Operators” below):
shippingAddress.countryCode != "SE"
Common examples
Shipping address country code filter
The following matches Sweden or Germany:
shippingAddress.countryCode = "SE" OR shippingAddress.countryCode = "DE"
Postal code range filter
The following matches postal code in the range 10000 to 15000. Note that it uses the “helper function” to_digits() that removes non-digit characters from string value. This is useful since postal codes often have both spaces and characters in them:
number_in_range(to_digits(shippingAddress.zip), 10000, 15000)
Customer name match filter
The following matches any customer name that starts with “Popup Store” (also, see “String matching” below):
string_match(customerName, "Popup Store*")
Stock availability filter
The following is only available in order routing, and checks that the given warehouse has stock available to fulfill all line items in the order:
has_stock()
Use a variable set
If you have a variable set with, for example, zip codes, you can use it like this to check if the recipient address exists in the set:
set_contains(variable_set("myZipCodes"), shippingAddress.zip)
Check if an order has a specific tag
This is how you can check if an order carries a specific tag:
set_contains(string_split(tags, ","), "myTag")
Fuzzy SKU or product filter
The following matches a specific SKU or a product title in the line items:
items_contains_sku("AS101") OR items_contains_title_match("Lipstick 666 *")
Available fields
These are the fields that can be accessed in an order:
- salesChannel – string
- salesChannelId – number
- orderNumber – string
- totalPrice – number
- totalTax – number
- created – string (date-time)
- createdDate – string (date)
- currency – string
- note – string
- shippingType – string
- shippingCompany – string
- shippingPrice – number
- isPaid – boolean
- deliveryInstructions – string
- requestedDeliveryDate – string (date)
- customerName – string
- contactEmail – string
- tags – string
- freeText1 – string
- freeText2 – string
- freeText3 – string
- shippingAddress.address1 – string
- shippingAddress.address2 – string
- shippingAddress.zip – string
- shippingAddress.city – string
- shippingAddress.province – string
- shippingAddress.countryCode – string
- shippingAddress.country – string
- billingAddress.address1 – string
- billingAddress.address2 – string
- billingAddress.zip – string
- billingAddress.city – string
- billingAddress.province – string
- billingAddress.countryCode – string
- billingAddress.country – string
Available functions
Please note that the has_stock function is only available in order routing since it is dependent on the stock levels at a specific warehouse.
Utility functions
- has_stock() – Returns true if warehouse can fulfill order
- to_digits(string) – Returns a new string where all non-digits have been removed
- to_alphanumeric(string) – Returns a new string with only digits and characters
- variable_set(string) – Retrieve the values from a variable set
Date functions
- now() – Returns the current date and time as a string
- now_date() – Returns the current date as a string
- date_in_range(date, from, to) – Checks if date (string) is in range of two other dates (also strings)
- date_before(date, to) – Checks if date (string) is before another date (string)
- date_after(date, from) – Checks if date (string) is after another date (string)
String functions
- string_starts_with(string, prefix) – Checks if string starts with prefix (case insensitive)
- string_ends_with(string, postfix) – Checks if string starts with postfix (case insensitive)
- string_match(string, pattern) – Checks if string matches pattern (string, see “String matching” below)
- string_split(string, pattern) – Split the string to a set using the pattern as delimiter
- string_split_digits(string, pattern) – Split the string to a set using the pattern as delimiter, apply “to_digits” to all strings in the new set
- string_split_alphanumeric(string, pattern) – Split the string to a set using the pattern as delimiter, apply “to_alphanumeric” to all strings
Number functions
- is_number(value) – Returns true if the field or expression can be used as a number
- number_in_range(number, from, to) – Checks if number (string or number) is in range of two other number (also string or number)
Line item functions
- lines_count() – Returns the number of order lines in the order
- items_count() – Returns the number of line items in the order
- items_contains_sku(sku) – Returns true if the given SKU (string) is among the line items
- items_contains_title_match(pattern) – Returns true if any product title matches the pattern (string, see “String matching” below)
- items_contains_variantTitle_match(pattern) – Returns true if any product variant title matches the pattern (string, see “String matching” below)
- items_contains_comment_match(pattern) – Returns true if any product comment matches the pattern (string, see “String matching” below)
- items_sku_set() – Returns a set with all the SKU’s from the order
- items_comments_set() – Returns a set with all the comments from the order
Set functions
- set_contains(set, string) – Check if the given set contains the given string
- set_contains_match(set, pattern) – Check if any string in the sat “matches” the given string
- set_contains_digits(set, string|number) – Call “to_digits” on the string, and then check if the set contains those numbers
- set_contains_alphanumeric(set, string) – Call “to_alphanumeric” on the string, and then check if the set contains the result
- set_contains_all(set, set) – Check if set one contains all strings from set two
- set_contains_any(set, set) – Check if set one contains any string from set two
- set_count(set) – Returns the number of strings in the set
Date format
Dates are represented as strings, and always in ISO 8601 format, either as date-time strings (exluding nano seconds, but including time zone) or simple dates:
- Date: “yyyy-MM-dd”, e.g. “2021-01-01”
- Datetime: “yyyy-MM-dd’T’HH:mm:ssZ”, e.g. “2021-01-01T23:12:01+01:00”
String matching
String equility and substrings
- String equality using the “=” operator is case sensitive
- Substring comparison using string_starts_with or string_ends_with are case insensitive
String pattern matching
Pattern matching is case insensitive and uses Glob style matching. Some examples:
- true: string_matches(“kalle”, “*lle”)
- false: string_matches(“kalle”, “kall[aoi]”)
- true: string_matches(“kalle”, “kall?”)
- false: string_matches(“kalle 1”, “kalle [2-8]”)
Operators
The following operators are available:
- NOT (or !)
- AND (or &)
- OR (or |)
- =
- !=
- >
- <
- <=
- >=
Grouping
You can group expressions using parenthesis, for example:
has_stock() AND (items_count() > 5 | items_contains_sku("asd123"))