Skip to main content

Custom Action Plugin

Zuora

Custom Action Plugin

Use the Custom Action Plugin to configure additional actions you want to trigger via Zuora Rules Engine. The Custom Action Plugin interface class, zqu.CustomActionPlugin, contains the following interface methods:

  • perform
  • isUpdateAction
  • isAddRemoveAction
  • isValidateAction

Perform Method

In the perform method, define the logic this custom action will execute. See the sample code below for an example implementation of the perform method.

The method takes the following input parameters.

Input

Data Type

Description

masterObject zqu.DataObject Contains the record that the rule is currently running against and its related data. This will generally be a Quote record.
attributes Map<String, Object> A map of parameters that can be defined by an admin when configuring a custom action.
logs String[] A list of strings that get printed to the Rules Engine log when the rules engine executes the action.

Rule Types Methods

When initiating the Rules Engine programmatically, you have an option to specify which types of actions to run. For example, calling the Rules Engine with the Rule Type of VALIDATION will only evaluate rules with a Validation action.

By default, the Rules Engine runs all actions.

When defining a custom action, set the following methods to return true to specify the types of this action.

Method

Rule Types that Can Invoke this Action

isUpdateAction()

ALL or PRICE

isAddRemoveAction()

ALL or PRODUCT

isValidateAction()

ALL or VALIDATION

Constructors

Using the following constructors, you can instantiate different types of DataObjects to add new data during a custom action.

Contructor Description

new ZChargeGroupDataObject
(DataObject dataQuote, ID productRatePlanId)

new ZChargeGroupDataObject
(zChargeGroup)

Creates a new ChargeGroup, retrieved through the zqu.zQuoteUtil.getChargeGroup global method.

QuoteRatePlan fields can be retrieved from this record, even if it has not yet been saved to the database.

Charge objects can be retrieved from an instance of this object using:

getChildren('zqu__QuoteRatePlanCharge__c')

new PlaceholderChargeGroupDataObject
(ID productRatePlanId)

Represents a charge group to be added after rules execution completes.

Charge and Plan details are not accessible from this instance, but because it does not call getChargeGroups, it runs more quickly and makes no SOQL queries.

new CommittedDataObject
(SObject obj)

Represents an SObject record inserted into the Database.

Not used for Product Actions.

Sample Code

As of version 10.11, this Sample Code can be applied to CPQ 9 (legacy UI) and CPQ X (new Quote Studio UI) based on the conditional logic for X or 9 seen in the code below. In case you already have CPQ 9 configured, all you need to do is add the CPQ X conditional logic, and if you are only going to be using CPQ X, you can exclude the CPQ 9 conditions.

Additionally, if the unmanaged Custom Action Plugin class was created before version 10.11, the metadata of the class needs to be updated to version 10.11 or higher.

The below sample code implements a custom action that updates charges, removes and adds rate plans, and validates the quote.

