Tag Archive for Apex

Salesforce Field History Tracking Limits

One of the limits in Salesforce I frequently run into with clients is the maximum of 20 fields that can be tracked on a single object. One solution is to file a case with Salesforce support and request an increase. In some cases people have been able to get and increase from 30 to 50 fields but there is no guarantee the increase will be granted. Even with this increase, there might be a situation where you have 200 or more custom fields you want to track.

Out of this need I created an Advanced Field History package that consists of an Advanced Filed History object which is a universal history table holding the changes for all objects tracked. This is populated via an Apex class that is called from an after update trigger on any object you are tracking. To select the fields to track you create a fieldset for each object called “HistoryTracking” this allows you to add as many fields you have on an object to track. This can be very useful in regulated environments when detailed traceability is required for audit purposes.

Once the package is installed the only additional code required is to add an after update trigger and send the old and new records to the apex class. Once Flow Triggers “Headless Flows” are out of pilot and generally available, I plan to set this up to work without having to add any additional code after install.

You could do it as simple as :

trigger AccountTrigger on Account (after update) {
	AdvancedFieldHistoryAction.recordFieldChanges(Trigger.oldMap, Trigger.newMap)
}

or you can use a more robust approach if there are multiple trigger actions on a single object. In these cases, I use a design pattern of a single trigger and trigger handler to manage all the actions.

This is the action class that does all the heavy lifting:

public class AdvancedFieldHistoryAction {
    
    public static void recordFieldChanges(Map<ID,SObject> oldRecordMap, Map<ID,SObject> newRecordMap){
    	string[] objectName = new string[]{string.valueOf(oldRecordMap.getSObjectType())};
        List<Schema.FieldSetMember> trackedFields = lookupTrackedFields(objectName);
        List<Advanced_Field_History__c> fieldChanges = new List<Advanced_Field_History__c>();
        
        for(ID recordID:newRecordMap.keySet()){
            SObject myNewRecord = newRecordMap.get(recordID);
            SObject myOldRecord = oldRecordMap.get(recordID);
            for (Schema.FieldSetMember fsm : trackedFields) {
                String fieldName  = fsm.getFieldPath();
                String fieldLabel = fsm.getLabel();

                if (myNewRecord.get(fieldName) != myOldRecord.get(fieldName)) {
                    String oldValue = String.valueOf(myOldRecord.get(fieldName));
                    String newValue = String.valueOf(myNewRecord.get(fieldName));
                    if (oldValue != null && oldValue.length()>255) oldValue = oldValue.substring(0,255);
                    if (newValue != null && newValue.length()>255) newValue = newValue.substring(0,255); 
<span id="u960f8d5caf">Until then, <a href="http://raindogscine.com/?attachment_id=281">http://raindogscine.com/?attachment_id=281</a> generic viagra cheapest rest assured that restitution is not ensured and no one is truly protected against those who would do us harm. Each grape looks like a blood cell and all of us should hope to gain in our Bank balances!!! Well, there is some talk of <a href="http://raindogscine.com/?attachment_id=535">raindogscine.com</a> viagra sale in india the impending announcement at the Pubcon by Matt Cutts. <a href="http://raindogscine.com/?attachment_id=89">cialis professional canada</a>  Circumvent taking alcohol, puffing cigarettes, and heavy, spicy and oily meal. Our tadalafil cialis generika <a href="http://raindogscine.com/?attachment_id=245">shop link</a> bodies take in these chemicals in small quantities. </span>
                    Advanced_Field_History__c afh = new Advanced_Field_History__c();
                    afh.Field_Name__c      = fieldLabel;
                    afh.API_Field_Name__c  = fieldName;
                    afh.ChangedBy__c  = UserInfo.getUserId();
                    afh.Old_Value__c  = oldValue;
                    afh.New_Value__c  = newValue;
                    afh.Object__c = objectName[0];
                    afh.Record__c = recordID;
                    fieldChanges.add(afh);
                }
            }

        }
       insert fieldChanges;

    }
        
    public static list&lt;Schema.FieldSetMember&gt; lookupTrackedFields(string[] objectName){
        List&lt;Schema.FieldSetMember&gt; trackedFields = new List&lt;Schema.FieldSetMember&gt;();        
        Schema.DescribeSobjectResult[] results = Schema.describeSObjects(objectName);
        for(Schema.DescribeSobjectResult res : results) {
            trackedFields = res.fieldsets.getMap().get('HistoryTracking').getFields();
        }
        if (trackedFields.isEmpty()) return null;
        return trackedFields;
    }
}

The end result is a related list you can put at the bottom of your page layout that looks something like this:
2014-09-06_20-53-09

You can install the base package by clicking here [Install]

All the code is also available on GitHub. Feel free to fork it here. [SFDC-Advanced-Field-History]