1,001 views
In this article, we will review how OrderCloud leverages the rules engine to extend platform logic, including the resources currently backed by the engine and the syntax used to create rule-based expressions.
Overview
The rules engine is integrated into the OrderCloud platform as a means of extending platform logic via custom rule-based expressions. The expressions are defined in free-text properties using a custom syntax, consisting of OrderCloud models, operators and predefined functions, which are evaluated by the rules engine and used for “if-then” logic in the OrderCloud platform.
Below documents the resources that currently leverage the rules engine, along with high-level “if-then” logic.
Resource | If | Then |
---|---|---|
Promotions |
| Create promotion discount of ValueExpression . |
Order Approvals – Buyer Approval Rules | RuleExpression | Progress through the order approval workflow. |
Order Returns – Seller Approval Rules | RuleExpression | Progress through the order returns workflow. |
For example, the following promotion requirement has been translated into respective expressions.
10% off (up to $20) orders greater than $100 and containing product with ID ABC
EligibleExpression | order.Total > 100 and items.any(ProductID = 'ABC') |
ValueExpression | max(order.Total * 0.1, 20) |
Syntax
To ensure that you construct expressions correctly, the following syntax rules will need to met to avoid errors and unexpected behaviour.
- Expressions use dot notation to navigate an object construct, e.g.
order.xp.MyCustomProperty
. - String values must be enclosed in straight single quotes (
'
), e.g.'My Value'
. - DateTime values must be enclosed in hashes (
#
), using the US date format, e.g.#6/24/2023#
. - Parentheses (
()
) may be used to enclose sub-expressions and control order of execution, e.g.order.Total > 100 and (order.xp.MyProperty = 'this' or order.xp.MyProperty = 'that')
.
Operators
Operator Type | Available Options |
---|---|
Comparison | = /== , <> /!= , < , > , <= , >= |
Logical | and , or and not |
Mathematical | + , - , * , / and % |
Models
While order and line item entities are the primary models in expressions, additional models can be accessed via child models. The following table shows how to access available models, which can also be identified throughout OrderCloud’s API reference documentation, including orders, line items, and both can be found under the order worksheet.
Model | Syntax |
---|---|
Order | order.<property> |
Line Item | item.<property> |
Product | item.Product.<property> in items.<function>(arg) |
Variant | item.Variant.<property> in items.<function>(arg) |
User (Order Owner) | order.FromUser.<property> |
Billing Address | order.BillingAddress.<property> |
Shipping Address | item.ShippingAddress.<property> in items.<function>(arg) |
Source Inventory/Seller/Supplier Location | item.ShipFromAddress.<property> in items.<function>(arg) |
Functions
Functions provide a means of evaluating specialised rules and conditions that are built into the rules engine. These pre-defined functions are documented below.
Items Functions
Items functions will have an implicit item context, within the function, therefore the item.
prefix is omitted from any condition of the function argument.
Function | Returns | Description |
---|---|---|
items.any(arg) | boolean | Returns true if any line item meets the specified arg condition. e.g. items.any(ProductID = 'ABC') |
items.all(arg) | boolean | Returns true if all line item meets the specified arg condition. e.g. items.all(ProductID = 'ABC') |
items.quantity(arg) | number | Returns the sum of line item quantities where the line item meets the specified arg condition. e.g. items.quantity(ProductID = 'ABC') > 2 |
items.count(arg) | number | Returns the number of line item where the line item meets the specified arg condition. e.g. items.count(ProductID = 'ABC') > 2 |
items.total(arg) | number | Returns the sum of all line item LineSubtotal s where the line item meets the specified arg condition.e.g. items.total(ProductID = 'ABC') > 100 |
product.incategory(args) | boolean | Returns true if the product for the line item being evaluated is assigned to one or more of the n category ID arguments. e.g. items.any(product.incategory('cat1', cat2')) |
Item Functions
The item functions along with any item specific condition can only be utilised in expressions for line item level promotions.
Function | Returns | Description |
---|---|---|
item.product.incategory(args) | boolean | Returns true if the product for the line item being evaluated is assigned to one or more of the n category ID arguments. e.g. item.product.incategory('cat1', cat2') |
item.incategory(args) | boolean | The same as item.product.incategory(args), but with a shortened expression to save characters. e.g. item.incategory('cat1', cat2') |
Order Functions
The current order functions are limited to order approvals and usage should be limited to the order approvals – buyer approval rules resource to prevent any unexpected behaviour.
Function | Returns | Description |
---|---|---|
order.approved(ruleID) | boolean | Returns true if the ruleID has previously been approved for the order. e.g. order.approved('rule1') |
Order Return Functions
The current order return functions are limited to order returns, therefore usage should be limited to the order returns – seller approval rules resource to prevent any unexpected behaviour.
Function | Returns | Description |
---|---|---|
orderreturn.approved(ruleID) | boolean | Returns true if the ruleID has previously been approved for the order return. e.g. orderreturn.approved('rule1') |
General Functions
Function | Returns | Description |
---|---|---|
min(arg1, arg2) | number | Returns the smaller of the two arg values. e.g. min(order.LineItemCount, 5) |
max(arg1, arg2) | number | Returns the larger of the two arg values. e.g. max(20, order.Total * 0.1) |
now(arg) | date/time | Returns a date/time with +/- arg value in days. e.g. order.DateCreated < now(-5) |
Limitations and Gotchas
As powerful as the rules engine can be, it’s not fool proof. If you are not careful with the way in which expressions are constructed, you may find yourself spending a lot of time troubleshooting unexpected behaviour due to a poorly written expression. The following limitations and gotchas will assist in mitigating both expression creation and troubleshooting efforts:
- Expressions are limited to 400 characters.
- Expressions do not support checking against null or whitespace values.
- Arrays cannot be evaluated in expressions.
- Inventory cannot be evaluated in expressions.
- No validation against invalid result types for expressions. For example, the
ValueExpression
expects a decimal result, however the expressionorder.IsSubmitted = false
would result to a boolean and return a 0 amount. - There is no way to extend or customise the rules engine directly, however leveraging webhooks it is possible for middleware to implement custom logic to fulfill any business requirements not covered by the rules engine.
- Avoid creating conditions on properties that will change between order states as this could cause promotions to become invalidated and corrupt an order. For example, if using the condition
order.Status = "Unsubmitted"
where order approvals are active, when the order does get submitted, the order promotion will become invalidated. - Value expressions will be rounded to 2 decimal places, to the nearest number. This means that it’s possible that minor rounding issues could occur when applying multiple promotion calculations. For example, the following tables show two orders made with a line item level promotion to provide 5% each subtotal of each line item. As can be seen in the Totals row, the aggregate promotion discount has a +/-0.01 discrepancy.
UnitPrice | Quantity | LineSubtotal | 5% Promotion Raw Value | 5% Promotion Rounded Value |
---|---|---|---|---|
9.95 | 1 | 9.95 | 0.4975 | 0.50 |
9.95 | 1 | 9.95 | 0.4975 | 0.50 |
9.95 | 1 | 9.95 | 0.4975 | 0.50 |
Total | 3 | 29.85 | 1.4925 | 1.50 |
UnitPrice | Quantity | LineSubtotal | 5% Promotion Raw Value | 5% Promotion Rounded Value |
---|---|---|---|---|
9.95 | 3 | 29.85 | 1.4925 | 1.49 |
Total | 3 | 29.85 | 1.4925 | 1.49 |