Teach Me Salesforce

A community approach to learning salesforce.com

Archive for the ‘Advanced’ Category

Integrating Google Maps in Salesforce

with one comment

Requirement is to show the location of my Account on Google Maps. It looks difficult but actually it is very simple to implement.

Just create a new visualforce page with this code

<apex:page standardController="Account">

<script src="http://maps.google.com/maps?file=api">
</script>

<script type="text/javascript">

var map = null;
var geocoder = null;

var address = "{!Account.BillingStreet}, {!Account.BillingPostalCode} {!Account.BillingCity}, {!Account.BillingState}, {!Account.BillingCountry}";

function initialize() {
if(GBrowserIsCompatible())
{
map = new GMap2(document.getElementById("MyMap"));
map.addControl(new GMapTypeControl());
map.addControl(new GLargeMapControl3D());

geocoder = new GClientGeocoder();
geocoder.getLatLng(
address,
function(point) {
if (!point) {
document.getElementById("MyMap").innerHTML = address + " not found";
} else {
map.setCenter(point, 13);
var marker = new GMarker(point);
map.addOverlay(marker);
marker.bindInfoWindowHtml("Account Name : <b><i> {!Account.Name} </i></b>
Address : "+address);
}
}
);
}
}
</script>
<div id="MyMap" style="width:100%;height:300px"></div>
<script>
initialize() ;
</script>

</apex:page>

As you can see that I have used standard controller so you need to pass the account Id in URL. Let’s say if the page name is “GoogleMaps” then the URL will look something like this : “…/apex/GoogleMaps?id=YOUR_ACCOUNT_ID”.

Use this page as in line visualforce page on native layout and enjoy the Google maps with Salesforce.

References

http://code.google.com/apis/maps/documentation/webservices/

http://code.google.com/apis/maps/index.html

 

Original Post by Forceguru : http://forceguru.blogspot.com/2012/01/integrating-google-maps-in-salesforce.html

Written by Ankit Arora (forceguru)

January 16, 2012 at 3:18 am

Displaying Role Hierarchy on Visualforce Page – Tree View

leave a comment »

This is a cross post from my Blog.

Ever thought of building a tree like data structure for the users based on role hierarchy and displaying it in the form of a JavaScript tree with node selection capability on the Visualforce page?

So recently I came across a functionality where a third party javascript calendar was used on the VisualForce page and all events were fetched programmatically through Apex and plotted on the calendar. The UI looked good along with other custom developed functionality. All was fine until client asked if it was possible to select some of the logged in user’s subordinates through custom VF page and plot events on the calendar for the selected users only. In other words, fetch and display only those events which were owned by users who worked below the logged in user in the role hierarchy.

It got me thinking and I did some research to check if there was an easier way to get this done, but soon realized that this required custom and tricky Apex/VF code. There’s a nice little script written by Jeff Douglas that was closest to what I actually wanted.

So I came up with this handy utility which fulfils my requirements. Getting user IDs of subordinates could also be useful in situations where, for example, you want to do a comparative analysis of performance for all users reporting to a manager.

There are mainly two parts to the solution I designed:

1. RoleUtil (Apex Class): Utility class which exposes the following API

a. public static RoleNodeWrapper getRootNodeOfUserTree (Id userOrRoleId) – function creates the tree data structure for the requested user or role ID and returns the root node to the caller

b. public static ListgetAllSubordinates (Id userId) – function returns the list of all subordinate users for the requested user ID

c. public static String getTreeJSON (Id userOrRoleId) – function returns the JSON string for the requested user or role ID

d. public static String getSObjectTypeById(Id objectId) – general utility function to return the string representation of the object type for the requested object ID

e. public static Boolean isRole (Id objId) – internally uses the getSObjectTypeById (#d above) to check whether the requested object ID is of UserRole type

f. public class RoleNodeWrapper (inner class) – wrapper for user role, represents a node in the tree data structure mentioned above and exposes boolean properties like hasChildren, hasUsers, isLeafNode, etc

public class RoleUtil {

    /********************* Properties used by getRootNodeOfUserTree function - starts **********************/
    // map to hold roles with Id as the key
    private static Map  roleUsersMap;

    // map to hold child roles with parentRoleId as the key
    private static Map > parentChildRoleMap;

    // List holds all subordinates
    private static List allSubordinates {get; set;}

    // Global JSON generator
    private static JSONGenerator gen {get; set;}

    /********************* Properties used by getRootNodeOfUserTree function - ends **********************/

    /********************* Properties used by getSObjectTypeById function - starts ********************* */
    // map to hold global describe data
    private static Map gd;

    // map to store objects and their prefixes
    private static Map keyPrefixMap;

    // to hold set of all sObject prefixes
    private static Set keyPrefixSet;
    /********************* Properties used by getSObjectTypeById function - ends **********************/

    /* // initialize helper data */ 
    static {
        // initialize helper data for getSObjectTypeById function
        init1();

        // initialize helper data for getRootNodeOfUserTree function
        init2();
    }

    /* // init1 starts  */
    private static void init1() {
        // get all objects from the org
        gd = Schema.getGlobalDescribe();

        // to store objects and their prefixes
        keyPrefixMap = new Map{};

        //get the object prefix in IDs
        keyPrefixSet = gd.keySet();

        // fill up the prefixes map
        for(String sObj : keyPrefixSet) {
            Schema.DescribeSObjectResult r =  gd.get(sObj).getDescribe();
            String tempName = r.getName();
            String tempPrefix = r.getKeyPrefix();
            keyPrefixMap.put(tempPrefix, tempName);
        }
    }
    /* // init1 ends */

    /* // init2 starts  */
    private static void init2() {

        // Create a blank list
        allSubordinates = new List();

        // Get role to users mapping in a map with key as role id
        roleUsersMap = new Map([select Id, Name, parentRoleId, (select id, name from users) from UserRole order by parentRoleId]);

        // populate parent role - child roles map
        parentChildRoleMap = new Map >();        
        for (UserRole r : roleUsersMap.values()) {
            List tempList;
            if (!parentChildRoleMap.containsKey(r.parentRoleId)){
                tempList = new List();
                tempList.Add(r);
                parentChildRoleMap.put(r.parentRoleId, tempList);
            }
            else {
                tempList = (List)parentChildRoleMap.get(r.parentRoleId);
                tempList.add(r);
                parentChildRoleMap.put(r.parentRoleId, tempList);
            }
        }
    } 
    /* // init2 ends */

    /* // public method to get the starting node of the RoleTree along with user list */
    public static RoleNodeWrapper getRootNodeOfUserTree (Id userOrRoleId) {
        return createNode(userOrRoleId);
    }

    /* // createNode starts */
    private static RoleNodeWrapper createNode(Id objId) {
        RoleNodeWrapper n = new RoleNodeWrapper();
        Id roleId;
        if (isRole(objId)) {
            roleId = objId;
            if (!roleUsersMap.get(roleId).Users.isEmpty()) {
                n.myUsers = roleUsersMap.get(roleId).Users;
                allSubordinates.addAll(n.myUsers);
                n.hasUsers = true;
            }
        }
        else {
            List tempUsrList = new List();
            User tempUser = [Select Id, Name, UserRoleId from User where Id =: objId];
            tempUsrList.add(tempUser);
            n.myUsers = tempUsrList;
            roleId = tempUser.UserRoleId;
        }
        n.myRoleId = roleId;
        n.myRoleName = roleUsersMap.get(roleId).Name;
        n.myParentRoleId = roleUsersMap.get(roleId).ParentRoleId;

        if (parentChildRoleMap.containsKey(roleId)){
            n.hasChildren = true;
            n.isLeafNode = false;
            List lst = new List();
            for (UserRole r : parentChildRoleMap.get(roleId)) {
                lst.add(createNode(r.Id));
            }           
            n.myChildNodes = lst;
        }
        else {
            n.isLeafNode = true;
            n.hasChildren = false;
        }
        return n;
    }

    public static List getAllSubordinates(Id userId){
        createNode(userId);
        return allSubordinates;
    }

    public static String getTreeJSON(Id userOrRoleId) {
        gen = JSON.createGenerator(true);
        RoleNodeWrapper node = createNode(userOrRoleId);
        gen.writeStartArray();
            convertNodeToJSON(node);
        gen.writeEndArray();
        return gen.getAsString();
    }

    private static void convertNodeToJSON(RoleNodeWrapper objRNW){
        gen.writeStartObject();
            gen.writeStringField('title', objRNW.myRoleName);
            gen.writeStringField('key', objRNW.myRoleId);
            gen.writeBooleanField('unselectable', false);
            gen.writeBooleanField('expand', true);
            gen.writeBooleanField('isFolder', true);
            if (objRNW.hasUsers || objRNW.hasChildren)
            {
                gen.writeFieldName('children');
                gen.writeStartArray();
                    if (objRNW.hasUsers)
                    {
                        for (User u : objRNW.myUsers)
                        {
                            gen.writeStartObject();
                                gen.writeStringField('title', u.Name);
                                gen.writeStringField('key', u.Id);
                            gen.WriteEndObject();
                        }
                    }
                    if (objRNW.hasChildren)
                    {
                        for (RoleNodeWrapper r : objRNW.myChildNodes)
                        {
                            convertNodeToJSON(r);
                        }
                    }
                gen.writeEndArray();
            }
        gen.writeEndObject();
    }

    /* // general utility function to get the SObjectType of the Id passed as the argument, to be used in conjunction with */ 
    public static String getSObjectTypeById(Id objectId) {
        String tPrefix = objectId;
        tPrefix = tPrefix.subString(0,3);

        //get the object type now
        String objectType = keyPrefixMap.get(tPrefix);
        return objectType;
    }
    /* // utility function getSObjectTypeById ends */

    /* // check the object type of objId using the utility function getSObjectTypeById and return 'true' if it's of Role type */
    public static Boolean isRole (Id objId) {
        if (getSObjectTypeById(objId) == String.valueOf(UserRole.sObjectType)) {
            return true;
        }
        else if (getSObjectTypeById(objId) == String.valueOf(User.sObjectType)) {
            return false;
        } 
        return false;
    }
    /* // isRole ends */

    public class RoleNodeWrapper {

        // Role info properties - begin
        public String myRoleName {get; set;}

        public Id myRoleId {get; set;}

        public String myParentRoleId {get; set;}
        // Role info properties - end

        // Node children identifier properties - begin
        public Boolean hasChildren {get; set;}

        public Boolean isLeafNode {get; set;}

        public Boolean hasUsers {get; set;}
        // Node children identifier properties - end

        // Node children properties - begin
        public List myUsers {get; set;}

        public List myChildNodes {get; set;}
        // Node children properties - end   

        public RoleNodeWrapper(){
            hasUsers = false;
            hasChildren = false;
        }
    }
 }

2. TreeView (Visualforce component): Dynatree based reusable VF component that exposes input parameters like

a. roleOrUserId – required string type input attribute

b. selectable – boolean attribute to indicate whether you want to display checkboxes against nodes in the tree for user selection

c. JsonData – optional string type input attribute, if supplied to the component ignores the “roleOrUserId” attribute and displays the tree structure for the input JSON string

d. value – a string type output attribute which returns the IDs/Keys of the selected nodes in the CSV format, which can then be utilised by the page controller

<apex:component controller="TreeViewController">
<apex:attribute name="roleOrUserId" required="true" type="String" assignTo="{!roleOrUserId}" description="Enter Role or User Id to build the hierarchy. Pass null if you are passing JSON data as a parameter" />
<apex:attribute name="selectable" type="Boolean" assignTo="{!selectable}" description="Do you want nodes to be selectable?" />
<apex:attribute name="value" type="String" description="IDs of selected Nodes in CSV format" />
<apex:attribute name="JsonData" type="String" assignTo="{!JsonData}" description="JSON input for the tree component" />
<apex:inputHidden id="selectedKeys" value="{!value}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'jquery/jquery.js' )}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'jquery/jquery-ui.custom.js' )}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'jquery/jquery.cookie.js' )}" />
<apex:includeScript value="{!URLFOR($Resource.DynaTree, 'src/jquery.dynatree.js' )}" />

<apex:stylesheet value="{!URLFOR($Resource.DynaTree, 'src/skin/ui.dynatree.css')}" />

<!-- Add code to initialize the tree when the document is loaded: -->
<script type="text/javascript">
$(function(){
// Attach the dynatree widget to an existing <div id="tree"> element
// and pass the tree options as an argument to the dynatree() function:
$("#tree").dynatree({
onActivate: function(node) {
// A DynaTreeNode object is passed to the activation handler
// Note: we also get this event, if persistence is on, and the page is reloaded.
//alert("You activated " + node.data.key);
},
persist: false,
checkbox: {!selectable},
generateIds: false,
classNames: {
checkbox: "dynatree-checkbox",
expanded: "dynatree-expanded"
},
selectMode: 3,
children: {!JsonString},
onSelect: function(select, node) {
// Get a list of all selected nodes, and convert to a key array:
var selKeys = $.map(node.tree.getSelectedNodes(), function(node){
return node.data.key;
});
jQuery(document.getElementById("{!$Component.selectedKeys}")).val(selKeys.join(", "));

// Get a list of all selected TOP nodes
var selRootNodes = node.tree.getSelectedNodes(true);
// ... and convert to a key array:
var selRootKeys = $.map(selRootNodes, function(node){
return node.data.key;
});
},
});
});
</script>

<!-- Add a <div> element where the tree should appear: -->
<div id="tree"> </div>

</apex:component>

You can see a working demo of the functionality here: http://treeview-developer-edition.ap1.force.com/

The code is available as unmanaged package (https://login.salesforce.com/packaging/installPackage.apexp?p0=04t90000000LlqQ) if you want to use it in your org. The code has been written assuming positive use cases and exceptional situations have not much been handled. It is advised to review and tweak the code before you use it in your org.

Written by Ankit Arora (forceguru)

January 16, 2012 at 2:59 am

JavaScript Remoting

with 2 comments

One of the major enhancements from visualforce this release(Summer 11) and my favorite is JavaScript remoting.

JavaScript Remoting is the ability to invoke apex class method from javascript embedded in a visualforce page.JavaScript Remoting is now available after summer 11 release.

Here is a quick example : we have a Apex class which defines a get Account method.

global with sharing class MyJSRemoting
{
    public static Account account { get; set; }

    @RemoteAction
    global static Account getAccount(String accountName) {
        account = [select id, name, phone, type, numberofemployees from
             Account where name = :accountName limit 1];
        return account;
    }
}

This method is having @RemoteAction annotation making it available to be called from a visualforce page. Now in our visualforce page we have a script tag set which is how we embed javascript within visualforce pages.

<apex:page controller="MyJSRemoting">

<script type="text/javascript">
function getAccountJS() {
    var accountNameJS = document.getElementById('accountname').value;

    ankit.MyJSRemoting.getAccount( accountNameJS, function(result, event)
    {
        if (event.status)
        {
           document.getElementById("{!$Component.theBlock.pbs.pbsi2.accId}")
                                  .innerHTML = result.Id;
           document.getElementById("{!$Component.theBlock.pbs.pbsi1.name}")
                                  .innerHTML = result.Name;
        }
    }, {escape:true});
}
</script>

    <input id="accountname" type="text">

    <button onclick="getAccountJS();">Get Account</button>

    <apex:pageblock id="theBlock">
        <apex:pageblocksection columns="2" id="pbs">

            <apex:pageblocksectionitem id="pbsi1">
                 <apex:outputtext id="name">
            </apex:outputtext></apex:pageblocksectionitem>

            <apex:pageblocksectionitem id="pbsi2">
                <apex:outputtext id="accId">
            </apex:outputtext></apex:pageblocksectionitem>

        </apex:pageblocksection>
    </apex:pageblock>
</apex:page>

This JavaScript code then invokes the getAccount method in the apex class to take action.

Please note I have used namespace “ankit.MyJSRemoting” as there is a namespace registered on my organization.

JavaScript Remoting is the feature which is primarily be used by developers and will allow them to create richer and more interactive user interfaces that everyone will benefit from.

In particular, lets developers create rich javascript based user interfaces by invoking apex controller methods within JavaScript code. JavaScript is typically used to render small parts of user interface. Result from a Javascript remoting call are much faster then using a typical visualforce reRender model, developers can provide more instant feedback to the user. This is because remoting calls are stateless. Means only data is set back and forth instead a lot of bulky HTML.

Additional Visualforce enhancements :

1) Filtered Lookup via Visualforce.
2) Inline editing for rich text area field.
3) Field Set property accessors.

