Mass Contact Transfer – Part 2 (The Controller)

mgsmith | Monday, April 20th, 2009 | 8 Comments »

Click on the SourceCode tab above to view the full sourcecode for the application or to install a complete working version into your environment.

In the last blog post I went into some of the details behind the Mass Contact Transfer VisualForce page. In this post, I’ll delve into the page Controller and supporting classes that contain the business logic for the page.

The transferContacts class is the controller for the VisualForce page. The controller has four main functions:

  • Provide the get/set methods for fields used on the VisualForce page
  • Create picklists of users for the To and From user fields
  • A doSearch() method that looks for records based on the criteria entered
  • A doTransfer() method to transfer the selected contacts to the new users

Get/Set methods

Generally these are very simple methods to either set the value of a public variable or return that value. Get/Set methods are always preceded by the word get or set followed by the name of the variable. On the VisualForce page these are referenced as {!fieldname}. For example, using value=”!fromUserID}” on a VisualForce page will call the getfromUserID() method to retrieve the value and setfromUserId() to set the value when the form is submitted.

// Variables
public string fromUserID;
public string toUserID;

// Get/Set Methods for the FROM and TO UserID's
public String getfromUserID()   { return fromUserID; }
public String gettoUserID()     { return toUserID; }
public void setfromUserID(string userID)  { this.fromUserID = userID; }
public void settoUserID(string userID)    { this.toUserID = userID; }

The From and To User picklist fields

Rather than require users to use a search lookup for users on the transfer form, I implemented this using two picklists for the From and To users. The FromUsers list includes all Users while the ToUsers lists only includes Active users. The setup behind the two picklists fields is very straight forward.

VisualForce page code for a picklist. The codeblock below accesses the getFromUsers() method to fill the values in the tag.

<apex:selectList value="{!fromUserID}" size="1" required="false"  id="fromUserID">
<apex:selectOptions value="{!FromUsers}"></apex:selectOptions>
</apex:selectList>

Apex Controller code to return the picklist of values. The getFromUsers() method returns a List object of Users sorted by Name. The SelectOption list type accepts two values – an ID and a display name. When a FromUser name is selected from the list, the Apex Controller will be able to access the ID of that user by referencing the toUserID variable. This is set by the page to the ID of the selected when the form is submitted.

public List<SelectOption> getFromUsers() {
    List<SelectOption> options = new List<SelectOption>();
    options.add(new selectOption('', '--- select a User ---'));
    for (User u : [Select ID, Name FROM User Order by Name]) {
            options.add(new selectOption(u.ID, u.Name));
    }
    return options;
}

Searching for Contacts

The doSearch() method is called when the [Find] button is clicked on the page.

<apex:commandButton title="Find" value="Find" action="{!doSearch}"/>

The overall goal of the method is to build an SOQL string, run the Query, and display a list of contacts on the page. As discussed in Part 1, the searchCriteria() class contains the methods necessary to generate the actual WHERE clause parts to build the query string. The logic behind this is shown below.

To start, a base SOQL string is built selecting all the fields in the Contact object along with key fields from the Account, Owner, Account.Owner, CreatedBy.Owner, and LastModifiedBy.Owner relationships. The initial contents of the WHERE exclude Contacts where the current owner is the owner to transfer contacts to (in other words, they own the contacts already).

// Build the base SOQL String, querying the standard Contact fields
// WHERE the current OwnerID = the selected value
string cSOQL = 'SELECT ' + contactFieldsList + ', Account.Name, Account.Site, Account.Owner.Name, '  +
'Account.Industry, Account.Type, Account.Owner.Alias, ' +
'Owner.Name, Owner.Alias, CreatedBy.Name, CreatedBy.Alias, LastModifiedBy.Name, LastModifiedBy.Alias ' +
'FROM Contact WHERE OwnerID <> \'' + toUserID + '\' ';

If a To Owner was selected, add to the WHERE clause to restrict the list of contacts owned by the To User ID.

// If a From User was selected, add this to the criteria
if (fromUserID <> null) cSOQL += ' AND OwnerID = \'' + fromUserID + '\' ';

For each line of Criteria, call the BuildWhereClause() method in the searchCriteria class.

// For each criteria line item, call the method to build the where clause component
for (searchCriteria cl : criteriaLine) {
cSOQL += cl.buildWhereClause(DebugMode);
}

Finally, order the list by Account Name and then Contact Name and limit the list to the first 250 contacts.

