How To: Filter Records in Related Lists With Super Clone Pro
December 16, 2017
How To: Clone a record and delete the original record in Salesforce
April 25, 2018

How To: Clone a Hierarchy of Records Asynchronously with Super Clone Pro

Cloning large and complex hierarchies of records can run into platform limits. Salesforce’s governor limits for CPU time limit, heap size, and view state can prevent successfully cloning a hierarchy. Factors that impact this include a large numbers of records, lots of data in the records, or a significant amount of custom Apex/Workflow/Formula logic. One solution is to run the Super Clone Pro process asynchronously. This provides higher limits to process the cloning logic successfully.

Submitting the cloning logic asynchronously requires additional custom coding. There is no warranty on the code below because each Salesforce environment and object being cloned is different, but this will hopefully provide a starting point that will help with your cloning.

  • This example uses custom objects called Solution and Solution Items. A custom Visualforce page will prompt the user for a new name, start date, and end date. This information will be passed to a queueable Apex job that runs the Super Clone Pro api.

  • The Visualforce page uses an actionpoller to check the job status. The Status will updates on the page job is processing and complete.

  • After completion, the records have been inserted, and will be available for the user to navigate to.

 

Super Clone Pro Configuration: solutionClone
* Notice the field actions set to “Set by URL Paramter”. The text in the Value column corresponds to the keys of the parameter map that is passed into the Super Clone Pro API. The API will apply the values from the parameter map to the fields in the Solution object.

Apex Class Queueable Job: SolutionCloneQueueable

public class SolutionCloneQueueable implements Queueable{
    // instance variable for parameters
    public final map<String, String> paramMap;
    
    // initialize
    public SolutionCloneQueueable(map<String, String> input) {
        paramMap = input;
    }
    
    // run clone logic
    public void execute(QueueableContext context) {
        set<Id> ObjIdSet = new set<Id>();
        map<Id, list<sObject>> ParentObjById;
        
        String recId = paramMap.get('recid');
        
        ObjIdSet.add(recId);
		Savepoint sp = Database.setSavepoint();
        try {
            system.debug('paramMap: ' + paramMap);
            // clone the record hierarchy
            ParentObjById = lcrm_scp.ScpApi.clone('solutionConfig', ObjIdSet, 1, paramMap);
            
            // retrieve new parent record 
            sObject newSObj = ParentObjById.get(recId)[0];
            list<Solution_Item__c> siList = new list<Solution_Item__c>();
            
            // perform post clone logic on Solution Items
            for(Solution_Item__c si : [SELECT Id, Name, Solution__c
                                         FROM Solution_Item__c
                                        WHERE Solution__c = :newSObj.Id]) {
            	si.Name = 'Add Logic for new name';      
                siList.add(si);
            }
            if (!siList.isEmpty()) {
                update siList;
            }
        } catch (exception e) {
            Database.rollback(sp);
            system.debug('error: ' + e.getMessage());
            // do other error handling logic
        }
        
    }
}

Apex Visualforce Controller: SolutionCloneController

public class SolutionCloneController {
    public Boolean isSubmitted {get; set;}
    public Boolean isCompleted {get; set;}
    public Boolean isPolling {get; set;}
    public Boolean isRecIdFound {get; set;}
    public String jobId {get; set;}
    public Solution__c sol {get; set;}
    public String recId;
    
    // initialize and retrieve the parent solution record
    public SolutionCloneController() {
        isSubmitted = false;
        isCompleted = false;
        isRecIdFound = false;
        recId = ApexPages.currentPage().getParameters().get('Id');
        try {
            sol = [SELECT Id, Name, Start_Date__c, End_Date__c
                   FROM Solution__c
                   WHERE Id = :recId
                   LIMIT 1];
            isRecIdFound = true;
        } catch (exception e) {
            ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR,'Solution record was not found. (' + recId + ')'));
        }
    }
    
    // submit the queueable apex job
    public void doSubmit() {
        // do custom parameter validation before submitting

        
        // setup parameter map
        map<String, String> paramMap = new map<String, String>();
        paramMap.put('recid', sol.Id);
        paramMap.put('name', sol.Name);
        paramMap.put('start', sol.Start_Date__c.format());
        paramMap.put('end', sol.End_Date__c.format());
        
        // submit the async job
        jobID = System.enqueueJob(new SolutionCloneQueueable(paramMap));
        isSubmitted = true;
    }
    
    // return to the origional record
    public PageReference doCancel() {
        return new PageReference('/'+recId);
    }
    
    // return the status of the queueable job
    public String getJobStatus() {
        AsyncApexJob jobInfo = [SELECT Status,NumberOfErrors FROM AsyncApexJob WHERE Id=:jobID];
        if(!'Queued'.equalsIgnoreCase(jobInfo.Status) && !'Processing'.equalsIgnoreCase(jobInfo.Status)) {
            isCompleted = true;
        }  
        return jobInfo.Status;
    }
    
}

Visualforce Page: SolutionClone

<apex:page controller="SolutionCloneController">
    <apex:form id="theForm">
        <apex:pageMessages id="errormsg" />
        <apex:pageBlock mode="maindetail">
            
            <apex:pageBlockButtons id="buttons" location="top">
                <apex:outputPanel rendered="{!!isSubmitted}" >
                    <apex:commandButton action="{!doSubmit}" value="Submit" status="submitButtonStatus" rerender="theForm" rendered="{!isRecIdFound}" />
                    <apex:commandButton action="{!DoCancel}" value="Cancel" immediate="true" />
                </apex:outputPanel> 
            </apex:pageBlockButtons>
            <apex:pageBlockSection columns="1" rendered="{!isRecIdFound && !isSubmitted}">
                <apex:inputField value="{!sol.Name}" />
                <apex:inputField value="{!sol.Start_Date__c}" />
                <apex:inputField value="{!sol.End_Date__c}" />
            </apex:pageBlockSection>
        </apex:pageBlock>
        
        <apex:outputPanel id="poll" rendered="{!isRecIdFound}">
            <apex:actionPoller rendered="{!!isCompleted}" reRender="completedjobs" interval="5"/>
            <apex:outputPanel rendered="{!isSubmitted}" id="completedjobs">
                <b>Status</b>: {!JobStatus} <br />
                <b>Job Id</b>: {!jobId}
                <apex:outputPanel rendered="{!isCompleted}">
                    <apex:actionFunction name="turnOffPoll" reRender="poll" />
                </apex:outputPanel>
            </apex:outputPanel>
        </apex:outputPanel>
    </apex:form>
</apex:page>

Custom Button on Solution page layout

/apex/SolutionClone?id={!Solution__c.Id}

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.