Happy Business Starts Here

APEX : RulesEngine Class Returns Success But Rules Weren't Run/Committed to DB?

Valued Scholar

APEX : RulesEngine Class Returns Success But Rules Weren't Run/Committed to DB?

Scenario:

 

I built a custom ApexREST Endpoint that our eCommerce team is going to call to create Opportunities and Zuora Quotes/Quote Charges in Salesforce.

The code successfully creates a zQuote in memory, then passes the zQuote Data to the zqu.Quote.createNewInstance() class to create the quote correctly. After we build and save the quote, I then build a list of chargeGroups using zqu.zQuoteUtil.getChargeGroup(Quote_Id,RatePlan_Id), and add them to the quote using zqu.zQuoteUtil.addChargeGroups to add the list.

 

What that leaves me with is a Quote, with Quote Rate Plans and Quote Rate Plan Charges. I want to run this newly created quote and related child Rate Plans/Charges through all of our Zuora Rules Engine rules. We have rules that are designed to add additional rate plans based on the rate plans that are already on the quote, as well as rules to set discounts and special prices.

 

So, I found the RulesEngine class which is supposed to be for running rules programmatically. I followed the documentation and the example code provided in the RulesEngine Class documentation. The response.getMessages() is returning the messages for the rules I expected to run, so it's finding the rules, the response.isSuccess() is returning TRUE, but the rules don't appear to be committing any changes to the quote or related charges. The rules should be adding an additional product, and discounting a product.

 

However, when I look at the quote in Salesforce, I don't see those Charges listed in the related lists If I re-calculate and re-save the items on the quote (from the Salesforce UI), then the rules run again (from the UI) and correctly insert the expected new products/discounts. So I know the rules themselves are working, but I cannot get them to successfully commit the changes to Salesforce through the RulesEngine via Apex. What am I doing wrong?

 

Here is my code for creating the quote/running the quote through the rules engine:

 

zqu__Quote__c zQuote = new zqu__Quote__c(Name = 'eCommerce Sale Quote ' + acct.Id,
					zqu__Account__c = acct.Id,
					zqu__Opportunity__c = opt.Id,
					zqu__BillToContact__c = cont.Id,
					zqu__SoldToContact__c = cont.Id,
					zqu__Service_Activation_Date__c = System.today(),
					zqu__StartDate__c = System.today(),
					zqu__SubscriptionType__c = capitalize(quoteType));

	zqu.Quote theQuote = zqu.Quote.createNewInstance(zQuote);

	theQuote.buildAndSave();

	List<zqu.zChargeGroup> chargeGroups = new List<zqu.zChargeGroup>();

	for(zqu__ProductRatePlan__c prp : ratePlanList){
		zqu.zChargeGroup chargeGroup = zqu.zQuoteUtil.getChargeGroup(theQuote.getId(),prp.Id);
		chargeGroups.add(chargeGroup);
	}

	System.debug('charge Groups: ' + chargeGroups);
	List<zqu.zChargeGroup> addedChargeGroup = zqu.zQuoteUtil.addChargeGroups(chargeGroups);
	System.debug('Charge Groups Added: ' + addedChargeGroup);
	
Id quoteId = theQuote.getId(); Set<zqu.CacheId> requiredIds = new Set<zqu.CacheId>(); requiredIds.add(new zqu.CacheId('zqu__Quote__c',quoteId)); Map<String,List<zqu.DataObject>> dataObjects = makeDataObjects(quoteId); zqu.RulesEngine re = new zqu.RulesEngine('zqu__Quote__c'); re.initialize(dataObjects, requiredIds); zqu.RulesEngineResponse response = re.run(zqu.RulesEngine.RuleType.ALL); finalMap.put('Account',acct.Id); finalMap.put('Contact',cont.Id); finalMap.put('Opportunity',opt.Id); finalMap.put('Quote',theQuote.getId()); finalMap.put('Rules Engine Response',String.valueOf(response)); finalMap.put('Rules Engine Object',String.valueOf(response.getMasterObject())); finalMap.put('Rules Engine Messages',String.valueOf(response.getMessages())); finalMap.put('Rules Engine Success',String.valueOf(response.isSuccess()));

Here is the context of the makeDataObjects method that I copied from the Documentation:

