Streamlining Opportunity Cloning in Salesforce for Annual Licensing

Introduction:

In Salesforce, organizations often encounter scenarios where they need to automate the process of cloning opportunities for annual licensing. This requirement involves creating a new opportunity for the upcoming year when an opportunity reaches the Closed Won stage. Additionally, the products associated with the original opportunity should be cloned as well. This article outlines a solution using an Apex batch process to automate the opportunity cloning, while also explaining the code and providing a sample test case.

Code Explanation:

The following Apex batch code accomplishes the opportunity cloning requirement:

                           
                              
   public class OpportunityCloningBatch implements Database.Batchable {
    public Database.QueryLocator start(Database.BatchableContext context) {
        //Clone those opportunities that are in stage Closed Won, whose close date is within one month, 
        and which have not yet been cloned
        Date currentDate = Date.today();
        Date oneMonthAgo = currentDate.addMonths(-1);
        
   String query = 'SELECT Id, Name, CloseDate, Cloning_Done__c, StageName,Pricebook2Id, (SELECT Id, 
   Product2Id, Quantity, UnitPrice FROM OpportunityLineItems) 
   FROM Opportunity '+ 'WHERE StageName = \'Closed Won\' AND Cloning_Done__c = false AND 
   CloseDate >= :oneMonthAgo AND CloseDate < :currentDate';
        return Database.getQueryLocator(query);
        
    }
    
    public void execute(Database.BatchableContext context, List scope) {
        List opportunitiesToClone = new List();
        List clonedProducts = new List();
        
        for (Opportunity opp : scope) {
            Opportunity newOpportunity = new Opportunity();
            
            // Clone relevant Opportunity field values
            opp.Cloning_Done__c = true;
            update opp;
            newOpportunity.Name = opp.Name + ' - ' + Date.today().year();
            newOpportunity.CloseDate = opp.CloseDate.addYears(1);
            newOpportunity.Pricebook2Id = opp.Pricebook2Id;
            newOpportunity.StageName = 'New';
            newOpportunity.Cloning_Done__c = false;
            
            opportunitiesToClone.add(newOpportunity);
            
            // Clone Opportunity Products
            for (OpportunityLineItem lineItem : opp.OpportunityLineItems) {
                OpportunityLineItem clonedItem = lineItem.clone(false, true, false, false);
                clonedItem.OpportunityId = null; // Remove association with the original 
                Opportunity
                clonedItem.Product2Id = lineItem.Product2Id;
                clonedItem.Quantity = lineItem.Quantity ;
                clonedItem.UnitPrice = lineItem.UnitPrice;
                clonedProducts.add(clonedItem);
            }
        }
        
        if (!opportunitiesToClone.isEmpty()) {
            insert opportunitiesToClone;
        }
        
        if (!clonedProducts.isEmpty()) {
            for (OpportunityLineItem clonedItem : clonedProducts) {
      // Assign the cloned Opportunity's Id
            clonedItem.OpportunityId = opportunitiesToClone[0].Id;            
 }
            
            insert clonedProducts;
        }
    }
    
    public void finish(Database.BatchableContext context) {
        // Additional logic after the batch execution (if needed)
    }
}


                           
                        

Explanation:

1. The `OpportunityCloningBatch` class implements the `Database.Batchable` interface and defines the required methods: `start`, `execute`, and `finish`.

2. In the `start` method, a query is constructed to fetch the opportunities that meet the cloning criteria: Closed Won stage, Cloning_Done__c field is false, and the Close Date falls within the last month.

3. The `execute` method iterates over the fetched opportunities and performs the cloning process.

4. For each opportunity, a new opportunity is created with the necessary field values cloned from the original opportunity. The new opportunity's Close Date is set to one year after the original opportunity's Close Date, and the Stage Name is set to "New".

5. The Opportunity Line Items are cloned by iterating through the original opportunity's line items and creating new line items with the same field values, excluding the association with the original opportunity.

6. The new opportunities and cloned line items are stored in lists for insertion.

7. The newly cloned opportunities are inserted into Salesforce, and the Cloning_Done__c field on the original opportunities is updated to true.