This is a cross post to my Blog.

Written by Ankit Arora (forceguru)

June 19, 2011 at 4:33 pm

Visualforce Code Generator

with 9 comments

This is a cross post from my Blog Post.

We know how much important role visualforce plays in our application. There are many instance where we need to create a visualforce page and apex class. Salesforce provides powerful tools like workflows, approvals, validation etc. from which we can implement our business functionalities with just button clicks.

Now I was having a requirement where I need to create many visualforce pages, some are custom and some are cloning native page with native layouts with some small changes. This just clicked me to create a tool from which we can create a visualforce page code with just clicking buttons. Yes!! Visualforce page code with button click.

This saved a lot of time, as I do not need to write any thing to create a heavy visualforce page and amazingly it is done just using button clicks.

Install the package : https://login.salesforce.com/packaging/installPackage.apexp?p0=04t90000000Pqos

Just append “/apex/vfgenerator__codegenerator” in URL.

Now let me jump into the explanation what exactly I have done. A simple UI will be displayed, where user can select the desired type of page, object name, record type, email field.

Object Name : Valid object API name (include namespace if any)

Record Type : Record type Id of object selected in Object Name
Type of Page :

  1. Edit : Code will be generated for the edit page of the object (according to “record type” layout if selected any).
  2. Detail : Code will be generated for the detail page of the object (according to “record type” layout if selected any)
  3. Custom Detail : Code will be generated for detail page according to the fields selected from UI
  4. Custom Edit : Code will be generated for edit page according to the fields selected from UI

