Enforcing Product Dependency Across Quotes and Assets
BlogRevenue Cloud / CPQEnforcing Product Dependency Across Quotes and Assets

Enforcing Product Dependency Across Quotes and Assets

A practical Salesforce Revenue Cloud (ARM) solution for enforcing product dependencies—allowing a product to be sold only when a related product is present in the quote or already owned by the customer.

Problem Statement:

Your sales team sells two products:

1.    DumpIT: The core software product.

2.    DumpIT Support: An add-on service that cannot be sold standalone.


The business rule is clear — Support should never be sold on its own. It only makes sense when a customer has DumpIT. But "has DumpIT" means two very different things depending on the situation:

  • They are buying DumpIT right now on the same quote
  • They already own DumpIT from a previous deal, and it now lives as an asset on their account


Both cases are legitimate. Both must be allowed. And any quote that has neither — a rep who just drops DumpIT Support standalone — must be blocked with a clear message: "DumpIT Support can only be sold with DumpIT."


The challenge is that Salesforce sees these two scenarios as completely separate data problems. The first lives on the current quote. The second lives outside it, in a historical asset record. Getting both into one rule, at configuration time, in real time, is exactly what this solution solves.


The Hidden Gap: Assets Are Not On the Quote

For a new sale, the quote-level check is simple. You look at the current quote lines, count the DumpIT products, and decide. CML handles this natively.


For an amendment or renewal, DumpIT was purchased in a prior transaction. The current quote has no DumpIT line at all — just the new DumpIT Support being added. If your constraint only looks at the current quote, it will always count zero DumpIT lines and incorrectly block a legitimate transaction.


This is the gap most implementations miss. The Constraint Rules Engine, by default, has no visibility into asset records. You have to explicitly configure it to fetch asset data and make it available inside CML. This is done through an Asset Context Definition — a feature available from Summer '25 onwards.


Think of the Asset Context Definition as plugging in a second data feed. Without it, @(sourceContextNode = "Asset") in your CML resolves against nothing. Your asset relation will always return zero, your amendments will be blocked, and the failure will be silent — no error, just wrong behavior.


Set up before Writing CML

These steps must be completed before a single line of CML works correctly.


Step 1 — Create the ConstraintEngineNodeStatus field

Create a Text Area (Long) field, length 5000, named ConstraintEngineNodeStatus on three objects: Quote Line Item, Order Product, and Asset Action Source. Anyone who uses Advanced Configurator needs Edit access to this field — the engine writes to it. Map this field in your Context Definition using the ConstraintEngineNodeStatus tag on each entity mapping.


Step 2 — Deploy the Quote Line Item Trigger

The constraint engine requires a trigger on Quote Line Item to initialise and maintain the ConstraintEngineNodeStatus__c field. Without it, the engine cannot write its processing state and the configurator will not behave correctly. Salesforce provides the trigger code — deploy it as-is.


Step 3 — Extend the Asset Context Definition

In Setup → Context Definitions, find AssetContext__stdctx and extend it. Map it to the Account field so the engine knows to fetch assets belonging to the same customer on the quote. Activate the context after saving all mappings.


Step 4 — Register It in Revenue Settings

Go to Setup → Revenue Settings → Set Up Asset Context for Product Configurator and select the asset context definition you just created. This single toggle is what connects your CML's @(sourceContextNode = "Asset") annotation to actual data. Miss this step and the asset relation in your CML will always be empty, regardless of what the customer actually owns.


Step 5 — Enable Advanced Configurator

In Revenue Settings, scroll to Advanced Configuration Rules and Constraints and toggle it on. If using assets in rules, ensure an Asset Context Definition is configured and that products have "Configure During Sale" set to Allowed.


Adding the Constraint Model in Salesforce

Once your CML is ready and all setup steps are complete, follow these steps to deploy it:

1. Create a Constraint model

  • From the App Launcher, find and select Constraint Models.
  • Click New Constraint Model.
  • Enter a name and API name for the constraint model.
  • Select the active sales transaction context definition.
  • Save the constraint model.

2. In the Constraint Models app, select the constraint model you want to modify.

3. On the Details page, select the version name of the constraint model.

4. In the Constraint Builder header in the upper left corner, select CML Editor.

5. To write or edit CML code directly and define your constraints and rules, work in the CML Editor code window.

6. Save your changes.

7. To make the constraint model available for use, select Activate.


CML Solution

python
type LineItem;
type DumpIT : LineItem 
type DumpITSupport : LineItem;
@(virtual = true)
type Quote {
    @(sourceContextNode = "Asset")
    @(closeRelation = true)
    relation DumpITAsset : DumpIT[0..10];
    @(sourceContextNode = "SalesTransaction.SalesTransactionItem")
    relation DumpITSupport : DumpITSupport[0..10];
    @(sourceContextNode = "SalesTransaction.SalesTransactionItem")
    @(closeRelation = true)
    relation DumpIT : DumpIT[0..10];
    constraint(
        (DumpITAsset[DumpIT] == 0 && DumpIT[DumpIT] == 0) -> DumpITSupport[DumpITSupport] == 0,
        "DumpIT Support can only be sold with DumpIT"
    );
}