8. The `finish` method can be used for any additional logic that needs to be executed after the batch operation completes.

Test Case:

Here's an example test case for the `OpportunityCloningBatch`:

                           

                              @IsTest
private class OpportunityCloningBatchTest {
    @IsTest
    static void testCloneBatchClass() {
        Pricebook2 testPricebook = new Pricebook2(Name = 'Test Pricebook', IsActive = true);
        insert testPricebook;
        Product2 testProduct = new Product2(Name = 'Test Product');
        insert testProduct;
        Id pricebookId = Test.getStandardPricebookId();
        PricebookEntry testPricebookEntry = new PricebookEntry(
            Pricebook2Id = pricebookId,
            Product2Id = testProduct.Id,
            UnitPrice = 100,
            IsActive = true
        );
        insert testPricebookEntry;
        
        // Create test data
        Opportunity opp = new Opportunity(
            Name = 'Test Opportunity',
            StageName = 'Closed Won',
            CloseDate = Date.today().addDays(-10),
            Cloning_Done__c = false,
            Pricebook2Id = pricebookId
        );
        insert opp;

        // Add Opportunity Line Items
        OpportunityLineItem oli = new OpportunityLineItem(
            OpportunityId = opp.Id,
            PricebookEntryId = testPricebookEntry.Id,
            Quantity = 1,
            UnitPrice = 100

        );
        insert oli;

        Test.startTest();
        // Execute the batch class
        Database.executeBatch(new OpportunityCloningBatch(), 1);
        Test.stopTest();
        
    }
}

                           
                        

Test class which have code coverage to Batch class

Figure 1:Test class which have code coverage to Batch class

Original Opportunity record which has to be cloned by batch class

Figure 2:Original Opportunity record which has to be cloned by batch class.

Cloned Opportunity records after batch class run

Figure 3:Cloned Opportunity records after batch class run.

The same functionality can also be achieved using a Flow. This is explained below.

Implementing Scenario using Record-Triggered Flow

To implement Opportunity Cloning we can create a flow that clones an Opportunity With its Products. Once the flow is activated, it will automatically Clone an Opportunity and Opportunity Products whenever an Opportunity is created or Updated. To create a flow for Opportunity Cloning using Record-Triggered Flow, you can follow these steps:

  • Navigate to the Flow Builder in Salesforce and create a new flow.
  • Select “Object" as "Opportunity”, "Configure Trigger" as "A record is created or updated" and "Optimize the flow for:" as "Actions and Related Records".
  • Add a "Decision" Element to check when you want to Clone the record.
  • Add a "Create Record" element to the flow, and use the value of the fields of the record which Triggered the flow, we only need to change the data of the name field as ‘Triggered_opp_name – CurrentYear’, close date field with next year’s date , stage field as "New" and Cloning Done field as “False”.
  • Add an “Update Triggering Record" element to the flow, and to update the Triggered record field we only need to change the Cloning_Done as “True”.
  • Add a "Get Record" Element to get all the records of the Opportunity Product related to the triggered record.
  • Add a “Loop” Element to iterate over each record which has fulfilled the above condition.
  • Add a “Create record” Element to create the clone Opportunity Products of the triggered opportunity which contains similar data as on the iterated Opportunity products.
  • Save and activate the flow.

Once the Record-Triggered Flow is activated, it will automatically Clone an Opportunity and its related Opportunity Products whenever an Opportunity is created or Updated. Here's a screenshot of a sample flow:

Cloned Opportunity records after batch class run

Figure 4:Cloned Opportunity records after batch class run.

Original Opportunity record which has to be cloned

Figure 5:Original Opportunity record which has to be cloned.

Cloned opportunity record after flow is triggered

Figure 6:Cloned opportunity record after flow is triggered.

Conclusion:

Automating the cloning of opportunities for annual licensing in Salesforce can significantly improve productivity and ensure accurate record management. By implementing the provided Apex batch code, organizations can seamlessly create new opportunities with cloned products based on specific criteria. Additionally, the article explained the key sections of the code and provided a sample test case to verify the expected behavior of the solution. Leverage this solution to streamline opportunity cloning and enhance your organization's sales processes.

For any queries please reach out to support@astreait.com.