Dec 3, 2012

Shared Activity for more than 10 Contacts


One of the major use of custom pages, classes are for extending the standard functions. I have recently come across one of such requirements. The requirement was:
  • Client meets, say 15 contacts out of 25 people in an Company (or Account)
  • User comes back and needs to record the call in one shot. i.e. instead of going to each contact and clicking on 'Log A Call' button 5 times they wanted an easier way of doing this
Solution that was proposed (and accepted after demo):
  • Have a button in Account details page - Create Mass Tasks (or Create Mass Calls)
  • This opens up a page which will show list of Contacts of that Account
  • User selects the required contacts - clicks on 'Create Call' button
  • This opens up Task/Event edit page
  • User enters details and clicks on Save
  • This should create tasks/events for each of the selected contacts
Now you might be wondering why we did not suggest Shared Activities, but if you note it clearly says that upto 10 Contacts can be related. But here we needed more than 10 contacts at a time (most of the time).

So now let us jump to the technical stuff. Created a VF page which shows all contacts of an account. For selection, I created a checkbox - Log A Call. The UI would be something similar to a list view.

VF page code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<apex:page extensions="MassLogACallController_Contacts" standardcontroller="Account">
    <apex:form>
        <apex:pageblock id="Customlist" title="Contacts ">
            <apex:pageblockbuttons>
                <apex:commandbutton action="{!CreateCall}" value="Create Call">
                <apex:commandbutton action="{!cancel}" value="Cancel">
            </apex:commandbutton></apex:commandbutton></apex:pageblockbuttons>
            
            <apex:pageblocktable value="{!lstContacts}" var="lst">
                <apex:column>
                    <apex:inputfield value="{!lst.Log_a_Call__c}">
                </apex:inputfield></apex:column> 
                <apex:column headervalue="Contact Name">
                    <apex:outputfield value="{!lst.Name}">
                </apex:outputfield></apex:column>
            </apex:pageblocktable>
        </apex:pageblock>
    </apex:form>
</apex:page>

Now the controller code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class MassLogACallController_Contacts {
    public List<contact> lstContacts {get; set;}
    public String accountId {get; set; }
    
    public MassLogACallController_Contacts(ApexPages.StandardController controller) {
        accountId = ApexPages.CurrentPage().getParameters().get('id');
        lstContacts = [select Name, Log_a_call__c from Contact where AccountId =: accountId];
    }
    
    public PageReference createCall() {
        update lstContacts;
        PageReference pg=new PageReference('/apex/MassCreateCall?accId=' + accountId);
        pg.setRedirect(true);
        return pg;
    }
}

So note that 'Create Call' method calls a VF page (MassCreateCall) by passing the Account ID. This page would replicate the detail page of task. It is simple and straight forward, so I will not include it's code. What's not simple is the 'Save' method. On click of Save, the code should create tasks (Activity History or Events - as per your requirement) for all those selected contacts.

Here is the constructor and 'save()' method code:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public MassCreateCallController_Events(ApexPages.StandardController controller) {
        this.evt = (Event)controller.getRecord();
        // more initialization
}

public PageReference createCalls() {
        try {
            if(lstContacts.size() &gt; 0) {
                // lstContacts is the list of Contacts of this Account whose Log_A_Call__c = true
                for(Contact c : lstContacts) {
                    newEvent = new Event();
                    newEvent.Call_Type__c = evt.Call_Type__c;
                    newEvent.Subject = evt.Subject;
                    newEvent.WhatId = accountId;
                    newEvent.IsAllDayEvent = evt.IsAllDayEvent;
                    newEvent.StartDateTime = evt.StartDateTime;
                    newEvent.EndDateTime = evt.EndDateTime;
                    newEvent.OwnerId = userId;
                    newEvent.RecordTypeId = evt.RecordTypeId;
                    newEvent.Description = evt.Description;
                    newEvent.WhoId = c.Id;
                    lstEvents.add(newEvent);
                    // Clear the checkbox of each contact
                    c.Log_a_call__c = false;
                }
                insert lstEvents;
                update lstContacts;
            }
        }
        catch(Exception e) {
            ApexPages.addMessage(new ApexPages.Message(ApexPAges.Severity.FATAL, e.getMessage()));
        }
        PageReference pg = new PageReference('/' + accountId);
        pg.setRedirect(true);
        return pg;
    }