// Sort the results and limit to the first 250 rows
cSOQL += ' ORDER BY Account.Name, Name LIMIT 250' ;

searchCriteria class

The BuildWhereClause() to build the WHERE clause component for each of the search criteria lines is where it got complex. This method had to generate code to handle different field types (Text, PickList, Boolean, Number, Date, DateTime, etc.), different operators (=, >, <, contains, IN, etc.) and different types of values. Most of the logic is fairly straight forward, checking the field type and the operator and generating errors where the two are not compatible (BOOLEAN and ‘Less Than’, for example).

The complex part of the logic is to handle Date and DateTime field types. The most recent version of the application now supports the date format of the current user when parsing the value entered. Where this became tedious was for DateTime types. For example, you might expect the following SOQL WHERE block to only retrieve Contacts created on April 1, 2009: WHERE CreatedDate = 2009-04-01. However, CreatedDate is a DateTime field and requires a timestamp, not just a date. In order to properly query Contacts created on 4/1/09 the actual WHERE clause needs to looks like: WHERE (CreatedDate >= 2009-04-01T00:00:00Z AND CreatedDate <= 2009-04-01T23:59:59Z)

Transferring the Contacts

The final step in the process is to transfer the selected contacts to the ‘To User’. The toTransfer() method starts by building a list of Contact ID’s for selected Contacts. Part 1 of this blog post goes into the checkbox on each Contact. Only checked contacts are transferred. The list of ID’s is passed to a Query to select the Contacts. Finally, the OwnerID for each Contact is changed and the Database.Update() method is called to do the actual transfer.

// Build a list of Contact ID's to transfer ownership for
List<string> IDs = New List<string>();
for (transferContactSearchResults c : searchResults) {
if (c.selected) IDs.add(c.contact.ID) ;
}

// Query the contacts being transferred
List<Contact> contacts = [SELECT ID, OwnerID, Name, Account.Name, Title, Owner.Alias FROM Contact WHERE ID IN :IDs];
for (Contact c : contacts) {
c.ownerID = toUserID ;
}

// Process Errors and Count the Number of Records Transferred
Integer transferCount = 0;
List<database.saveresult> srs = database.update(contacts);
for (database.saveresult sr : srs) {
if (!sr.isSuccess()) {
      	ApexPages.AddMessage(new ApexPages.Message(ApexPages.Severity.FATAL, sr.getId() + '/' + sr.getErrors()[0].getMessage() ));
} else {
      	transferCount++;
      }
}

At the very end, the doSearch() method is called to look for additional contacts if there were more than 250 returned in the first query call.

Quick links to Class source code:

The final part of this blog post series will go into building the test class.

  • Share/Bookmark

8 Comments

  1. Vin D'Amico says:

    This is a wonderful tool. It allows you to quickly search for and transfer contacts. Well done!

    Just be aware that activities and events associated with the contacts are not re-assigned. That one addition would make this truly awesome.

    Thanks!

  2. Nathalie says:

    Installation fails with the test module – here is the error message:

    Package components error list:

    Problem Component Detail
    transfercontacts_test.myUnitTest() Apex Classes(01p00000000CjP2) System.Exception: Too many query rows: 501
    Class.transferContacts.getFromUsers: line 93, column 23
    Class.transferContacts_Test.myUnitTest: line 80, column 9

  3. mgsmith says:

    Nathalie,

    Seems that you have a quite a bit of active users, which is causing the Test method to fail due to SalesForce governor limits on test methods. I updated the code to have a ‘test’ mode where it limits the # of records retrieved.

    Mike

  4. Ken says:

    Fails to install with the message:
    Missing feature Apex Classes Installing this package requires the following feature and its associated permissions: Apex Classes

  5. Ken says:

    I should mention that this is for Professional edition, which is supposed to be supported.

  6. draicairl says:

    I found this site using google.com And i want to thank you for your work. You have done really very good site. Great work, great site! Thank you!

    Sorry for offtopic

  7. Mark says:

    I installed the app from AppExchange without any issues but when I open the Mass Transfer Contacts tab, I get the following error:

    System.Exception: collection exceeds maximum size: 1001

    An unexpected error has occurred. Your solution provider has been notified. (f2b)

  8. pk says:

    I am looking for a code which can replicate the product search feature similar to when you add multiple products to an opportunity or price book.

    In SFDC it shows the more filters and less filters link. Can you give some idea how to get a exact replica of it in a custom visualforce page.

Leave a Reply