order
Placing an order: check the stock, price the lines, pick a discount, confirm. Each step is its own message, so each can be extended or replaced with rules of its own. The arithmetic is in expressions; the rules decide what happens next.
Stock
$when order.checkStock (items: Array, stock: Object) {
short = items filter (i => (stock[i.sku] ?? 0) < i.qty) map (i => i.sku)
if (short is not empty) {
list = short mkString ", "
diesel.throw (code = "E_STOCK", msg = "out of stock: ${list}")
}
}
Pricing
$when order.price (items: Array) {
payload = items map (i => i.qty * i.price) fold (s = 0.0) (x => s + x)
}
Discounts
Guards pick exactly one rule: members get 10%, anyone else spending over 100 gets 5%, the rest pay full price.
$when order.discount (subtotal: Float, member: Boolean) if (member) {
payload = subtotal * 0.10
}
$when order.discount (subtotal: Float, member: Boolean) if (not member and subtotal > 100.0) {
payload = subtotal * 0.05
}
$when order.discount (subtotal: Float, member: Boolean) if (not member and subtotal <= 100.0) {
payload = 0.0
}
Placing it
Each step's variables (subtotal, discount) belong to this rule only; the caller sees just the payload it returns.
$when order.place (items: Array, member: Boolean, stock: Object) {
order.checkStock (items, stock)
subtotal = order.price(items = items)
discount = order.discount(subtotal = subtotal, member = member)
payload = {status: "confirmed", subtotal: subtotal, discount: discount, total: subtotal - discount}
}
order.submit is what a shop would call: the same, but a stock problem becomes a rejected order instead of an error.
$when order.submit (items: Array, member: Boolean, stock: Object) {
try {
order.place (items, member, stock)
} catch (e) {
payload = {status: "rejected", reason: e.message}
}
}