Transfer Data To A New Opportunity Using Apex


Last year, I authored a post detailing the method of transferring values to a new opportunity using the URL. This post serves as an alternative approach to achieving the same objective of automatically populating values to a new record, but this time utilizing Visualforce and an Apex Class controller.

For our demonstration, we’ll make some general assumptions about the values we want to pass to the new record. To keep it straightforward, our plan is to populate the Name, Stage, and Close Date of the new opportunity. Additionally, we’ll design the logic to be flexible enough to include any additional information that Salesforce might pass as key-value pairs.

Here’s an overview of how we will structure our logic:

  • The user clicks the “New Opportunity” button.
  • Since we are overriding the standard “New” button on the Opportunity object, the button is accessible from any standard/custom page where the “New” button resides (e.g., the Contact detail page related list and the Account detail page related list).
  • The override logic directs the user to the newOppOverride.page Visualforce page.
  • The action attribute in the newOppOverride.page definition executes the newOppOverride.class Apex logic.
  • If the Apex logic encounters any issues, the user remains on the newOppOverride.page, and errors are displayed.
  • If the Apex logic is successful, the user is directed to the standard Salesforce page for a New Opportunity record, and values for some of the fields will be pre-populated.

Now that we have a handle on the logic we can build the Visualforce page.

<apex:page action="{!prepopulateValues}" extensions="newOpportunityOverride" standardController="Opportunity">
<!--
	Created by: Greg Hacic
	Last Update: 9 February 2016 by Greg Hacic
	Questions?: greg@interactiveties.com
	
	Notes:
		- allows for pre-population of specific Opportunity field values when the "New" Opportunity button is clicked
-->
	<apex:sectionHeader subtitle="Auto-populate Certain Fields" title="Opportunity"></apex:sectionHeader><!-- the header for the page, which is not necessary -->
	<apex:pageMessages></apex:pageMessages><!-- error messaging section for page - allows for display of any issues to the human -->
</apex:page>

Some points to consider regarding the Visualforce page:

  • We utilize the standard controller for Opportunity, allowing the Visualforce page to serve as a button override for the object.
  • The prePopulateValues method, specified in the action attribute, is invoked before the page is rendered.
  • The apex:pageMessages tag is the designated area for displaying any exception or failure messages.

Moving on to the Apex Class.

/*
	Created by: Greg Hacic
	Last Update: 9 February 2016 by Greg Hacic
	Questions?: greg@interactiveties.com
	
	NOTES:
		- extension for newOpportunityOverride.page
		- built in order to pre-populate a Name, Stage & Close Date values on new Opportunity records
		- tests located at newOpportunityOverrideTest.class
*/
public class newOpportunityOverride {
	
	private final Opportunity o; //Opportunity object
	
	//constructor
	public newOpportunityOverride(ApexPages.StandardController standardPageController) {
		o = (Opportunity)standardPageController.getRecord(); //instantiate the standard controller for the Opportunity object
	}
	
	//method called from the Visualforce page action attribute
	public PageReference prepopulateValues() {
		
		Map<String, String> passedParams = System.currentPageReference().getParameters(); //grab the parameters for the current page
		PageReference pageWhereWeEndUp = new PageReference('/006/e'); //set the return page reference to the New Opportunity page
		
		pageWhereWeEndUp.getParameters().putAll(passedParams); //copy all of the mappings from passedParams map to pageWhereWeEndUp map (in case Salesforce sends something we don't know we need)
		
		//if the Account Id was passed
		if (passedParams.containsKey('accid')) { //if the passedParams map contains the key accid
			List<Account> accounts = [SELECT Name FROM Account WHERE Id = :passedParams.get('accid')]; //query for Account.Name
			if (!accounts.isEmpty()) { //if the query resulted in a record
				pageWhereWeEndUp.getParameters().put('opp3', accounts[0].Name+' - '+Date.Today().format()); //pass the Opportunity Name value formatted as the Account.Name - date
			}
		}
			
		pageWhereWeEndUp.getParameters().put('opp11','Prospecting'); //StageName = Prospecting
		pageWhereWeEndUp.getParameters().put('opp9',Date.Today().addDays(30).format()); //Close Date defaulted to 30 days from now
		
		//you may get invalid session errors while trying to automatically save via redirect so we need to remove any auto save keys from the map
		String dropSaveNew = pageWhereWeEndUp.getParameters().remove('save_new'); //remove the save_new key value pair
		String dropSave = pageWhereWeEndUp.getParameters().remove('save'); //remove the save key value pair
		
		pageWhereWeEndUp.getParameters().put('nooverride', '1'); //prevents looping after recordtype selection (if applicable)
		pageWhereWeEndUp.setRedirect(true); //indicate that the redirect should be performed on the client side
		return pageWhereWeEndUp; //send the person on their way
	}

}

