Thursday, October 4, 2012

Roll up summary Using Trigger:

Roll up summary Using Trigger:
You can use this if your organization meets max roll up condition on any particular object.Observe the results either by logs for entire trigger operations or check the status on annual revenue by adding more opportunities for any account.The class looks like this

public class LREngine {

/*
Tempalte tokens
0 : Fields to project
1 : Object to query
2 : Optional WHERE clause filter to add
3 : Group By field name
*/
static String SOQL_TEMPLATE = 'SELECT {0} FROM {1} {2} GROUP BY {3}';

/**
Key driver method that rolls up lookup fields based on the context. This is specially useful in Trigger context.

@param ctx Context the complete context required to rollup
@param detailRecordsFromTrigger child/detail records which are modified/created/deleted during the Trigger
@returns Array of in memory master objects. These objects are not updated back to the database
because we want client or calling code to have this freedom to do some post processing and update when required.
*/
public static Sobject[] rollUp(Context ctx, Sobject[] detailRecordsFromTrigger) {

// API name of the lookup field on detail sobject
        String lookUpFieldName = ctx.lookupField.getName();
     
        Set<Id> masterRecordIds = new Set<Id>();
        for (Sobject kid : detailRecordsFromTrigger) {
            masterRecordIds.add((Id)kid.get(lookUpFieldName));
        }
        return rollUp(ctx, masterRecordIds);
}

/**
Key driver method that rolls up lookup fields based on the context. This is meant to be called from non trigger contexts like
scheduled/batch apex, where we want to rollup on some master record ids.

@param Context the complete context required to rollup
@param masterIds Master record IDs whose child records should be rolled up.
@returns Array of in memory master objects. These objects are not updated back to the database
because we want client or calling code to have this freedom to do some post processing and update when required.
*/
public static Sobject[] rollUp(Context ctx,  Set<Id> masterIds) {
        // K: Id of master record
        // V: Empty sobject with ID field, this will be used for updating the masters
        Map<Id, Sobject> masterRecordsMap = new Map<Id, Sobject>();
        for (Id mId : masterIds) {
            masterRecordsMap.put(mId, ctx.master.newSobject(mId));
        }

    // #0 token : SOQL projection
    String soqlProjection = ctx.lookupField.getName();
    // k: detail field name, v: master field name
    Map<String, String> detail2MasterFldMap = new Map<String, String>();
    for (RollupSummaryField rsf : ctx.fieldsToRoll) {
    // create aggreate projection with alias for easy fetching via AggregateResult class
    // i.e. SUM(Amount) Amount
    soqlProjection += ', ' + rsf.operation + '(' + rsf.detail.getName() + ') ' + rsf.detail.getName();
    detail2MasterFldMap.put(rsf.detail.getName(), rsf.master.getName());
    }

    // #1 token for SOQL_TEMPLATE
    String detailTblName = ctx.detail.getDescribe().getName();
   
    // #2 Where clause
    String whereClause = '';
    if (ctx.detailWhereClause != null && ctx.detailWhereClause.trim().length() > 0) {
    whereClause = 'WHERE ' + ctx.detailWhereClause ;
    }
   
    // #3 Group by field
    String grpByFld = ctx.lookupField.getName();
   
    String soql = String.format(SOQL_TEMPLATE, new String[]{soqlProjection, detailTblName, whereClause, grpByFld});
    // aggregated results
    List<AggregateResult> results = Database.query(soql);
   
    for (AggregateResult res : results){
Id masterRecId = (Id)res.get(grpByFld);
Sobject masterObj = masterRecordsMap.get(masterRecId);
if (masterObj == null) {
System.debug(Logginglevel.WARN, 'No master record found for ID :' + masterRecId);
continue;
}

for (String detailFld : detail2MasterFldMap.keySet()) {
Object aggregatedDetailVal = res.get(detailFld);
masterObj.put(detail2MasterFldMap.get(detailFld), aggregatedDetailVal);
}    
    }
   
    return masterRecordsMap.values();
    }




    /**
        Exception throwed if Rollup Summary field is in bad state
    */
    public class BadRollUpSummaryStateException extends Exception {}

   /**
       Which rollup operation you want to perform
    */
    public enum RollupOperation {
        Sum, Max, Min, Avg, Count
    }
 
    /**
    Represents a "Single" roll up field, it contains
    - Master field where the rolled up info will be saved
    - Detail field that will be rolled up via any operation i.e. sum, avg etc
    - Operation to perform i.e. sum, avg, count etc
   
    */
    public class RollupSummaryField {
        public Schema.Describefieldresult master;
        public Schema.Describefieldresult detail;
        public RollupOperation operation;
     
        // derived fields, kept like this to save script lines later, by saving the same
        // computations over and over again
        public boolean isMasterTypeNumber;
        public boolean isDetailTypeNumber;
        public boolean isMasterTypeDateOrTime;
        public boolean isDetailTypeDateOrTime;
     
