How to Activate Salesforce Revenue Cloud Advanced Configurator & Build Two Constraint Models

Photo of author
Written By Jean-Michel Tremblay
Salesforce Consultant

1 – Prerequisites & High‑Level Flow

RequirementNotes
Revenue Cloud Advanced licenseOrg must already have RCA enabled and the standard Sales Transaction context extended.
Admin or equivalent perm setYou’ll create fields, edit context definitions, and deploy Apex.
Developer Console / VS CodeNeeded for the small Quote Line Item trigger.

High‑Level Flow

  1. Add a Text Area (Long) field called ConstraintEngineNodeStatus to three objects.
  2. Extend the Sales Transaction context to carry that attribute.
  3. Map it in QuoteOrder, and Asset entity mappings.
  4. Deploy a one‑time trigger on Quote Line Item.
  5. Enable Advanced Configurator in Revenue Settings.
  6. Build two constraint models (one virtual quote, one attribute rule).
  7. Test & validate.

2 – Step 1 – Create ConstraintEngineNodeStatus Fields

Field typeText Area (Long) — Length = 5 000

2.1 – Quote Line Item (QLE)

  1. Setup ▸ Object Manager ▸ Quote Line Item ▸ Fields & Relationships ▸ New
  2. Choose Text Area (Long) ▸ Next.
  3. Field Label = Constraint Engine Node Status
  4. Field Name = ConstraintEngineNodeStatus (no underscores)
  5. Length = 5 000 ▸ assign field‑level security ▸ Save.

2.2 – Order Product (OrderItem)

Repeat the exact steps above on Order Product.

2.3 – Asset Action Source

Repeat again on Asset Action Source.

Tip: Anyone who will use Advanced Configurator needs Edit access; the engine writes to this field.


3 – Step 2 – Extend & Tag Your Context Definition

  1. Setup ▸ Context Definitions ▸ open your extended Sales Transaction context.
  2. Edit Nodes
    • SalesTransactionItem → Add Attribute
      • NameConstraintEngineNodeStatus
      • TypeInput & Output
      • Data TypeString
    • AssetActionSource → Add Attribute
      • NameAssetConstraintEngineNodeStatus
      • TypeInput & Output
      • Data TypeString
  3. Attribute Tags
    • On each node, scroll to the new attribute, click Add Tag, use the same name (Salesforce will append _c).
  4. Save the context.

4 – Step 3 – Map the Attribute in All Entity Mappings

Repeat this process for QuoteOrder, and Asset entity mappings.

Example – Quote Entities Mapping

  1. In Context Definitions, click  next to Quote Entities Mapping → Edit SObject Mapping.
  2. Map ConstraintEngineNodeStatus (left) ➜ ConstraintEngineNodeStatus__c on Quote Line Item (right).
  3. Verify TransactionType is also mapped.
  4. Save, then repeat for Order & Asset mappings (using the asset‑specific attribute for Asset Action Source).

Don’t forget to Activate the context after all mappings are saved.


5 – Step 4 – Deploy the Quote Line Item Trigger

Create a new trigger on Quote Line Item and paste the code below.