global with sharing class MyCustomActionPlugin implements zqu.CustomActionPlugin {
     global void perform(zqu.DataObject masterObject, Map<String, Object> attributes, String[] logs) {
         String quoteId = (String) masterObject.get('Id');
         String attributeMsg = (String) attributes.get('Application');
         List <zqu.DataObject> plans = masterObject.getChildren('zqu__QuoteRatePlan__c');
         List <zqu.DataObject> charges = masterObject.getChildren('zqu__QuoteRatePlanCharge__c');
         
         Boolean isQuoteStudioProcess = false;
         for (zqu.DataObject plan : plans) {
              if (plan instanceof zqu.QPlanDataObject) {
                  isQuoteStudioProcess = true;
                  break;
              }
          }
          
          // Log Attributes.
          logs.add('Quote Id : ' + quoteId);
          logs.add('Attribute Message : ' + attributeMsg);
          logs.add('Is Quote Studio Process : ' + isQuoteStudioProcess);
          logs.add('Number Of Plans : ' + plans.size());
          logs.add('Number Of Charges : ' + charges.size());
          
          // ------------ ADD PRODUCT ACTION ------------ //
          ID productRatePlanIdToAdd = 'a0YDR000006iES5';
          zqu.DataObject toBeAddedPlan = null;
          
          // CASE 1 //
          toBeAddedPlan = new zqu.PlaceholderChargeGroupDataObject(productRatePlanIdToAdd);
          toBeAddedPlan.flagAdded();
          masterObject.addChild('zqu__QuoteRatePlan__c', toBeAddedPlan);
          
          // CASE 2 //
          toBeAddedPlan = null;
          if (isQuoteStudioProcess) {
              // CPQ X Rules Engine Flow.
              toBeAddedPlan = new zqu.QPlanDataObject(quoteId, productRatePlanIdToAdd);
          } else {
              // CPQ 9 Rules Engine Flow.
              toBeAddedPlan = new zqu.ZChargeGroupDataObject(masterObject, productRatePlanIdToAdd);
          }
          zqu__ProductRatePlan__c prp = new zqu__ProductRatePlan__c(Id = productRatePlanIdToAdd);
          zqu.DataObject dataProductRatePlan = new zqu.CommittedDataObject(prp);
          toBeAddedPlan.putParent('zqu__ProductRatePlan__c', dataProductRatePlan);
          toBeAddedPlan.flagAdded();
          masterObject.addChild('zqu__QuoteRatePlan__c', toBeAddedPlan);
          
          // ------------ UPDATE PRODUCT ACTION ------------ //
          Decimal chargeListPrice = 10, tierListPrice = 10, quantity = 20, tierDiscount = 50;
          for (zqu.DataObject charge : charges) {
              // Update the list price at the charge level for 'Flat Fee Pricing' charge type.
              if (charge.get('zqu__Model__c') == 'Flat Fee Pricing') {
                  charge.put('zqu__ListPrice__c', chargeListPrice);
                  logs.add('Updating list Price of (' + charge.get('Name') + ') to ' + chargeListPrice + '.');
                  chargeListPrice += 10;  
              }
          
              // Update the list price at the charge level for 'Per Unit Pricing' charge type.
              if (charge.get('zqu__Model__c') == 'Per Unit Pricing') {
                  charge.put('zqu__Quantity__c', quantity);
                  logs.add('Updating quantity of (' + charge.get('Name') + ') to ' + quantity + '.');
                  quantity += 10;  
              }
  
              // Update the tier details (List Price, Discount & Effective Price).
              if (charge.get('zqu__ChargeType__c') == 'Recurring' && (charge.get('zqu__Model__c') == 'Tiered Pricing' || charge.get('zqu__Model__c') == 'Volume Pricing')) {
                  // CPQ 9 Rules Engine Flow.
                  if (!isQuoteStudioProcess) {
                      zqu.ZChargeDataObject chargeObject = (zqu.ZChargeDataObject) charge;
                      List<zqu__QuoteCharge_Tier__c> tiers = chargeObject.getCharge().chargeTiersObjects;
                      if (tiers != null && tiers.size() > 0) {
                          for (zqu__QuoteCharge_Tier__c tier : tiers) {
                              tier.zqu__Price__c = tierListPrice;
                              tier.zqu__Discount__c = tierDiscount;
                              tier.zqu__Effective_Price__c = (tierListPrice / 2);
                              tierListPrice += 10;
                          }
                          logs.add('Updating the tiers of charge (' + charge.get('Name') + ').');
  
                          // Populate the changed tier information.
                          chargeObject.rePopulateCustomChargeTiers(tiers);  
                      }
                  }
                  
                  // CPQ X Rules Engine Flow.
                  if (isQuoteStudioProcess) {
                      zqu.QChargeDataObject chargeObject = (zqu.QChargeDataObject) charge;
                      if (chargeObject != null) {
                          zqu.QCharge qc = chargeObject.getQCharge();
                          if (qc != null) {
                              List<zqu.QTier> tiers = qc.getTiers();
                              if (tiers != null && tiers.size() > 0) {
                                  for (zqu.QTier tier : tiers) {
                                      tier.put('zqu__Price__c', tierListPrice);
                                      tier.put('zqu__Discount__c', tierDiscount);
                                      tier.put('zqu__Effective_Price__c', (tierListPrice / 2));
                                      tierListPrice += 10;
                                  }
                                  
                                  // Update the tiers in the charge object.
                                   qc.setTiers(tiers);
                               }
                           }
                       }
                   }
               }
           }
           
           // ------------ REMOVE PRODUCT ACTION ------------ //
           String productToRemove = 'Annually Billed Per Unit Product';
           for (zqu.DataObject plan : plans) {
               String productName = (String) plan.get('zqu__QuoteProductName__c');
               System.debug('Product Name : ' + productName);
               if (productName == productToRemove) {
                   logs.add('Product Found : ' + productName);
                   // Mark As Removed.
                   plan.flagRemoved();
                   logs.add('Removed : ' + productName);
               }
           }
           
           // ------------ VALIDATION ERROR ------------ //
           if (plans.size() > 2) {
               throw new zqu.CustomValidationException('You cannot have more than two plans on a quote.');
           }
       }
   
       global Boolean isUpdateAction() {
           return true;
       }
   
       global Boolean isAddRemoveAction() {
           return true;
       }
   
       global Boolean isValidateAction() {
           return true;
       }
   }

 