In the above screen I have selected “Edit” as the type of page and “Account” in object, also provided my email. When I click “Generate Code” it displays like this :

Just copy the code and paste it in new visualforce page. Why I have given my email id because when you paste the code in visualforce page it will loose all the formatting and will display in one single line, but code sent on email will come with formatting. So we can copy the generated code from email also. Now when you hit the newly created visualforce page it will display all fields in edit mode which are displayed on native edit page. Isn’t that good?

Now lets try creating some custom visualforce page. Select “Custom Edit” in “Type of Page” and “Account” in “Object Name”. It will display a section “Select Fields”. Click on “Display Fields”, it will display all fields which are up datable by present user.

Select some fields and click on  “Generate Code”. Again copy paste the code in your visualforce page, it will display selected fields in edit mode.

Now heavy visualforce pages are created with just button clicks.

Package provided is managed because the tool is under testing and I would like to have feedback from you all. Once it is free from all bugs I will provide the code.

Declaration: You may find similar post related to “Visualforce Code Generator”. The end result may be similar but there is a considerable difference in the approaches being followed here. So I, hereby declare that my project is entirely my own creation and has not been copied from any other person/organization’s words or idea. Please feel free to drop me an email at “arora.salesforce@gmail.com” if there is any disagreement.

Cheers