        public RollupSummaryField(Schema.Describefieldresult m,
                                         Schema.Describefieldresult d, RollupOperation op) {
            this.master = m;
            this.detail = d;
            this.operation = op;
            // caching these derived attrbutes for once
            // as their is no view state involved here
            // and this caching will lead to saving in script lines later on
            this.isMasterTypeNumber = isNumber(master.getType());
            this.isDetailTypeNumber = isNumber(detail.getType());
            this.isMasterTypeDateOrTime = isDateOrTime(master.getType());
            this.isDetailTypeDateOrTime = isDateOrTime(detail.getType());
            // validate if field is good to work on later
            validate();
        }
     
        void validate() {
            if (master == null || detail == null || operation == null)
                throw new BadRollUpSummaryStateException('All of Master/Detail Describefieldresult and RollupOperation info is mandantory');

            if ( (!isMasterTypeDateOrTime && !isMasterTypeNumber)
                  || (!isDetailTypeDateOrTime && !isDetailTypeNumber)) {
                    throw new BadRollUpSummaryStateException('Only Date/DateTime/Time/Numeric fields are allowed');
            }
         
            if (isMasterTypeDateOrTime && (RollupOperation.Sum == operation || RollupOperation.Avg == operation)) {
                throw new BadRollUpSummaryStateException('Sum/Avg doesnt looks like valid for dates ! Still want, then implement the IRollerCoaster yourself and change this class as required.');
            }
        }
     
        boolean isNumber (Schema.Displaytype dt) {
            return dt == Schema.Displaytype.Currency
                   || dt == Schema.Displaytype.Integer
                   || dt == Schema.Displaytype.Percent
                   || dt == Schema.Displaytype.Double;
        }
     
        boolean isDateOrTime(Schema.DisplayType dt) {
            return dt == Schema.Displaytype.Time
                   || dt == Schema.Displaytype.Date
                   || dt == Schema.Displaytype.Datetime;
        }
    }
 
    /**
    Context having all the information about the rollup to be done.
    Please note : This class encapsulates many rollup summary fields with different operations.
    */
public class Context {
   // Master Sobject Type
   public Schema.Sobjecttype master;
   // Child/Details Sobject Type
   public Schema.Sobjecttype detail;
   // Lookup field on Child/Detail Sobject
   public Schema.Describefieldresult lookupField;
   // various fields to rollup on
   public List<RollupSummaryField> fieldsToRoll;

// Where clause or filters to apply while aggregating detail records
public String detailWhereClause;  

   public Context(Schema.Sobjecttype m, Schema.Sobjecttype d,
                           Schema.Describefieldresult lf) {
this(m, d, lf, '');                          
        }
     
   public Context(Schema.Sobjecttype m, Schema.Sobjecttype d,
                           Schema.Describefieldresult lf, String detailWhereClause) {
       this.master = m;
       this.detail = d;
       this.lookupField = lf;
       this.detailWhereClause = detailWhereClause;
       this.fieldsToRoll = new List<RollupSummaryField>();
   }
 
   /**
    Adds new rollup summary fields to the context
   */
   public void add(RollupSummaryField fld) {
       this.fieldsToRoll.add(fld);
   }
}  
}
and the trigger is

trigger OppRollup on Opportunity (after insert, after update,                                          after delete, after undelete) {       // modified objects whose parent records should be updated      Opportunity[] objects = null;         if (Trigger.isDelete) {          objects = Trigger.old;      } else {         /*             Handle any filtering required, specially on Trigger.isUpdate event. If the rolled up fields             are not changed, then please make sure you skip the rollup operation.             We are not adding that for sake of similicity of this illustration.         */          objects = Trigger.new;      }      /*       First step is to create a context for LREngine, by specifying parent and child objects and       lookup relationship field name      */      LREngine.Context ctx = new LREngine.Context(Account.SobjectType, // parent object                                             Opportunity.SobjectType,  // child object                                             Schema.SObjectType.Opportunity.fields.AccountId // relationship field name                                             );           /*       Next, one can add multiple rollup fields on the above relationship.        Here specify         1. The field to aggregate in child object        2. The field to which aggregated value will be saved in master/parent object        3. The aggregate operation to be done i.e. SUM, AVG, COUNT, MIN/MAX      */      ctx.add(             new LREngine.RollupSummaryField(                                             Schema.SObjectType.Account.fields.AnnualRevenue,                                             Schema.SObjectType.Opportunity.fields.Amount,                                             LREngine.RollupOperation.Sum                                           ));       ctx.add(             new LREngine.RollupSummaryField(                                             Schema.SObjectType.Account.fields.SLAExpirationDate__c,                                                Schema.SObjectType.Opportunity.fields.CloseDate,                                                LREngine.RollupOperation.Max                                          ));                                             /*        Calling rollup method returns in memory master objects with aggregated values in them.        Please note these master records are not persisted back, so that client gets a chance        to post process them after rollup       */       Sobject[] masters = LREngine.rollUp(ctx, objects);          // Persiste the changes in master      update masters; }

No comments:

Post a Comment