Firing criteria
A condition is a single comparison: a left-hand value, an operator, and
a right-hand value. The engine evaluates each condition in the rule, then
combines them using the rule’s ConditionsMet setting (All / Any / Custom).
Tested object + tested field
What value does the left-hand side read? The picker offers:
- Quote — fields on the parent Quote (Quote-mode orgs only).
- Opportunity — fields on the parent Opportunity (both modes).
On a configuration submitted for approval,
Opportunity.Amount/Quote.TotalPriceread the configuration’s own total — and on an amendment that is the amount the amendment books (the delta against the contract), so thresholds fire on what the deal is worth, not on the full amended configuration. - Account — fields on the deal’s Account.
- ConfigurationOption — scalar fields on the in-flight configuration
draft:
TotalPrice,ProductCount,OptionName,PricingModel,Status. - Product — any line on the deal carries this product (any-match semantics; fires if at least one product on the cart matches).
- Bundle Option — any bundle option in the configured tree matches.
Operators
The picklist mirrors the pricing rule operator vocabulary so admins who already author Price Conditions feel at home:
| Operator | Reads as | Numeric? | String? | Notes |
|---|---|---|---|---|
Equals | lhs == rhs | yes | yes | Case-insensitive for strings |
Not_Equals | lhs != rhs | yes | yes | |
Greater_Than | lhs > rhs | yes | — | Skips if RHS null (see below) |
Greater_Or_Equal | lhs >= rhs | yes | — | Skips if RHS null |
Less_Than | lhs < rhs | yes | — | Skips if RHS null |
Less_Or_Equal | lhs <= rhs | yes | — | Skips if RHS null |
Is_Null | lhs is null | yes | yes | |
Is_Not_Null | lhs is not null | yes | yes | |
Contains | lhs contains rhs | — | yes | Case-insensitive |
Starts_With | lhs begins with rhs | — | yes | Case-insensitive |
Ends_With | lhs ends with rhs | — | yes | Case-insensitive |
Value source
What value does the right-hand side read?
- Literal. Whatever you typed.
20,Enterprise,true. Coerced numerically when both sides parse as numbers. - Field Reference. A scope-qualified field path like
Account:Discount_Threshold__c. Reads the same context map the LHS uses, so you can compare two fields on the same deal. - Variable. A reference to an
ApprovalVariable__cby name, written as either${VarName}or bareVarName. See Approval variables.
Combining conditions
Each rule has a ConditionsMet setting:
- All. Every condition must pass. Default. Use for AND semantics.
- Any. At least one must pass. Use for OR semantics.
- Custom. A boolean expression like
(1 AND 2) OR 3, referring to conditions by their 1-indexed position. If the expression is unparseable, the engine falls back to All and logs the fallback in the trace.
Aggregate conditions
A single condition like “discount > 20%” only reads ONE field on ONE record. To answer “how many bundle lines are in roll-up mode?” or “what’s the total margin across all lines?”, switch the condition’s Mode picker from Field to Line Aggregate.
The sub-editor exposes:
- Aggregator —
COUNT/SUM/AVG/MIN/MAX. - Aggregator Field — the line-item field the aggregator operates on
(ignored for
COUNT). Schema-driven picker: filtered to numeric fields when the outer operator is numeric, otherwise unconstrained. - Filter Field / Operator / Value — an optional WHERE clause that decides which line items participate. Same operator picklist as field-mode.
- Outer Operator + Value — compares the aggregate result. Forced to numeric operators because every aggregator returns a number.
Worked examples.
| You want | Mode = Line Aggregate, set: |
|---|---|
| Approve if any line has > 20% discount | COUNT(*) where Discount__c Greater_Than 20, outer Greater_Or_Equal 1 |
| Approve if total line value > $250k | SUM(TotalPrice), no filter, outer Greater_Than 250000 |
| Approve if average margin under 30% | AVG(Margin__c), no filter, outer Less_Than 30 |
| Approve if any bundle is in Roll-Up mode | COUNT(*) where PricingModel Equals Roll-Up, outer Greater_Or_Equal 1 |
| Approve if minimum line margin under 10% | MIN(Margin__c), no filter, outer Less_Than 10 |
| Approve if largest single-line price > $50k | MAX(TotalPrice), no filter, outer Greater_Than 50000 |
Empty result sets resolve to 0 for every aggregator (so an empty cart never fires a SUM-over-threshold rule by accident). Non-numeric aggregator fields log a trace note and resolve to 0 as well.
Approval variables
A variable is a named expression you author once. Conditions reference it by name; the engine resolves the expression against the deal at submit time.
When to factor a value out:
- The same threshold appears in three rules (“$50k” duplicated; if the threshold changes, you’d edit three places).
- The value lives on the Account, not the Quote — e.g.
Account.Discount_Threshold__c— and several rules want it. - The math is non-trivial — a formula crossing two objects.
Author variables in Pinion Admin → Approvals → Variables. Each record has:
- Name — referenced from conditions as
${Name}. - Description — surfaced in the wizard picker so the right one gets chosen.
- Formula — a dotted field path (e.g.
Quote.Account.Discount_Threshold__c) or constant expression. - Return Type —
Number/Currency/Percent/Text/Boolean. Drives operator compatibility.
Scope Product
A rule (or a single condition) can be scoped to a product so it only fires when that product is on the deal. This is the difference between “any high-value deal needs CFO” (no scope) and “any deal with our Enterprise SKU needs Product Marketing” (scoped).
The rule’s ScopeProduct__c field is the rule-level scope. When set,
the engine pre-filters: if the deal doesn’t carry the product, the rule
is skipped before its conditions even evaluate. Bundle parents count —
a child line whose ParentBundle__c.Product2Id matches the scope
product satisfies the gate. Non-bundle products match on direct
Product2Id.
Conditions have their own ScopeProduct__c for per-condition overrides.
Inheritance rule: the effective scope is
condition.ScopeProduct ?? rule.ScopeProduct — a condition without an
override inherits the rule’s, but a condition with its own override
replaces (not narrows) the rule’s. Use this when most conditions
on a rule share a scope but one condition needs a different product
(rare; usually a sign you want two rules).
Worked example. A rule scoped to “Enterprise Tier” with two conditions:
Opportunity.Amount Greater_Than 100000— no condition scope, inherits “Enterprise Tier” → only fires when Enterprise Tier is on the cart AND amount > $100k.Product.Quantity Greater_Than 10— same inheritance. Reads “any Product on this cart whose quantity > 10,” gated to deals carrying Enterprise Tier.
If you instead want condition 2 to scope to “Starter Tier” — same rule,
different product — set its ScopeProduct__c to Starter Tier and the
rule keeps Enterprise.
Related
- Authoring a Rule — where conditions attach to a step.
- Approvers — who signs off once conditions match.
- Runtime & Troubleshooting — reading the condition trace.