Auto-populating the Opportunity name upon creation
We briefly considered some automatic numbering scheme for Opportunity records. But this only helped with uniquely identifying an Opportunity once it was created. We then considered a "rule" that each team member would need to follow when naming the opportunity. But, I do not want to ask my Sales users to stop and remember some "rule". I want them out servicing our customers and selling.
So, I proposed we implement the rule in a trigger. Here is how to do it.
First, I create a APEX class that will do the work. I do not like to put much code into triggers because this separates the code from the unit tests (which must be in a class). I really believe in putting the unit test code near to the work code for two reasons: (a) I can see both at the same time and reuse constants and help methods and (b) I don't like to pollute the global namespace with lots of class names. Many other developers share this concern and we've asked SF to allow us to put classes into folders. If you feel the way I do then vote for these ideas Enhanced Package/Namespace Support (like Java/.Net) and Visualforce pages, Apex class etc.. into folder.
The steps below are those I used to migrate from Test to Production. In other words all of the following worked within a sandbox and these are the steps I used to copy to Prod. Of course you will build this in a sandbox first, :)
Create the worker class and one worker method stub:
public with sharing class OpportunityUtilities { public static void ProcessOpportunityUpsert(ListoppList) { } }
Next create the trigger and call this work method on the updated items. I like to name my triggers to indicate what they work on and when.
trigger OpportunityUpsertTrigger on Opportunity (before insert, before update) { OpportunityUtilities.ProcessOpportunityUpsert(trigger.new); }
Aside:
As I attempt to write this blog post I'm stuck waiting and waiting for the Salesforce instance to respond. This prompted me to update and re-post an old blog posting on speeding up development. See http://sforcehacks.blogspot.ca/2013/02/speed-up-development-two-tips.html But even these tips didn't help and I've been stuck for a several of hours waiting for the Force IDE to save to server and run the unit tests.
Next build the unit test (test driven development) within
OpportunityUtilities
The test creates the account name, a project location, a main product and the expected Opportunity name composed of all three. In the test I select the user profile of the user type that normally works with Opportunities. This way the unit test also validates the code works for the expected user.
The special naming rules only are applied to a special record type. This allows me to provide this special rule for one group of sales people while not affecting others.
// in class OpportunityUtilities
static testMethod void runOpportunityUtilitiesTestCases() { Profile p = [select Id, Name from Profile where Name='Custom Profile']; System.debug(p.Id + ' ' + p.Name); User u = [select Id, Name from User where IsActive = true AND ProfileId = :p.Id limit 1]; System.debug(u.Id + ' ' + u.Name); RecordType customType = [select Id, Name from RecordType
where SobjectType = 'Opportunity' AND Name LIKE 'Custom%' limit 1]; RecordType defaultType = [select Id, Name from RecordType
where SobjectType = 'Opportunity' AND Name LIKE 'Default%' limit 1]; System.debug(customType.Id + ' ' + customType.Name); System.runAs(u) { String testLocation = 'Some location with super long location name that is greater than thirty characters'; String testAccountName = 'Some Account with super long name that is greater than forty characters'; String mainProductValue = 'Some Product with a long name'; String defaultOpportunityName = 'Some Project Name'; String expectedOpportunityName = testAccountName.substring(0,40) + SEP + testLocation.substring(0,30) + SEP + mainProductValue.substring(0,10) + SEP; expectedOpportunityName = expectedOpportunityName.trim(); Account act = new Account(Name=testAccountName, OwnerId = u.Id); insert act; act = [select Id, RecordTypeLabel__c from Account where Name = :testAccountName limit 1]; // custom record type Opportunity opp; opp = new Opportunity(Location__c=testLocation,
Main_Product__c= mainProductValue, AccountId = act.Id, StageName='Prospecting', Name = defaultOpportunityName,
CloseDate = System.Date.today(), RecordTypeId = customType.Id); insert opp; opp = [select Id, Name from Opportunity where Id = :opp.Id]; System.debug('runOpportunityUtilitiesTestCases ' + opp.Id + ' "' + opp.Name + '" ?=? "' + expectedOpportunityName +'"'); System.assert(expectedOpportunityName == opp.Name); // default record type opp = new Opportunity(Location__c=testLocation,
Main_Product__c= mainProductValue, AccountId = act.Id, StageName='Prospecting', Name = defaultOpportunityName,
CloseDate = System.Date.today(), RecordTypeId = defaultType.Id); insert opp; opp = [select Id, Name from Opportunity where Id = :opp.Id]; System.debug('runOpportunityUtilitiesTestCases ' + opp.Id + ' "' + opp.Name + '" ?=? "' + defaultOpportunityName +'"'); System.assert(defaultOpportunityName == opp.Name); } }
Now, here is the worker method and the main method that calls it.
public static String SEP = ' - '; public static void ProcessOpportunityUpsert(ListoppList) { ProcessTrafficOpportunityUpsert(oppList); } /** ProcessTrafficOpportunityUpsert To help users to quickly scan a list of opportunities and see what is important we reformat the Name. For every Traffic Opportunity reformat the Opportunity Name to fit this pattern: Account Name - Location__c - Main_Product__C - Case Number The Name field is limited to 100 chars so we truncate the fields to: Account 40 Location 30 Main Product 10 Case number is not truncated! */ public static void ProcessTrafficOpportunityUpsert(List oppList) { RecordType targetRecordType = [select Id, Name from RecordType where SobjectType = 'Opportunity' AND Name LIKE 'Traffic%' limit 1]; List toProcessList = new List (); for(Opportunity theRecord:oppList) { if(theRecord.RecordTypeId == targetRecordType.id) { toProcessList.add(theRecord); } } if(toProcessList.size() > 0) { List accountIds = new List (); List caseIds = new List (); for(Opportunity theRecord: toProcessList) { if(theRecord.AccountId!= null) accountIds.add(theRecord.AccountId); if(theRecord.Primary_Case__c!= null) caseIds.add(theRecord.Primary_Case__c); } List accountList = [select Id, Name from Account where id in :accountIds]; List caseList = [select Id, CaseNumber from Case where id in :caseIds]; Map nameMap = new Map (); for(Account acc: accountList) { nameMap.put(acc.id, acc.Name); } Map caseMap = new Map (); for(Case cs: caseList) { caseMap.put(cs.id, cs.CaseNumber); } for(Opportunity theRecord: toProcessList) { String accountName = nameMap.get(theRecord.AccountId); if(accountName.length()>39) accountName = accountName.substring(0, 40); String location = theRecord.Location__c; if(location.length()>29) location = location.substring(0, 30); String mainProduct = theRecord.Main_Product__c; if(mainProduct.length()>10) mainProduct = mainProduct.substring(0, 10); String caseNumber = caseMap.get(theRecord.Primary_Case__c); String composedName = ''; composedName += (accountName == null ? SEP : accountName + SEP); composedName += location + SEP; composedName += mainProduct + SEP; composedName += (caseNumber == null ? '' : caseNumber); theRecord.Name = composedName; } } }
No comments:
Post a Comment