Written by Ankit Arora (forceguru)

June 15, 2011 at 2:32 pm

Sending more than 10 E-mails from Apex

with 5 comments

Most of time we stuck with limits which salesforce.com pull over us. I found this interesting as this push me to explore more how we can overcome this.

Now we have a limit of using “sendEmail” in apex and it is 10 times. Isn’t this bad??

This is a cross-post to my Blog.

Now when I am dealing with anything in bulk first thing comes to my mind is Batch Class. Now what is the scenario which leads me to send more than 10 emails?? I have 15 system administrators in my organization and each logs 2-3 cases in a day. Now I want to send an email which contains details of case logged by each system administrator separately, means To address and Body of each email will be different.

So just create a batch class with this code :

global class Batch_CaseEmail implements Database.Batchable<sObject>,Database.Stateful
{
   Map<Id , List<Case>> userCaseMap {get; set;}
   List<Case> allCaseLoggedToday {get; set;}

   global Batch_CaseEmail()
   {
       //Map to maintain user id and cases logged by them today
       userCaseMap = new Map<Id, List<Case>>() ;

       //All sales rep (System admins)
       List<User> salesRep = new List<User>() ;
       salesRep = [select id , name , Email , ManagerId from User
                    where Profile.Name = 'System Administrator'] ;

       //All sales rep ids
       List<Id> salesIds = new List<Id>() ;
       for(User ur : salesRep)
       {
           salesIds.add(ur.Id) ;
       }

       //All cases logged today by sales rep
       allCaseLoggedToday = new List<Case>() ;
       allCaseLoggedToday = [select Id, CaseNumber,CreatedById, Owner.name 
                            , account.Name , contact.name from Case 
                            where CreatedDate = TODAY AND CreatedById in : salesIds] ;
   }

   global Database.QueryLocator start(Database.BatchableContext BC)
   {
      //Creating map of user id with cases logged today by them
      for(Case c : allCaseLoggedToday)
      {
          if(userCaseMap.containsKey(c.CreatedById))
          {
              //Fetch the list of case and add the new case in it
              List<Case> tempList = userCaseMap.get(c.CreatedById) ;
              tempList.add(c);
              //Putting the refreshed case list in map
              userCaseMap.put(c.CreatedById , tempList) ;
          }
          else
          {
              //Creating a list of case and outting it in map
              userCaseMap.put(c.CreatedById , new List<Case>{c}) ;
          }
      }

      //Batch on all system admins (sales rep)
      String query = 'select id , name , Email from User 
                     where Profile.Name = \'System Administrator\'';
      return Database.getQueryLocator(query);
   }

   global void execute(Database.BatchableContext BC, List<sObject> scope)
   {

      for(Sobject s : scope)
      {
          //Type cast sObject in user object
          User ur = (User)s ;

          //If system admin has logged any case today then only mail will be sent
          if(userCaseMap.containsKey(ur.Id))
          {
              //Fetching all cases logged by sys admin
              List<Case> allCasesOfSalesRep = userCaseMap.get(ur.Id) ;

              String body = '' ;
              //Creating tabular format for the case details
              body = BodyFormat(allCasesOfSalesRep) ;

              //Sending Mail
              Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage() ;

              //Setting user email in to address
              String[] toAddresses = new String[] {ur.Email} ;

              // Assign the addresses for the To and CC lists to the mail object
              mail.setToAddresses(toAddresses) ;

              //Email subject to be changed
              mail.setSubject('New Case Logged');

              //Body of email
              mail.setHtmlBody('Hi ' + ur.Name + ',

 Details of Cases logged today is as follows : 

' + body + '
 Thanks');

              //Sending the email
              Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
          }
      }
   }

   public String BodyFormat(List<Case> lst)
   {
       String str = '' ;
       for(Case cs : lst)
       {
           str += '<tr><td>'+ cs.CaseNumber +'</td>'+'<td>'+ cs.Owner.Name 
                   +'</td>'+'<td>'+ cs.Account.Name +'</td>'
                   +'<td>'+ cs.Contact.Name +'</td>'+'</tr>' ;
       }
       str = str.replace('null' , '') ;
       String finalStr = '' ;
       finalStr = '<table border="1"> <td> CaseNumber </td> <td> Owner </td> 
                   <td> Account </td> <td> Contact </td> ' + str +'</table>' ;
       return finalStr ;
   }

   global void finish(Database.BatchableContext BC)
   {
   }

}

Code is self explanatory, hope there will be no issues understanding it.

Execute this class using below script in system logs :

Batch_CaseEmail controller = new Batch_CaseEmail() ;
Integer batchSize = 1;
database.executebatch(controller , batchSize);

All system administrators will get an email something like this :

Email Format

Written by Ankit Arora (forceguru)

June 5, 2011 at 2:12 pm

Apex Trigger and an Asprin can give you a dynamic approval process

with 3 comments

This post is crossed posted on my blog.

Ever come across the need of a large approval process where you do not know who the approver of the next stage will be? We all do don’t we?

In a recent project requirement, I had to work on a big approval process. My basic requirement was:

1. One approval process for child object
2. Once child is approved, it should trigger the master object approval process

Both the approval process had around 5 steps and the approvers where determined by the role hierarchy and the region of the submitter.

Yikes isn’t it?

Pre-requisites:
For this recipe, you need
1. Basic knowledge of APEX and trigger
2. Basic knowledge of Approval process
3. Aspirin (just in case you miss a step and go reverse engineering on it, its scary!!!)

Part I: The Child Object Dynamic Approval process.

Steps for Child Object Dynamic Process
1. Create two fields on the child object. These fields should not be shown on Page Layout.
i) Approval_step__c: Text field to determine which step the approval is.
ii) Next_approval__c: A user field to store the approver. (In case there are multiple approvers or parallel approver, create multiple fields)

