1 – Prerequisites & High‑Level Flow
Requirement | Notes |
---|---|
Revenue Cloud Advanced license | Org must already have RCA enabled and the standard Sales Transaction context extended. |
Admin or equivalent perm set | You’ll create fields, edit context definitions, and deploy Apex. |
Developer Console / VS Code | Needed for the small Quote Line Item trigger. |
High‑Level Flow
- Add a Text Area (Long) field called
ConstraintEngineNodeStatus
to three objects. - Extend the Sales Transaction context to carry that attribute.
- Map it in Quote, Order, and Asset entity mappings.
- Deploy a one‑time trigger on Quote Line Item.
- Enable Advanced Configurator in Revenue Settings.
- Build two constraint models (one virtual quote, one attribute rule).
- Test & validate.
2 – Step 1 – Create ConstraintEngineNodeStatus
Fields
Field type: Text Area (Long) — Length = 5 000
2.1 – Quote Line Item (QLE)
- Setup ▸ Object Manager ▸ Quote Line Item ▸ Fields & Relationships ▸ New
- Choose Text Area (Long) ▸ Next.
- Field Label =
Constraint Engine Node Status
- Field Name =
ConstraintEngineNodeStatus
(no underscores) - 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
- Setup ▸ Context Definitions ▸ open your extended Sales Transaction context.
- Edit Nodes
- SalesTransactionItem → Add Attribute
- Name:
ConstraintEngineNodeStatus
- Type: Input & Output
- Data Type: String
- Name:
- AssetActionSource → Add Attribute
- Name:
AssetConstraintEngineNodeStatus
- Type: Input & Output
- Data Type: String
- Name:
- SalesTransactionItem → Add Attribute
- Attribute Tags
- On each node, scroll to the new attribute, click Add Tag, use the same name (Salesforce will append
_c
).
- On each node, scroll to the new attribute, click Add Tag, use the same name (Salesforce will append
- Save the context.
4 – Step 3 – Map the Attribute in All Entity Mappings
Repeat this process for Quote, Order, and Asset entity mappings.
Example – Quote Entities Mapping
- In Context Definitions, click ▼ next to Quote Entities Mapping → Edit SObject Mapping.
- Map
ConstraintEngineNodeStatus
(left) ➜ConstraintEngineNodeStatus__c
on Quote Line Item (right). - Verify TransactionType is also mapped.
- 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
- Setup ▸ Revenue Settings
- Scroll to Advanced Configuration Rules & Constraints.
- 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
- App Launcher ▸ Constraint Models ▸ New
- Name:
Bundle Auto‑Add Demo
- Context Definition: select your active context → Save.
- 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
Object: Quote
API Name: CustomPermissionQuote__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
- New Constraint Model → Name:
ThinkStation Attribute Control
- Add Item →
ThinkSystem‑ST50
(stand‑alone product with attributes). - Save → open CML Editor.
- 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
- Create/open a quote.
- Add Smart Office Bundle (includes 24″ monitor).
- Verify Remote Work Bundle auto‑adds.
- Delete Smart Office → both bundles disappear (engine respects user intent).
Scenario B – Attribute Control
- Add ThinkSystem‑ST50 to a quote.
- Configure – Margin & Revenue visible.
- Set CustomPermissionQuote__c = TRUE on the quote.
- Re‑configure – Margin is hidden; Revenue is disabled.
10 – Troubleshooting & Best Practices
Issue | Fix / Tip |
---|---|
Constraint Models tab not visible | Check app profiles & tab visibility. |
Field isn’t updating | Ensure trigger deployed and users have Edit on the field. |
Attribute missing in context | Activate the context after adding tags. |
CML rule ignored | Confirm product IDs, model is Activated, clear cache. |
Performance | Prefer product‑scoped rules; use virtual‑quote rules sparingly. |
11 – Resources & Next Steps
- Salesforce Help – Activate Advanced Configurator
- Salesforce Help – Advanced Configurator Overview
- Blog repo – full trigger + CML samples (coming soon on TheCloudUpdate.co).
- Need AI help writing CML? Comment on the YouTube video for access to my Constraint‑Model GPT.
Happy configuring! If you found this guide useful, share it with your Revenue Cloud team and subscribe for more deep‑dive tutorials.