trigger QuoteItemTrigger on QuoteLineItem (before insert) {
    //collect QuoteActionIds 
    Set<Id> quoteActionIds = new Set<Id>();

    for (QuoteLineItem qi : Trigger.new) {
        if (qi.QuoteActionId != null && qi.ConstraintEngineNodeStatus__c == null) {
            quoteActionIds.add(qi.QuoteActionId);
        }
    }

    if (!quoteActionIds.isEmpty()) {
        // Step 1: Get QuoteAction → SourceAsset
        Map<Id, Id> quoteActionToAssetId = new Map<Id, Id>();
        for (QuoteAction qAction : [
            SELECT Id, SourceAssetId 
            FROM QuoteAction 
            WHERE SourceAssetId != null 
              AND Id IN :quoteActionIds
        ]) {
            quoteActionToAssetId.put(qAction.Id, qAction.SourceAssetId);
        }

        // Step 2: Get AssetActions
        List<AssetAction> assetActions = [
            SELECT Id, AssetId, ActionDate 
            FROM AssetAction 
            WHERE AssetId IN :quoteActionToAssetId.values()
        ];

        // Step 3: Get latest AssetAction per Asset
        Map<Id, AssetAction> assetIdToLatestAction = new Map<Id, AssetAction>();
        for (AssetAction aAction : assetActions) {
            AssetAction existing = assetIdToLatestAction.get(aAction.AssetId);
            if (existing == null || aAction.ActionDate > existing.ActionDate) {
                assetIdToLatestAction.put(aAction.AssetId, aAction);
            }
        }

        // Step 4: Get related AssetActionSource records
        Map<Id, Id> assetIdToActionId = new Map<Id, Id>();
        for (Id assetId : assetIdToLatestAction.keySet()) {
            assetIdToActionId.put(assetId, assetIdToLatestAction.get(assetId).Id);
        }

        List<AssetActionSource> assetActionSources = [
            SELECT ConstraintEngineNodeStatus__c, AssetAction.AssetId 
            FROM AssetActionSource 
            WHERE AssetActionId IN :assetIdToActionId.values() ORDER BY CreatedDate DESC
        ];
        // Step 5: Map AssetId → Status
        Map<Id, String> assetIdToStatus = new Map<Id, String>();
        for (AssetActionSource actionSource : assetActionSources) {
            if (!assetIdToStatus.containsKey(actionSource.AssetAction.AssetId) && 
                actionSource.ConstraintEngineNodeStatus__c != null) {
                assetIdToStatus.put(
                    actionSource.AssetAction.AssetId, 
                    actionSource.ConstraintEngineNodeStatus__c
                );
            }
        }
        List<QuoteLineItem> toUpdate = new List<QuoteLineItem>();
        // Step 6: Set ConstraintEngineNodeStatus__c directly on Trigger.new records
        for (QuoteLineItem qi : Trigger.new) {            
            if (qi.QuoteActionId != null && qi.ConstraintEngineNodeStatus__c == null) {
                Id assetId = quoteActionToAssetId != null ? quoteActionToAssetId.get(qi.QuoteActionId) : null;
                if (assetId != null && assetIdToStatus != null) {
                    String status = assetIdToStatus.get(assetId);
                    if (status != null) {
                       qi.ConstraintEngineNodeStatus__c = status;
                    }
                }
            }
        }
    }
}

This trigger initializes and maintains the new field for the engine.


6 – Step 5 – Enable the Advanced Configurator Engine

  1. Setup ▸ Revenue Settings
  2. Scroll to Advanced Configuration Rules & Constraints.
  3. Toggle Enable Advanced Configurator → Save.

You should now see Constraint Models in the App Launcher. Ensure the tab is visible in relevant profiles.


7 – Step 6 – Constraint Model #1: Cross‑Bundle Auto‑Add

7.1 – Create the Model Skeleton

  1. App Launcher ▸ Constraint Models ▸ New
  2. NameBundle Auto‑Add Demo
  3. Context Definition: select your active context → Save.
  4. In Version 1 → Add Items:
    • Smart Office Bundle
    • Remote Work Bundle
    • (All option products auto‑import.)

7.2 – Switch to CML Editor

Save the model, click  next to the version, choose CML Editor, and append the virtual quote rule:

type LineItem;

type SmartOfficeBundle : LineItem {
    relation monitor24in1080p : Monitor24in1080p[1..3];

    relation monitor32in4k : Monitor32in4K[0..3];

    relation monitor27in1440p : Monitor27in1440p[0..3];

    relation laptopi716gb : Laptopi716GB[0..1];

    relation laptopi932gb : Laptopi932GB[0..1];

    relation laptopi58gb : Laptopi58GB[1..3] {
        default Laptopi58GB(2);
    }

    relation dockingstationhd : DockingStationHD[0..1];

    relation dockingstation4k : DockingStation4K[0..1];

    relation officesuiteteamsenterprise : OfficeSuiteTeamsEnterprise[0..1];

    relation officesuiteteamsstandard : OfficeSuiteTeamsStandard[1];

}

type Monitor24in1080p : LineItem;

type Monitor32in4K : LineItem;

type Monitor27in1440p : LineItem;

