Quantcast
Channel: Thoughts From a Cloudy Mind
Viewing all articles
Browse latest Browse all 8

Creating Rollup Summaries for Standard to Standard Objects

$
0
0

Update: @hammnick pointed out that you can in fact do this using roll-up summaries. I swear I checked this before hand and it wasn’t working. Regardless, I will keep this post up as I think it provides a useful learning tool. This would be more useful in a situation where you do not have a Master-Detail relationship and still want to gather counts. I am glad I didn’t spend too much time in this!

I was reading through @nikpanter‘s blog a little while ago and came across this post about learning Apex: http://www.xlerate.ca/nik/?p=165. In that post Nik mentioned writing a trigger to count the number of opportunities tied to an account as well as the number of won and number of closed opportunities. I reached out to Nik and confirmed that he hadn’t written this trigger and then proceeded to write it myself.

Since we are keeping track of opportunity counts on the account, we need to remember to update the account whenever an opportunity is inserted, updated, deleted or undeleted. We can calculate the stats after each of these actions are done so this will be an ‘After’ trigger for each of those events on the opportunity object.

We will need three custom fields on the Account to hold the values. I created the following fields:

  • Total # of Opportunities (Total_Number_of_Opportunities__c)
  • # of Closed Opportunities (Num_of_Closed_Opportunities__c)
  • # of Won Opportunities (Num_of_Won_Opportunities__c)
My approach was to first get all of the accounts tied to the opportunities in the trigger.  Once I had those, I could then query for ALL of the opportunities tied to each of those accounts and then count up each of our stats. I added the accounts to a map with a list of the opportunities so that we could check the size of each list tied to an opportunity in order to get the opportunity stats.
Since this is a trigger it will only take effect once an action is taken on an opportunity. You will want to perform an update on all of your opportunities to prompt the update of the account stats. You can use the data loader to export just the Id of all opportunities and then use that file to ‘update’ the opportunities, causing the trigger to fire and your accounts to be updated.
Trigger
trigger updateAccountOpportunityCounts on Opportunity (after insert, after update, after delete, after undelete) {

    ////////////////////////////////////////////////////
    //Set up Map, a Set and a List
    //Map will hold accounts and all opportunities tied to it
    //Set will hold Ids of accounts for the opportunities in the trigger
    //List to hold accounts which we will be updating
    ////////////////////////////////////////////////////
    Map<Account, List<Opportunity>> totalCountMap = new Map<Account, List<Opportunity>>();
    Set<Id> accountIdSet = new Set<Id>();
    List<Account> accountUpdateList = new List<Account>();

    ////////////////////////////////////////////////////
    //Create the set of Account IDs.
    //If this is a delete trigger, pull them the trigger.old, otherwise from trigger.new
    ////////////////////////////////////////////////////
    if(Trigger.isDelete)
        for(Opportunity o : trigger.old)
            accountIdSet.add(o.AccountId);
    else
        for(Opportunity o : trigger.new)
            accountIdSet.add(o.AccountId);

    //Query for all of the accounts assoicated with the opportunities in the trigger using the above created set of Ids
    Map<Id, Account> accountMap = new Map<Id, Account>([select Id, Name, Total_Number_of_Opportunities__c, Num_of_Won_Opportunities__c, Num_of_Closed_Opportunities__c  from Account where Id IN :accountIdSet]);

    ////////////////////////////////////////////////////
    //loop through each of the opportunities associated with the above accounts
    //add them to lists in the totalCountMap
    //**Note that in a delete trigger (since this is after delete), any deleted opportunities will NOT appear in the query
    ////////////////////////////////////////////////////
    for(Opportunity o: [select Id, AccountId, IsWon, IsClosed from Opportunity where AccountId IN :accountMap.keySet()]){

        //temporarily list to either get existing list from Map or insert new list to map
        List<Opportunity> tempList = new List<Opportunity>();

        //if this account already exists, get the list of opportunities so we can add this opportunity to it.
        if(totalCountMap.containsKey(accountMap.get(o.AccountId)))
            tempList = totalCountMap.get(accountMap.get(o.AccountId));

        //add the opportunity to the list (whether it is new or existing)
        tempList.add(o);

        //Add the Account and list of opportunites into the Map. This will replace a map entry if it already exists
        totalCountMap.put(accountMap.get(o.AccountId),tempList);
    }

    ////////////////////////////////////////////////////
    //loop through all of the accounts in the map and get the counts for total number of opps,
    //count of closed and count of won opps.
    ////////////////////////////////////////////////////
    for(Account a : totalCountMap.keySet()){
        //initialize our counters
        Integer wonOpps = 0;
        Integer closedOpps = 0;

        //loop through the associated opportunities and count up the opportunities
        for(Opportunity o : totalCountMap.get(a)){
            if(o.IsClosed){
                closedOpps++;
                if(o.IsWon) wonOpps++; //can't be won without being closed, reduce script statments by checking this here
            }
        }

        //set the counts
        a.Total_Number_of_Opportunities__c = totalCountMap.get(a).size();
        a.Num_of_Won_Opportunities__c = wonOpps;
        a.Num_of_Closed_Opportunities__c = closedOpps;

        //add the account to the list
        accountUpdateList.add(a);
    }

    //update all of the accounts if the list isn't empty
    if(!accountUpdateList.IsEmpty()) update accountUpdateList;
}
Of course we need to test the trigger as well. My test class creates 200 opportunities and adds them to an account. It then updates deletes and undeletes opportunities confirming the proper number of accounts each time:
Test Class
@isTest
private class testUpdateAccountOpportunityCounts {

