OrderCloud: The Rules Engine

Reading Time: 4 minutes

 980 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.

ResourceIfThen
PromotionsEligibleExpressionCreate promotion discount of ValueExpression.
Order Approvals – Buyer Approval RulesRuleExpressionProgress through the order approval workflow.
Order Returns – Seller Approval RulesRuleExpressionProgress 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

EligibleExpressionorder.Total > 100 and items.any(ProductID = 'ABC')
ValueExpressionmax(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 TypeAvailable Options
Comparison=/==<>/!=, < , ><=>=
Logicalandor 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.

ModelSyntax
Orderorder.<property>
Line Itemitem.<property>
Productitem.Product.<property>
Product.<property>
in items.<function>(arg)
Variantitem.Variant.<property>
Variant.<property>
in items.<function>(arg)
User (Order Owner)order.FromUser.<property>
Billing Addressorder.BillingAddress.<property>
Shipping Addressitem.ShippingAddress.<property>
ShippingAddress.<property>
in items.<function>(arg)
Source Inventory/Seller/Supplier Locationitem.ShipFromAddress.<property>
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.

FunctionReturnsDescription
items.any(arg)booleanReturns true if any line item meets the specified arg condition.
e.g. items.any(ProductID = 'ABC')
items.all(arg)booleanReturns true if all line item meets the specified arg condition.
e.g. items.all(ProductID = 'ABC')
items.quantity(arg)numberReturns 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)numberReturns the number of line item where the line item meets the specified arg condition.
e.g. items.count(ProductID = 'ABC') > 2
items.total(arg)numberReturns the sum of all line item LineSubtotals where the line item meets the specified arg condition.
e.g. items.total(ProductID = 'ABC') > 100
product.incategory(args)booleanReturns 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.

FunctionReturnsDescription
item.product.incategory(args)booleanReturns 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)booleanThe 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.

FunctionReturnsDescription
order.approved(ruleID)booleanReturns 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.

FunctionReturnsDescription
orderreturn.approved(ruleID)booleanReturns true if the ruleID has previously been approved for the order return.
e.g. orderreturn.approved('rule1')

General Functions

FunctionReturnsDescription
min(arg1, arg2)numberReturns the smaller of the two arg values.
e.g. min(order.LineItemCount, 5)
max(arg1, arg2)numberReturns the larger of the two arg values.
e.g. max(20, order.Total * 0.1)
now(arg)date/timeReturns 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 expression order.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.
UnitPriceQuantityLineSubtotal5% Promotion
Raw Value
5% Promotion
Rounded Value
9.9519.950.49750.50
9.9519.950.49750.50
9.9519.950.49750.50
Total329.851.49251.50
Order 1 promotion example.
UnitPriceQuantityLineSubtotal5% Promotion
Raw Value
5% Promotion
Rounded Value
9.95329.851.49251.49
Total329.851.49251.49
Order 2 promotion example.

References

Leave a Reply

Your email address will not be published.