2. Create the approval process.
i) Initial submission action will be field update: Approval_step__c=’Submitted for approval’
ii) In approver step, select related user and add the Next_approval__c
iii) In the approval Step Action, field update for Approval_step__c with the next step of Approval, e.g., ‘Regional Manager’ or maybe ‘Higher Manager’. Keep a  note of these keywords. (If the approval process is directly tied to the role hierarchy it is advisable to Update the field with next Role in approval, that way you can avoid confusion)
iv) Repeat the ii) and iii) till you finish all the approval steps.

3. Write a before update trigger on child object
i) Check if Trigger.old.Approval_step__c != Trigger.new.Approval_step__c (this is a pseudo code, you will have to iterate through trigger.new and old and compare all values, if you do not know how to do this, add a comment below)
ii) Now based on the value in Approval_step__c fetch the respective user from Role Hierarchy or anywhere else. Better option would be to use custom settings for this. Add this user in Next_approval__c
iii) Repeat step ii) till you finish all the approval steps. The final approval step would be to either empty the  Next_approval__c field or set it back to manager of the user.


4. Optional step If you need to trigger another approval process after this.
i) In the approval process, set the final approval action to ‘Approved’
ii) In the trigger if the field value is ‘Approved’ then submit the Master in Approval process using Apex Code. (There are many links which give you direct code, I might share the code later, but it has to be done in code if you want continuous approval process)