Below is a sample code with a test class for the custom action plugin.

@isTest
global class MyCustomActionPluginTest {
    public static final String DEFAULT_BILLTO_CONTACT_FIRST_NAME = 'Test Bill-To First Name';
​
    public static final String DEFAULT_BILLTO_CONTACT_LAST_NAME = 'Test Bill-To Last Name';
​
    public static final String DEFAULT_SOLDTO_CONTACT_FIRST_NAME = 'Test Sold-To First Name';
​
    public static final String DEFAULT_SOLDTO_CONTACT_LAST_NAME = 'Test Sold-To Last Name';
    
    public static final String DEFAULT_OPPORTUNITY_NAME = 'Test Opportunity Name';
​
    public static final String DEFAULT_ACCOUNT_NAME = 'Test Salesforce Account Name';
    
    public static final String DEFAULT_CURRENCY = 'USD';
    
    public static final Date DEFAULT_EFFECTIVE_START_DATE = System.today();
    
    public static final Date DEFAULT_EFFECTIVE_END_DATE = System.today().addYears(1);
    
    public static final String NEW_SUBSCRIPTION = 'New Subscription';
    
    public static final String MOCK_ZUORA_PROD_ID = '1118e487366202430136757c960716b6';
    
    @TestSetup
    private static void setupTestData(){
        //create a test account
        Account testaccount = new Account(Name = DEFAULT_ACCOUNT_NAME);
        insert testaccount;
        system.assertEquals([SELECT count() from Account],1);
        
        //create billto,soldTo contacts
        Contact billToContact = new Contact(FirstName = DEFAULT_BILLTO_CONTACT_FIRST_NAME, LastName = DEFAULT_BILLTO_CONTACT_LAST_NAME);
        billToContact.accountId = testaccount.Id;
        insert billToContact;
        
        Contact soldToContact = new Contact(FirstName = DEFAULT_SOLDTO_CONTACT_FIRST_NAME, LastName = DEFAULT_SOLDTO_CONTACT_LAST_NAME);
        soldToContact.accountId = testaccount.Id;
        insert soldToContact;
        system.assertEquals([SELECT count() from Contact],2);
        
        //create an opportunity
        Opportunity testopportunity = new Opportunity(Name = DEFAULT_OPPORTUNITY_NAME, AccountId = testaccount.Id);
        testopportunity.StageName = 'Closed Won';
        testopportunity.CloseDate = System.today();
        insert testopportunity;
        system.assertEquals([SELECT count() from Opportunity],1);
​
        //create a zuora quote
        zqu__Quote__c quote = new zqu__Quote__c();
        quote.zqu__Currency__c = DEFAULT_CURRENCY;
        quote.Name = 'Positive Quote';
        quote.zqu__Account__c = testaccount.Id;
        quote.zqu__AutoRenew__c = true;
        quote.zqu__Amendment_Name__c = 'Positive Amendment';
        quote.zqu__Opportunity__c = testopportunity.Id;
        quote.zqu__BillToContact__c = billToContact.Id;
        quote.zqu__SoldToContact__c = soldToContact.Id;
        quote.zqu__InitialTerm__c = 12.0;
        quote.zqu__RenewalTerm__c = 6.0;
        quote.zqu__PaymentMethod__c = 'Credit Card';
        quote.zqu__ValidUntil__c = DEFAULT_EFFECTIVE_START_DATE;
        quote.zqu__StartDate__c = DEFAULT_EFFECTIVE_START_DATE;
        quote.zqu__SubscriptionTermStartDate__c = DEFAULT_EFFECTIVE_START_DATE;
        quote.zqu__SubscriptionTermEndDate__c = DEFAULT_EFFECTIVE_END_DATE;
        quote.zqu__SubscriptionType__c = NEW_SUBSCRIPTION;
        quote.zqu__BillingMethod__c = 'Both';
        quote.zqu__Subscription_Term_Type__c = 'Termed';
        insert quote;
        system.assertEquals([SELECT count() from zqu__Quote__c],1);
        
        //creating ZProducts
        zqu__ZProduct__c zuoraproduct = new zqu__ZProduct__c();
        zuoraproduct.Name = 'CC Testing';
        zuoraproduct.zqu__EffectiveStartDate__c = DEFAULT_EFFECTIVE_START_DATE;
        zuoraproduct.zqu__EffectiveEndDate__c = DEFAULT_EFFECTIVE_END_DATE;
        zuoraproduct.zqu__SKU__c = 'testingsku0001';
        zuoraproduct.zqu__ZuoraId__c = 'zuoraid0000000001';
        zuoraproduct.zqu__Deleted__c = false;
        insert zuoraproduct;
        system.assertEquals([SELECT count() from zqu__ZProduct__c],1);
        
        //creating salesforce product
        Product2 testsfproduct = new Product2();
        testsfproduct.Name = 'CC Testing';
        testsfproduct.zqu__EffectiveStartDate__c = DEFAULT_EFFECTIVE_START_DATE;
        testsfproduct.zqu__EffectiveEndDate__c = DEFAULT_EFFECTIVE_END_DATE;
        testsfproduct.zqu__SKU__c = 'testingsku0001';
        testsfproduct.zqu__ZuoraId__c = 'zuoraid0000000001';
        testsfproduct.zqu__Deleted__c = false;
        insert testsfproduct;
        system.assertEquals([SELECT count() from Product2],1);
        
        //creating Product rate plan
        zqu__ProductRatePlan__c testproductrateplan = new zqu__ProductRatePlan__c();
        testproductrateplan.Name = 'Test Product Rate Plan';
        testproductrateplan.zqu__EffectiveStartDate__c = DEFAULT_EFFECTIVE_START_DATE;
        testproductrateplan.zqu__EffectiveEndDate__c = DEFAULT_EFFECTIVE_END_DATE;
        testproductrateplan.zqu__Product__c = testsfproduct.Id;
        testproductrateplan.zqu__ZProduct__c = zuoraproduct.Id;
        testproductrateplan.zqu__ZuoraId__c = MOCK_ZUORA_PROD_ID;
        testproductrateplan.zqu__Deleted__c = false;
        testproductrateplan.zqu__ActiveCurrencies__c = DEFAULT_CURRENCY;
        insert testproductrateplan;
        system.assertEquals([SELECT count() from zqu__ProductRatePlan__c],1);
        
        //creating Product rate plan charge with Model as 'Flat Fee Pricing' and Type as 'Recurring'
        zqu__ProductRatePlanCharge__c productrateplancharge = new zqu__ProductRatePlanCharge__c();
        productrateplancharge.Name = 'Test Product Rate Plan Charge';
        productrateplancharge.zqu__Model__c = 'Flat Fee Pricing';
        productrateplancharge.zqu__Type__c = 'Recurring';
        productrateplancharge.zqu__UOM__c = 'UOM tesing';
        productrateplancharge.zqu__DefaultQuantity__c = 1;
        productrateplancharge.zqu__MinQuantity__c = 0;
        productrateplancharge.zqu__MaxQuantity__c = 500;
        productrateplancharge.zqu__RecurringPeriod__c = 'Month';
        productrateplancharge.zqu__ZuoraId__c = MOCK_ZUORA_PROD_ID;
        productrateplancharge.zqu__ProductRatePlan__c = testproductrateplan.Id;
        productrateplancharge.zqu__Deleted__c = false;
        if (productrateplancharge.zqu__Model__c == 'Discount-Fixed Amount' || productrateplancharge.zqu__Model__c == 'Discount-Percentage') {
            productrateplancharge.zqu__Discount_Apply_Type__c = 3;
            productrateplancharge.zqu__Upto_How_Many_Periods__c = 5;
            productrateplancharge.zqu__Discount_Level__c = 'RatePlan';
        }
        // Set List Price for non-tiered charge
        productrateplancharge.zqu__ListPrice__c = 1000;
        
        //creating Product rate plan charge with Model as 'Flat Fee Pricing' and Type as 'Recurring'
        zqu__ProductRatePlanCharge__c productrateplanchargeflatfee = new zqu__ProductRatePlanCharge__c();
        productrateplanchargeflatfee.Name = 'Test Flat Fee Product Rate Plan Charge';
        productrateplanchargeflatfee.zqu__Model__c = 'Flat Fee Pricing';
        productrateplanchargeflatfee.zqu__Type__c = 'Recurring';
        productrateplanchargeflatfee.zqu__UOM__c = 'UOM tesing';
        productrateplanchargeflatfee.zqu__DefaultQuantity__c = 1;
        productrateplanchargeflatfee.zqu__MinQuantity__c = 0;
        productrateplanchargeflatfee.zqu__MaxQuantity__c = 500;
        productrateplanchargeflatfee.zqu__RecurringPeriod__c = 'Month';
        productrateplanchargeflatfee.zqu__ZuoraId__c = MOCK_ZUORA_PROD_ID;
        productrateplanchargeflatfee.zqu__ProductRatePlan__c = testproductrateplan.Id;
        productrateplanchargeflatfee.zqu__Deleted__c = false;
        productrateplanchargeflatfee.zqu__ListPrice__c = 1000;
        
        //creating Product rate plan charge with Model as 'Tiered Pricing' and Type as 'Recurring'
        zqu__ProductRatePlanCharge__c productrateplanchargetierpricing = new zqu__ProductRatePlanCharge__c();
        productrateplanchargetierpricing.Name = 'Test Tiered Pricing Product Rate Plan Charge';
        productrateplanchargetierpricing.zqu__Model__c = 'Tiered Pricing';
        productrateplanchargetierpricing.zqu__Type__c = 'Recurring';
        productrateplanchargetierpricing.zqu__UOM__c = 'UOM tesing';
        productrateplanchargetierpricing.zqu__DefaultQuantity__c = 1;
        productrateplanchargetierpricing.zqu__MinQuantity__c = 0;
        productrateplanchargetierpricing.zqu__MaxQuantity__c = 500;
        productrateplanchargetierpricing.zqu__RecurringPeriod__c = 'Month';
        productrateplanchargetierpricing.zqu__ZuoraId__c = '1118e487366202430136757c960715b5';
        productrateplanchargetierpricing.zqu__ProductRatePlan__c = testproductrateplan.Id;
        productrateplanchargetierpricing.zqu__Deleted__c = false;
        productrateplanchargetierpricing.zqu__ListPrice__c = 1000;
        
        List<zqu__ProductRatePlanCharge__c> productrateplanchargeList = new List<zqu__ProductRatePlanCharge__c>{productrateplanchargeflatfee,productrateplanchargetierpricing};
        insert productrateplanchargeList;
        system.assertEquals([SELECT count() from zqu__ProductRatePlanCharge__c],2);
        
        //create quote amendment
        zqu__QuoteAmendment__c testQuoteAmendment = new zqu__QuoteAmendment__c();
        testQuoteAmendment.zqu__Quote__c = quote.Id;
        insert testQuoteAmendment;
        system.assertEquals([SELECT count() from zqu__QuoteAmendment__c],1);
        
        //create quote rate plan
        zqu__QuoteRatePlan__c testQuoteRatePlan = new zqu__QuoteRatePlan__c();
        testQuoteRatePlan.zqu__Quote__c = quote.Id;
        testQuoteRatePlan.zqu__ProductRatePlan__c = testproductrateplan.Id;
        testQuoteRatePlan.zqu__QuoteAmendment__c = testQuoteAmendment.Id;
        testQuoteRatePlan.zqu__QuoteProductName__c = 'Orange Box';
        insert testQuoteRatePlan;
        system.assertEquals([SELECT count() from zqu__QuoteRatePlan__c],1);
        
        //create quote rate plan charge
        List<zqu__QuoteRatePlanCharge__c> testQuoteRatePlanChargeList = new List<zqu__QuoteRatePlanCharge__c>();
        List<zqu__QuoteCharge_Tier__c> testquotechargetierList = new List<zqu__QuoteCharge_Tier__c>();
        List<zqu__ProductRatePlanChargeTier__c> testproductrateplanchargetierList = new List<zqu__ProductRatePlanChargeTier__c>();
        for(Integer productrateplanchargeIterator = 0;productrateplanchargeIterator < productrateplanchargeList.size();productrateplanchargeIterator++) {
            zqu__QuoteRatePlanCharge__c testQuoteRatePlanCharge = new zqu__QuoteRatePlanCharge__c();
            testQuoteRatePlanCharge.zqu__QuoteRatePlan__c = testQuoteRatePlan.Id;
            testQuoteRatePlanCharge.zqu__ProductRatePlanCharge__c = productrateplanchargeList[productrateplanchargeIterator].Id;
            testQuoteRatePlanCharge.zqu__Model__c = productrateplanchargeList[productrateplanchargeIterator].zqu__Model__c;
            testQuoteRatePlanCharge.zqu__ChargeType__c = productrateplanchargeList[productrateplanchargeIterator].zqu__Type__c;       
            testQuoteRatePlanChargeList.add(testQuoteRatePlanCharge);
            
            //create product rate plan charge tier
            zqu__ProductRatePlanChargeTier__c prodchargetier = new zqu__ProductRatePlanChargeTier__c(
                zqu__Active__c = false,
                zqu__Currency2__c = DEFAULT_CURRENCY,
                zqu__Currency__c = DEFAULT_CURRENCY,
                zqu__Deleted__c = false,
                zqu__EndingUnit__c = 0.0,
                zqu__IsOveragePrice__c = false,
                Name = '1',
                zqu__PriceFormat2__c ='Flat Fee',
                zqu__PriceFormat__c ='Flat Fee',
                zqu__Price__c = 1000,
                zqu__ProductRatePlanCharge__c = productrateplanchargeList[productrateplanchargeIterator].Id,
                zqu__StartingUnit__c = 0.0,
                zqu__Tier__c = 1
            );
            testproductrateplanchargetierList.add(prodchargetier);
        }        
        insert testQuoteRatePlanChargeList;
        insert testproductrateplanchargetierList;
        system.assertEquals([SELECT count() from zqu__QuoteRatePlanCharge__c],2);
        system.assertEquals([SELECT count() from zqu__ProductRatePlanChargeTier__c],2);
        
        //create quote charge tier
        for(Integer quoterateplanchargeIterator = 0;quoterateplanchargeIterator < testQuoteRatePlanChargeList.size();quoterateplanchargeIterator++){
            //create quote charge tier
            if(testQuoteRatePlanChargeList[quoterateplanchargeIterator].zqu__Model__c == 'Tiered Pricing') {               
                zqu__QuoteCharge_Tier__c tier = new zqu__QuoteCharge_Tier__c(
                    Name = String.valueOf('Tier ' + quoterateplanchargeIterator + 1),
                    zqu__Tier__c = quoterateplanchargeIterator + 1,
                    zqu__Price__c = quoterateplanchargeIterator + 1,
                    zqu__Discount__c = 0,
                    zqu__Effective_Price__c = 1000,
                    zqu__StartingUnit__c = quoterateplanchargeIterator,
                    zqu__EndingUnit__c = quoterateplanchargeIterator + 1,
                    zqu__IsOveragePrice__c = false,
                    zqu__QuoteRatePlanCharge__c = testQuoteRatePlanChargeList[quoterateplanchargeIterator].Id
                );
                testquotechargetierList.add(tier);
            }
        }
        insert testquotechargetierList;
        system.assertEquals([SELECT count() from zqu__QuoteCharge_Tier__c],1);
    }
    
    @isTest
    private static void test_perform() {
        
        zqu__Quote__c testquote = [SELECT Id from zqu__Quote__c LIMIT 1];
        system.assertNotEquals(testquote,null);
        
        zqu__QuoteRatePlan__c testQuoteRatePlan = [SELECT Id, zqu__QuoteProductName__c, zqu__ProductRatePlan__c from zqu__QuoteRatePlan__c WHERE zqu__Quote__c =:testquote.Id];
        system.assertNotEquals(testQuoteRatePlan,null);
​
        //Prepare the masterobject with parent object as zqu__Quote__c
        zqu.DataObject masterObject = new zqu.CommittedDataObject(testquote);
        system.assertNotEquals(masterObject,null);
        
        //Add the Quote Rate Plan Object as first child of the parent zqu__Quote__c
        zqu.DataObject rateplanObj = new zqu.CommittedDataObject(testQuoteRatePlan);
        system.assertNotEquals(rateplanObj,null);
        masterObject.addChild('zqu__QuoteRatePlan__c',rateplanObj);
        
        //Get the Quote Rate Plan Charge Groups and add Quote Rate Plan Charge & Tier details as children
        List<zqu.zChargeGroup> chargeGroups = zqu.zQuoteUtil.getChargeGroups(testquote.Id, new List<Id>{testQuoteRatePlan.zqu__ProductRatePlan__c});
        system.assertNotEquals(chargeGroups,null);
        for (zqu.zChargeGroup cg : chargeGroups) {
            zqu.DataObject dataCg = new zqu.ZChargeGroupDataObject(cg);
            for(zqu.DataObject chargedata : dataCg.getChildren('zqu__QuoteRatePlanCharge__c')) {
                masterObject.addChild('zqu__QuoteRatePlanCharge__c',chargedata);
            }
        }
                
        Map<String, Object> attributes = new Map<String, Object>();
        List<String> logs = new List<String>();
        
        Test.startTest();
            MyCustomActionPlugin customPlugin = new MyCustomActionPlugin();
            customPlugin.perform(masterObject, attributes, logs);
            customPlugin.isUpdateAction();
            customPlugin.isAddRemoveAction();
            customPlugin.isValidateAction();
        Test.stopTest();
    }
}