    static testMethod void myUnitTest() {
        //Create an account
        Account a = new Account(Name='My Freakin Awesome Test Account');
        insert a;

        //create, say, 200 opportunites attached to said account
        List<Opportunity> oList = new List<Opportunity>();
        for(Integer i=1; i<201; i++){
          oList.add(new Opportunity(Name='Opportunity ' + i,
                        AccountId=a.id,
                        StageName='Qualification',
                        closeDate=Date.newInstance(2100,01,01)
                        )
         );
        }
        insert oList;

        //Make sure we actually have 200 opportunities and none of them are closed or closed won.
        a = [select Total_Number_of_Opportunities__c, Num_of_Won_Opportunities__c, Num_of_Closed_Opportunities__c from Account where Id = :a.id limit 1];
        System.assertEquals(200, a.Total_Number_of_Opportunities__c);
        System.assertEquals(0, a.Num_of_Won_Opportunities__c);
        System.assertEquals(0, a.Num_of_Closed_Opportunities__c);

        //Now let's close half of the opportunities, 50 of those won, other 50 lost
        for(Integer i=0; i<100; i++){
          if(i<50)
            oList[i].StageName='Closed Won';
          else
            oList[i].StageName='Closed Lost';
        }

        update oList;
        a = [select Total_Number_of_Opportunities__c, Num_of_Won_Opportunities__c, Num_of_Closed_Opportunities__c from Account where Id = :a.id limit 1];
        System.assertEquals(200, a.Total_Number_of_Opportunities__c);
        System.assertEquals(50, a.Num_of_Won_Opportunities__c);
        System.assertEquals(100, a.Num_of_Closed_Opportunities__c);

        //Let's delete some opportunities now (going to delete every other in the list)
        List<Opportunity> deleteList = new List<Opportunity>();
        for(Integer i=0; i<200; i++){
          deleteList.add(oList[i]);
          i++; //to skip every other
        }

        delete deleteList;
        a = [select Total_Number_of_Opportunities__c, Num_of_Won_Opportunities__c, Num_of_Closed_Opportunities__c from Account where Id = :a.id limit 1];
        System.assertEquals(100, a.Total_Number_of_Opportunities__c);
        System.assertEquals(25, a.Num_of_Won_Opportunities__c);
        System.assertEquals(50, a.Num_of_Closed_Opportunities__c);

        //Finally, let's undelete the deleted opportunities
        undelete deleteList;
        a = [select Total_Number_of_Opportunities__c, Num_of_Won_Opportunities__c, Num_of_Closed_Opportunities__c from Account where Id = :a.id limit 1];
        System.assertEquals(200, a.Total_Number_of_Opportunities__c);
        System.assertEquals(50, a.Num_of_Won_Opportunities__c);
        System.assertEquals(100, a.Num_of_Closed_Opportunities__c);

    }
}

You can install this trigger and class along with the above three fields. You will need to add the fields to your page layouts if you choose too.

https://login.salesforce.com/packaging/installPackage.apexp?p0=04tE00000000Rnx



Viewing all articles
Browse latest Browse all 8

Trending Articles