That’s it. It is easily understood that you need to repeat the whole process for master if it needs dynamic approval.
After you done everything, take a deep breath and test your code. If it doesn’t the way it should, take an aspirin and debug. Since this uses hand-shake between trigger and approval steps make sure you don’t miss out on any step.

Note: As this approval process updates back-end, the visual process manager won’t be able to visualize it.

Try it and hope you don’t need a aspirin.

P.s. This is my first post on this blog, do share your thoughts.

Written by Siddhesh Kabe

May 25, 2011 at 1:46 pm

Updating reports with Eclipse and the Force.com IDE

with 9 comments

I came across a question on the Salesforce.com Answers forum today and thought the answer might be useful to others.

Here is the original question: http://bit.ly/k3nKHD

The question concerned updating reports after a new Stage was created on the Opportunity Object.  Once the new stage was created and Opportunity records had been updated to use that new stage, any reports leveraging the old stage in their filters would be broken.  The poster of the question was hoping there was a faster way to identify the reports that filtered on that stage than opening each report individually.  I suggested using Eclipse and the Force.com IDE along with Notepad++ to get things done.  This will work with much more than just Stage, so insert your own search terms as desired.

The following assumes that you are already comfortable with Eclipse and the Force.com IDE:

The first thing you will want to do is download all of your report metadata to Eclipse.   If you have reports that are in Production and not your Sandbox then you will need to connect to your Production environment or refresh your Sandbox first.  If you do need to connect to Production just BE CAREFUL.

  • Create a new project
  • Include ‘reports’ or more specifically the report folders you are concerned with
  • Let Eclipse refresh the project with the reports metadata
Now that you have all of the metadata for your reports, it is time to search through the metadata for the StageName to find all of the reports using that Stage.
  • In Eclipse, navigate to the reports folder you just download. Right click and select properties
  • Highlight and Note (or copy) the Location. This is the path where the metadata files are located
  • Open a windows explorer window and navigate to the path you just copied
You should now see all of the folders containing your report metadata.
  • Open Notepad++ click Search > Find in Files  (or just Ctrl+Shift+F)
  • Enter your search term (in our case, the Stage) , make sure the ‘In all sub-folders’ box is checked and click the ‘Find All’
  • A message will pop up saying “Searching… Press Enter to Cancel”. Don’t touch anything, just let it do it’s search, this may take a little while depending on how many files you have.  Clicking OK will cancel the search.
You should now have a list of files where the Stage has been found.  You can use this to narrow down the reports that need to be updated and hopefully cut down the time you spend updating your reports.
Note: It is possible to update your reports directly from the IDE as well.  If you choose to do that, make sure you back up the report data to another location in case something goes wrong.

Written by knthornt

May 4, 2011 at 11:03 am

Posted in Advanced, Configuration

Tagged with

Follow

Get every new post delivered to your Inbox.

Join 195 other followers