As you can see I am looping through the list of contacts and inserting the task (Event in this case). 'evt' contains the values that are filled in the UI. So I am copying it to the new records while adding to list. Whenever a new record is added to list, I am clearing the checkbox (Log A Call) in each Contacts.

Works well with many use cases.

As always I am keen to know if any easier method was implemented/available.

Mar 23, 2012

Filling values into standard fields in PE

Long long ago I posted a blog about updating other object's status automatically. I have been planning for the next blog ever since. Finally took a break and posting one of the trick which I used in one of the project or implementation.

You all might know that in PE (Professional Edition) you can't write apex codes. That cuts off building much complex functionality from the instance. But sometimes clients are so adamant that they don't understand (or try so) developer's pain of inability of building the process that requires coding. Why? Because they have paid money!!
One of such situation which I recently came across: To re-use the Opportunity as Tender, but Stages should be different!! Here Opportunity Name cannot be used, as Tender Number (auto number) needs to be copied to the name. I could have used Tender as a custom object, but if I
did I had to re-do the standard functionality of adding products, quotes. Adding Products can be done with a junction object, but what about Quote, syncing etc?? Remember, it is PE. Hence had to reuse the Opportunity object.
Now the problem of overriding Opportunity Stage values with Tender Stage and copying the Tender Number to name to be solved. Obvious solution for different stages would be merging both Opportunity stages and Tender Stages into Stage field OR overriding with a VF
page and hiding the Stage. The first one is gonna piss-off the client (obviously design is not good), the latter looks promising but the Stage field is mandatory and it cannot be updated
through workflow because validation is executed before workflow. Did workflow field update (yes, this client had purchased workflow along with PE) come to your mind for copying the value? It doesn't work as (again) validation rule are executed before workflow field updates, hence it shows error on Name field.

Enough of dragging the problem, coming to the solution: Create 2 VF pages - one for Opportunity and other for Tender. Create 2 list buttons - New Opportunity and New Tender, these will be shown under Account, in Opportunity related list. We won’t have any problem with Opportunity VF as we are gonna use all standard fields. Problem would be with

Tender VF. Solution for this is to fill the mandatory field values through JavaScript. Hide the Stage, CloseDate, Name (or any other standard fields) from VF page and in the background fill some values in them.

Here is the Visualforce page code with some
JavaScript trick:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<apex:page id="page_opp" standardcontroller="Opportunity">    
<apex:form id="form_opp">
        <script type="text/javascript">
            function addLoadEvent(func) { 
                var oldonload = window.onload; 
                if (typeof window.onload != 'function') { 
                    window.onload = func; 
                }
                else { 
                    window.onload = function() { 
                        if (oldonload) { 
                            oldonload(); 
                        } 
                        func(); 
                    } 
                } 
            }
            addLoadEvent(test); 
            function test() {
                var d=new Date();
                document.getElementById("{!$Component.j_id2:gen:cls}").value = d.getDate() + '/' + (d.getMonth()+1) + '/' + d.getFullYear();
                document.getElementById("{!$Component.j_id2:gen:stg}").value = "SomeStageValue";
            }
        
</script>
        <apex:sectionheader subtitle="New Tender" title="Tender Details" />
        <apex:pageblock title="Tender Information">
        <apex:pagemessages/>
            <apex:pageblockbuttons location="Both">
                <apex:commandbutton action="{!Save}" value="Save" />
                <apex:commandbutton action="{!Cancel}" value="Cancel"/>
            </apex:pageblockbuttons>
            
            <apex:pageblocksection id="gen" showheader="false" title="General">
                <apex:inputfield label="Tender Name" value="{!Opportunity.Name}" />
                <apex:inputhidden id="stg" value="{!Opportunity.StageName}" />
                <apex:inputhidden id="cls" value="{!Opportunity.CloseDate}" />
                <apex:inputfield value="{!Opportunity.Tender_Authority__c}" />
                .....
                ...
                ..
            </apex:pageblocksection>
        </apex:pageblock>
</apex:form>
</apex:page>
As you can see, on load we are updating or inserting some values into those standard fields.

Comments are expected as there can be other ways of doing this. If you have any idea please share..

Featured Post

I am Salesforce Certified System Architect

By passing Identity & Access Management Designer, I earned System Architect. The journey of Application Architect & System Architec...

Popular Posts