private static Map<String, List<zqu.DataObject>> makeDataObjects(Id quoteId) {
	List <zqu.DataObject> dataChargeGroups = new List <zqu.DataObject> ();
	List <zqu.DataObject> dataCharges = new List <zqu.DataObject> ();

	List <zqu.ZChargeGroup> chargeGroups = zqu.zQuoteUtil.getChargeGroups(quoteId);

	// Create a DataObject for ZChargeGroup (QuoteRatePlan)
	// and ZCharges (QuoteRatePlanCharge).
	for(zqu.ZChargeGroup chargeGroup : chargeGroups){
		zqu.DataObject dataChargeGroup = new zqu.ZChargeGroupDataObject(chargeGroup);
		dataChargeGroups.add(dataChargeGroup);
		dataCharges.addAll(dataChargeGroup.getChildren(
		'zqu__' + 'QuoteRatePlanCharge__c'));
	}

	return new Map <String, List<zqu.DataObject>>{
		'zqu__' + 'QuoteRatePlan__c' => dataChargeGroups,
		'zqu__' + 'QuoteRatePlanCharge__c' => dataCharges
	};
}
8 REPLIES 8
Highlighted
Valued Scholar

Re: APEX : RulesEngine Class Returns Success But Rules Weren't Run/Committed to DB?

I guess nobody has any experience using the Rules Engine class provided by Zuora? Could really use some help here...

Highlighted
Guru

Re: APEX : RulesEngine Class Returns Success But Rules Weren't Run/Committed to DB?

Maybe @doyeli can help.

 

I have looked at this a long time ago but we are not currently using the rules engine.

 

Good luck.

Maggie Longshore
Highlighted
Zuora Alumni

Re: APEX : RulesEngine Class Returns Success But Rules Weren't Run/Committed to DB?

Thank you @MaggieL.

Hi @mmarchese , I will review and test this on my end during US hours tomorrow (Wednesday).

Will update this thread with my finding.

 

Thank you
Doyeli






If you found my answer helpful, please give me a kudo ↑
Help others find answers faster by accepting my post as a solution √

Highlighted
Valued Scholar

Re: APEX : RulesEngine Class Returns Success But Rules Weren't Run/Committed to DB?

Thanks @MaggieL for referring @doyeli - I'd appreciate any thoughts you could provide on the matter.

 

If it makes any difference, I've also already tried to use the .save() method after the rules engine response is successful, to save changes to the quote, but it doesn't appear to make any difference. Just not sure what I'm supposed to do to make sure these rules run and apply the appropriate changes in salesforce thru apex. The rules run fine through the UI, and they RUN thru the rules engine, but the new products/price changes don't seem to commit to the salesforce DB even though the associated rules are being executed via the apex class.

Highlighted
Zuora Alumni

Re: APEX : RulesEngine Class Returns Success But Rules Weren't Run/Committed to DB?

Hi @mmarchese, Thank you for confirming the following

 

>>If it makes any difference, I've also already tried to use the .save() method after the rules engine response is successful, to save changes to the quote, but it doesn't appear to make any difference.

 

I was going to suggest this.

I am reviewing this now and will be updating on this thread.

 

Thank you
Doyeli






If you found my answer helpful, please give me a kudo ↑
Help others find answers faster by accepting my post as a solution √

Highlighted
Zuora Alumni

Re: APEX : RulesEngine Class Returns Success But Rules Weren't Run/Committed to DB?

Hi @mmarchese, I have recenty  worked with one of Zuora Global services Engineer who was trying to implement the similar use case for another customer: creating a REST endpoint in SFDC which will create a Quote and then

  1. Add Charges
  2. Override Quantity/Price data
  3. Run Rules engine
  4. Update Charges with Rules Engine data

 

Following was the WORKING code snippet from where the Update was run:

 

zqu.GlobalCustomFieldDefinition.CHARGE_FIELDS = new Set<String>{ 'clineaccels__c', 'margins__c', 'multiyear2s__c', 'ncasubaccels__c', 'tp_margin__c' };  ======> These are the custom fields on Quote Rate Plan Charge 

Set<zqu.CacheId> requiredIds = new Set<zqu.CacheId>(); 
requiredIds.add( new zqu.CacheId( 'zqu_Quote_c', createdQuoteId ) );

List < zqu_ZRulec > rulesToRun = [SELECT Id, Name, zquJsonc, zquActionJsonc FROM zquZRule_c];

Map<String,List<zqu.DataObject>> dataObjects = makeDataObjects();

zqu.RulesEngine re = new zqu.RulesEngine('zqu_Quote_c', rulesToRun); 
re.initialize(dataObjects, requiredIds);

zqu.RulesEngineResponse response = re.run(zqu.RulesEngine.RuleType.ALL);