type DumpIT : LineItem and type DumpITSupport : LineItem Both products inherit from LineItem, which is the base type that maps to a Sales Transaction Item in Salesforce. DumpIT has two picklist attributes.


@(virtual = true) type Quote The virtual annotation means this type does not map to a single product record. It is a container that represents the entire quote context, used to define cross-product relationships and quote-level constraints. This is the standard pattern for any multi-product rule in RCA.


@(sourceContextNode = "Asset") on DumpITAsset This tells the engine to source this relation from the Asset context you registered in Revenue Settings. At runtime, it fetches all assets of type DumpIT belonging to the account on this quote. The count returned by DumpITAsset[DumpIT] reflects real historical purchases, not just what is on the current quote.


@(sourceContextNode = "SalesTransaction.SalesTransactionItem") on DumpIT and DumpITSupport This sources data from the live quote being built. The path SalesTransaction.SalesTransactionItem is the canonical CML path to current quote line items in RCA.


@(closeRelation = true) — critical on both DumpIT relations By default, when a constraint is violated, the CRE may try to satisfy it automatically by adding or removing products. closeRelation = true prevents the engine from auto-adding DumpIT to the quote just to satisfy the constraint. The intent is to block and show the error, not silently fix the configuration. This annotation must be on both the asset relation and the new DumpIT quote relation, otherwise the engine might resolve the violation in unexpected ways.


The Constraint Expression

(DumpITAsset[DumpIT] == 0 && DumpIT[DumpIT] == 0) -> DumpITSupport[DumpITSupport] == 0


The -> operator is a logical implication: "if the left side is true, the right side must also be true." Read the whole thing as: if there are zero DumpIT assets on the account AND zero DumpIT lines on the current quote, then the count of DumpITSupport must also be zero. The engine enforces this by preventing DumpITSupport from being added and surfacing the error message. The moment either count goes above zero, the left side becomes false, the implication is vacuously satisfied, and DumpITSupport is allowed.


Runtime Behaviour

New sale: Rep adds DumpIT and DumpIT Support to the same quote. DumpIT[DumpIT] = 1. Left side is false. Constraint satisfied. Both products allowed.


Amendment/Renewal: Rep opens an amendment for a customer who already owns DumpIT. DumpITAsset[DumpIT] = 1 from the asset context. Left side is false. Constraint satisfied. DumpIT Support allowed without needing a new DumpIT line.


Standalone attempt: Rep adds only DumpIT Support. No asset, no new DumpIT line. Both counts are zero. Left side is true. Engine forces DumpITSupport count to zero and displays: "DumpIT Support can only be sold with DumpIT."


Required Permissions

To create a constraint model: Product Configuration Constraints Designer permission set


Final Thoughts

The logic here is simple: before allowing DumpIT Support on a quote, check if the customer has DumpIT — either on the same quote or from a past purchase. CML lets you do both checks in a single expression, covering new sales and amendments without any extra flows or Apex.


The part most implementations get wrong is not the CML itself but the infrastructure behind it. The field, the trigger, the context definition, the revenue settings toggle - all four have to be in place before the asset relation returns any data. If something feels off during testing, check the wiring first.


Get the setup right, activate the model, and the rule enforces itself.

Share this post:
Sudhanshu Kaushik

About the Author

Sudhanshu Kaushik

Related Posts

Demystifying Salesforce Pricing – Revenue Cloud
Revenue Cloud / CPQ4 min read

Demystifying Salesforce Pricing – Revenue Cloud

Understanding the Core Building Blocks of Salesforce Pricing Before diving into configuration or process design in Salesforce Revenue Cloud, it’s important to understand the key conceptual components that drive pricing and qualification logic

G
GV Mantra16 Jun 2025
Read More →
Revenue Cloud – Product Catalog Building Blocks
Revenue Cloud / CPQ1 min read

Revenue Cloud – Product Catalog Building Blocks

Product Navigation Before you start learning or using Salesforce Revenue Cloud, it’s important to understand some basic building blocks of Product Catalog Management.

G
GV Mantra9 Jun 2025
Read More →
Revenue Cloud – Setting Up Multi-Currency
Revenue Cloud / CPQ2 min read

Revenue Cloud – Setting Up Multi-Currency

Once multi-currency is enabled in your org, you need to configure Revenue Cloud to ensure correct pricing on quotes and product additions. Follow these steps

G
GV Mantra9 Jun 2025
Read More →

Recent Posts

Categories

Tags

Stay Updated

Get the latest Salesforce insights delivered to your inbox.