How To: Clone a Hierarchy of Records Asynchronously with Super Clone Pro
February 19, 2018
How To: Increment a sequence in a text field when cloning a record in Salesforce
June 24, 2018

How To: Clone a record and delete the original record in Salesforce

Deleting the original record after successfully cloning it can be accomplished with a combination of Apex, a custom field, a Process Builder, and Super Clone Pro configurations. This sounds like a lot, but I promise that it will be easy. These directions will give you the Apex code that lets a Process Builder delete records, and they will illustrate all of the other settings you’ll need.

Fist we will get the hard part out of the way first. We will need to deploy some Apex code. Process Builder does not have a feature that lets you delete records. It will let you call an Apex class that can delete records, and that is what we are doing here. The only thing that may need adjustment is the test class. This test class creates accounts in the test. It may need to be changed if your environment has additional requirements to create an account. If not, you should be good to go.

  • Create a class named “PbDeleteRecords”, and paste in the code below.
/*
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

public with sharing class PbDeleteRecords {
    
    @InvocableVariable(
        Label='Record Id' 
        Description='Record Id of an object'
        Required=false)
    public String RecordId;
    
    @InvocableMethod(Label='Delete Record' Description='Deletes records for Ids passed into the method')
    public static void deleteRecords(list<PbDeleteRecords> pfDeleteRecordsList) {

        map<Id, sObject> deleteMap = new map<Id, sObject>();
        set<Id> idSet = new set<Id>();
        
        for (PbDeleteRecords dlt : pfDeleteRecordsList) {
            // skip if blank 
            if (String.isBlank(dlt.RecordId)) continue;
            
            // convert to Id type
            Id recId;
            try {
                recId = dlt.RecordId;
            } catch (exception e) {
                system.debug('Id exception: ' + e.getMessage());
                continue;
            }
            
            // create sObject for deletion
            sObject sObj = recId.getSObjectType().newSObject(recId);
            
            // put in map to prevent duplicates
            deleteMap.put(sObj.Id, sObj);
        }
        
        if (!deleteMap.isEmpty()) {
            // allOrNone=false, so delete errors do not stop the processing. 
            list<Database.DeleteResult> drList = database.delete(deleteMap.values(), false);
            for (Database.DeleteResult dr : drList) {
                if (dr.isSuccess()) {
                    System.debug('Deleted Record Id: ' + dr.getId());
                } else {
                    for(Database.Error err : dr.getErrors()) {
                        System.debug('Delete failure: ' + err.getStatusCode() + ' : ' + err.getMessage());
                    }
                }
            }
        }
    }
}
  • Create a class named “PbDeleteRecordsTest”, and paste in the code below.
/*
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 
THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

@isTest
private class PbDeleteRecordsTest {
    
    @testSetup static void setup() {
        // Create common test accounts
        List<Account> acctList = new List<Account>();
        for(Integer i=1;i<=4;i++) acctList.add(new Account(Name = 'TestAcct ('+i+')'));
        insert acctList;        
    }
    
    @isTest static void testDelete1() {
        list<PbDeleteRecords> pbList = new list<PbDeleteRecords>();
        
        // retrieve 3 of the 4 accounts to delete
        for (Account acc : [SELECT Id FROM Account LIMIT 3]) {
            PbDeleteRecords pb = new PbDeleteRecords();
            pb.RecordId = acc.Id;
            pbList.add(pb);
        }
        
        // run the delete method
        test.startTest();
        PbDeleteRecords.deleteRecords(pbList);
        test.stopTest();
        
        system.assertEquals(1, [SELECT Id FROM Account].size());
    }
    
    @isTest static void testDelete2() {
        list<PbDeleteRecords> pbList = new list<PbDeleteRecords>();
        PbDeleteRecords pb;
        
        // test with deleted record id 
        Account deleteAccount = [SELECT Id FROM Account LIMIT 1];
        pb = new PbDeleteRecords();
        pb.RecordId = deleteAccount.Id;
        pbList.add(pb);
        delete deleteAccount;
        
        // test with non-id value
        pb = new PbDeleteRecords();
        pb.RecordId = 'NotAnId';
        pbList.add(pb);
        
        // run the delete method
        test.startTest();
        PbDeleteRecords.deleteRecords(pbList);
        test.stopTest();
        
        system.assertEquals(3, [SELECT Id FROM Account].size());
    }  
}
  • Work with your Salesforce administrator to deploy the two classes.

Next, lets create a custom field on the object that we will be deleting. Users should have Edit permission to the field, so Super Clone Pro can assign a value to the field. However, this field should not be displayed on any page layouts.

  • Create a “Text” field 18 characters long that is labeled “Delete Source Id”.

Now we will create a Process Builder flow to run the Apex class we deployed.  The Process Builder logic will be set to only run on Insert to prevent the delete logic from running if someone accidentally enters a value into the field at a later date.

  • Create a Process Builder flow for your object.  The example below uses the Account object to demonstrate the steps.

  • Create a condition named “Delete source record”.  Enter the logic shown below to only run the steps if the record is new and field value isn’t blank.

  • Create a Call Apex action named “Delete Source Record”. This step calls the Apex method, and it passes in the value from the “Delete Source Id” custom field.

  • Create a Field Update action named “Clear Delete Source Id”. This step will blank out the value of the “Delete Source Id” custom field.

  • Activate the Process Builder.

We are almost there. Now we need to set the Clone configuration to put the original record’s Id into the “Delete Source Id” custom field, so it is there for the Process Builder.

  • Open the Super Clone Pro configuration for your object.
  • Find the “Delete Source Id” field in the list, and set the field Action to “Action Formula List”. Then enter “field(Id)” in the value column. This field should not be displayed on the page.
  • Save the configuration.

That’s it! That wasn’t too hard after we got the Apex class out of the way. Now the next time you clone, the original record will be deleted when the new record is inserted.

 

* Here is the disclaimer. The code comes with no warranty. I think it’s pretty good, but I’m bias because I wrote it. However, every Salesforce environment has a unique setup with different objects and custom logic, so there are bound to be instances where things don’t run as expected. This logic will delete records. Please test, test, and test some more before deploying any new logic.

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.