//5. Get MasterObject from RulesEngineResponse 
zqu.DataObject returnedObjs = response.getMasterObject(); 
//6. Get children from MasterObject 
List <zqu.DataObject> children = returnedObjs.getChildren('zqu_QuoteRatePlanCharge_c'); 
System.debug(LoggingLevel.Info,'# charges: '+children.size());

for (zqu.DataObject charge : children) { 
if (charge.get('zqu_Modelc') != 'Discount-Percentage') { 
continue; 
} 
// Get the list of tiers for the charge object and 
// modify the tier information. 
zqu.ZChargeDataObject chargeObject = (zqu.ZChargeDataObject) charge; 
// Create ChargeProxy
_c object instance from zCharge

List<zqu.zChargeGroup> chargeGroupsToUpdate = zqu.zQuoteUtil.getChargeGroups((String)returnedObjs.get('Id'));

for(zqu.zChargeGroup thisGroup : chargeGroupsToUpdate){

for (zqu.zCharge chargeToCheck : thisGroup.zCharges){ 
System.debug(LoggingLevel.Info,'chargeToCheck.Id: '+chargeToCheck.Id); 
System.debug(LoggingLevel.Info,'chargeObject.get(Id): '+chargeObject.get('Id')); 
if(chargeToCheck.Id == chargeObject.get('Id')){ 
chargeToCheck.EFFECTIVE_PRICE = String.valueOf(chargeObject.get('zqu_DiscountPercentage_c')); 
zqu.zQuoteUtil.calculateChargesOnEffectivePriceChange(thisGroup.zCharges);

chargeToCheck.chargeObject.put('Clineaccels_c', chargeObject.get('Clineaccelsc')); 
chargeToCheck.chargeObject.put('Margins
c', chargeObject.get('Marginsc')); 
chargeToCheck.chargeObject.put('Multiyear2s
c', chargeObject.get('Multiyear2sc')); 
chargeToCheck.chargeObject.put('NCASubaccels
c', chargeObject.get('NCASubaccelsc')); 
chargeToCheck.chargeObject.put('TP_Margin
c', chargeObject.get('TP_Margin_c')); 
System.debug(LoggingLevel.Info,'chargeToCheck: '+chargeToCheck);

zqu.zQuoteUtil.updateChargeGroup(thisGroup); 
System.debug(LoggingLevel.Info,'chargeGroupAfterUpdate: '+thisGroup); 
} 
} 
}

}

 

can you please review and confirm if something similar could work on your end

 

Thank you
Doyeli






If you found my answer helpful, please give me a kudo ↑
Help others find answers faster by accepting my post as a solution √

Highlighted
Valued Scholar

Re: APEX : RulesEngine Class Returns Success But Rules Weren't Run/Committed to DB?

@doyeli this looks promising actually, thanks for providing the complete code. I am going to work on implementing this today based on this information and will follow up if this solved my problem.

 

As a side note, I wish the Zuora Rules Engine Class documentation made it clearer that these steps were required after running the rules engine. It doesn't make any mention what-so-ever in the documentation to any of this, all it tells you how to do is convert the items to data objects and run them through the runes engine. Zuora should consider updating their documentation (https://knowledgecenter.zuora.com/CA_Commerce/I_Development_Resources/C_Component_Library/C_Global_C...) to avoid other users having the same confusion I have

Highlighted
Zuora Alumni

Re: APEX : RulesEngine Class Returns Success But Rules Weren't Run/Committed to DB?

HI @mmarchese, Thank you for the feedback, I agree.

We have already started working with internal  teams to get the Knowledge center Article updated with all detail.

 

Here are some additional information , based on discussion with Services and Engineering team:

 

Changes to the charge object only save if you are implementing the QuotesRulesEnginePlugin. If you're running the Rules Engine on your own, you have to dig back down into the Data Object which is returned to retrieve the "updated values" and then apply them to the charges yourself.

The Rules Engine alone doesn't commit anything to the database, just as it doesn't commit anything during product selection. But the rate plan DataObjects contain ChargeGroups, which you can save to the quote with global methods.

There's an easier way to retrieve them. If you cast each returned RatePlan DataObject as zqu.ZChargeGroupDataObject, then you can call getChargeGroup() on that and pass it to updateChargeGroup/addChargeGroup.

 

Hope this helps.

 

I will update this thread when the KC article is updated.

 

Thank you

Doyeli

 

 






If you found my answer helpful, please give me a kudo ↑
Help others find answers faster by accepting my post as a solution √