Due to potential access to the Visualforce page and class from various locations within Salesforce, we lack control over the information passed during navigation. Consequently, it is essential to accommodate the possibility of unexpected parameters. On line 26, we address this by passing all key-value pairs to the new page reference. This early integration allows us to overwrite existing parameters if needed.

Anticipating that Salesforce will naturally pass an Account Id as the key “accid” when available, we must handle scenarios where the “accid” key is not provided. Moreover, in situations where the key is absent, our objective is to guide the user to the new Opportunity detail page without populating the “Name” value. This specific logic is managed in lines 28-34 of the Apex class.

For individuals familiar with manipulating Salesforce URLs, it was once possible to include a “save” or “save_new” parameter to prompt the application to save the record without the user clicking the “Save” button. However, recent changes by Salesforce lead to invalid session Id exceptions when attempting to auto-save records via URL. Hence, lines 39-41 ensure the removal of these parameters if passed to our Apex class.

Now equipped with our Visualforce page and Apex class controller, we can override the “New” button on the Opportunity object. To do so, navigate to Setup > Customize > Opportunities > Buttons, Links, and Actions. Locate the “New” button link on the page, click the “Edit” link, select the newOpportunityOverride Visualforce page, and then click “Save.”

Completing our functionality, the last component is the Apex test logic. The provided unit test below ensures a 100.00% code coverage for the Apex Class as it is currently composed.

/*
	Created by: Greg Hacic
	Last Update: 9 February 2016 by Greg Hacic
	Questions?: greg@interactiveties.com
	
	Notes:
		- tests newOpportunityOverride.class (100.00% coverage)
*/
@isTest
private class newOpportunityOverrideTest {
	
	//newOpportunityOverride.class
	static testMethod void newOpportunityOverride() {
		//BEGIN: setup items
		//create some accounts
		List<Account> accounts = new List<Account>();
		accounts.add(new Account(Name = 'Testing Company'));
		insert accounts;
		//create some contacts
		List<Contact> contacts = new List<Contact>();
		contacts.add(new Contact(AccountId = accounts[0].Id, FirstName = 'Tess', LastName = 'Dachshund'));
		insert contacts;
		//END: setup items
		
		Test.startTest(); //switch to testing context
		
		PageReference pageRef = Page.newOpportunityOverride; //create a page reference to our Visualforce page
		Test.setCurrentPage(pageRef); //set the current page to that page
		ApexPages.StandardController standardController = new ApexPages.standardController(new Opportunity()); //instantiate the standard controller for the Opportunity object
		newOpportunityOverride ext = new newOpportunityOverride(standardController); //construct the extension
		
		//pass some parameters to the page
		pageRef.getParameters().put('save_new', '1'); //set save_new key value pair
		pageRef.getParameters().put('conid', contacts[0].Id); //set conid key value pair
		pageRef.getParameters().put('accid', accounts[0].Id); //set accid key value pair
		pageRef.getParameters().put('save', '1'); //set save key value pair
		
		//execute the logic
		String validationURLString = ext.prepopulateValues().getURL(); //get the resulting url from the prepopulateValues method
		
		//validate the results
		System.assertEquals(true, validationURLString.toLowerCase().contains('nooverride=1')); //string contains the nooverride key value pair
		System.assertEquals(true, validationURLString.contains('conid='+contacts[0].Id)); //string contains the conid key value pair
		System.assertEquals(true, validationURLString.contains('accid='+accounts[0].Id)); //string contains the accid key value pair
		System.assertEquals(true, validationURLString.contains('/006/e')); //string contains the new Opportunity URL
		System.assertEquals(true, validationURLString.contains('opp3')); //string contains the opp3 key
		System.assertEquals(true, validationURLString.contains('opp11')); //string contains the opp11 key
		System.assertEquals(true, validationURLString.contains('opp9')); //string contains the opp9 key
		System.assertEquals(false, validationURLString.toLowerCase().contains('save_new=1')); //string doesn't contain save_new=1
		System.assertEquals(false, validationURLString.toLowerCase().contains('save=1')); //string doesn't contain save=1
		
		Test.stopTest(); //revert back to normal context
	}

}

And there you have it—a different approach to transferring values to a new record, leveraging Apex this time. Moreover, this concept is versatile and applicable to any object within your organization.