type Laptopi716GB : LineItem;

type Laptopi932GB : LineItem;

type Laptopi58GB : LineItem;

type DockingStationHD : LineItem;

type DockingStation4K : LineItem;

type OfficeSuiteTeamsEnterprise : LineItem;

type OfficeSuiteTeamsStandard : LineItem;

type RemoteWorkBundle : LineItem {
    relation collabswzoombasic : CollabSWZoomBasic[1];

    relation collabswteamspro : CollabSWTeamsPro[0..1];

    relation headsetncusbc : HeadsetNCUSBC[1];

    relation webcam4k : Webcam4K[0..1];

    relation webcam1080p : Webcam1080p[0..1];

}

type CollabSWZoomBasic : LineItem;

type CollabSWTeamsPro : LineItem;

type HeadsetNCUSBC : LineItem;

type Webcam4K : LineItem;

type Webcam1080p : LineItem;

// Transaction‑level rule: when SmartOfficeBundle with a 24" monitor is added, auto‑add RemoteWorkBundle
@(virtual = true)
type Quote {
    @(sourceContextNode = "SalesTransaction.SalesTransactionItem")
    relation smartOfficeBundle : SmartOfficeBundle[0..1];

    @(sourceContextNode = "SalesTransaction.SalesTransactionItem")
    relation remoteWorkBundle : RemoteWorkBundle[0..1];

    // If SmartOfficeBundle contains a 24" 1080p monitor, require a RemoteWorkBundle
    require(
        smartOfficeBundle[SmartOfficeBundle].monitor24in1080p[Monitor24in1080p],
        remoteWorkBundle[RemoteWorkBundle],
        "Smart Office Bundle with 24\" monitor requires a Remote Work Bundle"
    );
}































Save → Activate the model.


8 – Step 7 – Constraint Model #2: Attribute Hide / Disable

8.1 – Prep the Permission Field

ObjectQuote
API NameCustomPermissionQuote__c (Checkbox)

Create the field, then add an attribute & mapping for it in your context (node = SalesTransaction). Tag it CustomPermissionQuote__c.

8.2 – Build the Model

  1. New Constraint Model → NameThinkStation Attribute Control
  2. Add Item → ThinkSystem‑ST50 (stand‑alone product with attributes).
  3. Save → open CML Editor.
  4. Append rules:
// Bring in the quote‐header custom permission
@(contextPath="SalesTransaction.CustomPermissionQuote__c")
extern boolean CustomPermissionQuote__c;

type LineItem;

type ThinksystemST50 : LineItem {
    @(defaultValue = "40")
    decimal(2) Margin;

    decimal(2) Revenue;

     // Hide the Margin attribute when the custom permission is true
    rule(CustomPermissionQuote__c==TRUE, "hide", "attribute", "Margin"); 

    // Disable the Revenue attribute when the custom permission is true
    rule(CustomPermissionQuote__c==TRUE,"disable", "attribute", "Revenue");

}

Save → Activate.


9 – Step 8 – Test Your Work (2 Scenarios)

Scenario A – Cross‑Bundle Rule

  1. Create/open a quote.
  2. Add Smart Office Bundle (includes 24″ monitor).
  3. Verify Remote Work Bundle auto‑adds.
  4. Delete Smart Office → both bundles disappear (engine respects user intent).

Scenario B – Attribute Control

  1. Add ThinkSystem‑ST50 to a quote.
  2. Configure – Margin & Revenue visible.
  3. Set CustomPermissionQuote__c = TRUE on the quote.
  4. Re‑configure – Margin is hidden; Revenue is disabled.

10 – Troubleshooting & Best Practices

IssueFix / Tip
Constraint Models tab not visibleCheck app profiles & tab visibility.
Field isn’t updatingEnsure trigger deployed and users have Edit on the field.
Attribute missing in contextActivate the context after adding tags.
CML rule ignoredConfirm product IDs, model is Activated, clear cache.
PerformancePrefer product‑scoped rules; use virtual‑quote rules sparingly.

11 – Resources & Next Steps


Happy configuring! If you found this guide useful, share it with your Revenue Cloud team and subscribe for more deep‑dive tutorials.

Leave a Comment