»
S
I
D
E
B
A
R
«
Trigger that maintains events on a group calendar
September 25th, 2009 by Evan Callahan

Does your organization maintain a group calendar of staff events, trainings, room rentals, or any other set of dates that are also stored somewhere in Salesforce? Do you wish there were a way to display them on a calendar?

One option would be to use workflow to create an event on every user’s calendar whenever an object is saved. But that creates noise for users, and isn’t nearly as intuitive as having a separate calendar for a given purpose. What you really want is a group calendar available to everyone that displays only events pertaining to one thing.

The use case

My client, a theater that sells blocks of tickets to its shows using Salesforce, needed a place to track the status of all their bookings on a calendar. To meet their requirement, I created a public calendar in their account. (See my general post on group/public calendars on the NPower Seattle blog for how to do this.)

group_cal

Getting the events on the calendar

My first thought was to use workflow to put events on that new calendar whenever an opp was moved to the right stage. It turns out you can’t do that – although a public calendar can be the “owner” of an event, you can’t select a public calendar as the user for a workflow task or event. Besides, the client needed not only to put this event on the calendar in the first place, but to update it automatically anytime the stage of the opportunity changes.

To maintain the events on the calendar, I created the following trigger:


trigger oppChangeAfter on Opportunity (after delete, after insert, after undelete, after update) {
// trigger to manage events on the shared theater Show Calendar
// written by Evan Callahan, copyright (c) 2009 NPower Seattle
// released under the GNU General Public License, http://www.gnu.org/licenses/gpl.html

      // need to know the salesforce ID of our public calendar
      final id publicCalendarId = '02380000000YHci';    

      // this flag allows us to bypass all queries if the opp is not the type that goes on the calendar
      boolean createEvents = false;

      // get acct names to put them in the events we create
      set oppAccts = new set();
      map acctMap;

      if (!trigger.isDelete) {
            set acctIds = new set();
            for (opportunity o : trigger.new) {
                  if (o.stageName != 'Quote' && o.stageName != 'Closed Lost' && o.show_date__c != null
                                    && o.show_date__c >= system.today().addMonths(-1) && o.theater_city__c != null) {
                        createEvents = true;
                        oppAccts.add(o.accountId);
                  }
            }
            if (createEvents) acctMap = new map([select id, name from account where id in : oppAccts]);
      }  

      // get opp ids in this trigger set
      set oppIds = (trigger.isDelete ? trigger.oldmap : trigger.newmap).keyset();    

      // get contacts for these opps so we can attach them to the events
      map oppCons = new map();

      if (createEvents) {
            for (opportunityContactRole ocr : [select opportunityid, contactid
                                                                        from opportunityContactRole
                                                                        where opportunityId in : oppIds and isPrimary = true]) {
                  oppCons.put(ocr.opportunityId, ocr.contactId);
            }
      } 

      // get existing events for these opps - need to update them
      map tzShowEventMap = new map();
      for (event e : [Select id, e.WhoId, e.WhatId, e.Subject, e.OwnerId, e.IsReminderSet, e.IsAllDayEvent, e.ActivityDate
                                          From Event e where whatId in : oppIds and ownerId =: publicCalendarId ]) {
            tzShowEventMap.put(e.whatId, e);
      }      

      event[] eventsToDelete = new event[0];
      if (trigger.isDelete) {
            eventsToDelete = tzShowEventMap.values();
      } else if (createEvents) {
            // add an event for the show
            for (opportunity o : trigger.new) {
                  if (o.stageName != 'Quote' && o.stageName != 'Closed Lost' && o.show_date__c != null
                                    && o.show_date__c >= system.today().addMonths(-1) && o.theater_city__c != null) { 

                        // create the event or get the existing one
                        event e = tzShowEventMap.containsKey(o.id) ? tzShowEventMap.get(o.id) : new Event();
                        e.isReminderSet = false;
                        e.isAllDayEvent = true;                        

                        // connect to the contact, if any
                        e.whoId = oppCons.containsKey(o.id) ? oppCons.get(o.id) : null;                        

                        // connect to the opp
                        e.whatId = o.id;                        

                        // get the acct name
                        string acctName = (o.accountId==null || !acctMap.containsKey(o.accountId)) ? '[Unknown]' : acctMap.get(o.accountid).name;
                        if (acctName.length() > 50) acctName = acctName.substring(0, 50);                                    

                        // the subject of the event includes the opp stage, acct name, and number of tickets purchased
                        // example -  45 Booked: NPower Seattle
                        e.Subject = ((o.total_tickets__c != null && o.total_tickets__c > 0) ? o.total_tickets__c.format() + ' ': '') +
                               (o.StageName == 'Closed Won' ? 'Closed' : o.StageName) + ': ' + acctName;                               

                        // the date of the show is in a custom field
                        e.activityDate = o.show_date__c;                            

                        // to put it on the right calendar, set the owner ID to the calendar ID
                        e.ownerId = publicCalendarId;                          

                        // put this event on the map
                        tzShowEventMap.put(o.id, e);
                  } else {
                        // delete events with opps that no longer fit the bill
                        if (tzShowEventMap.containsKey(o.id)) eventsToDelete.add(tzShowEventMap.get(o.id));
                  }
            }
            // add and update!
            upsert tzShowEventMap.values();
      }
      if (!eventsToDelete.isEmpty()) delete eventsToDelete;
}

Another option for a truly public calendar

This calendar is working well – we created a link to it for all users, and they can hover over events on the calendar to see the details or link directly to the opportunity or associated contact. However, the calendar is not publicly available outside Salesforce.

Another option we considered was using the Google Apps API to add the opportunities to an external Google calendar. That would allow us to share the calendar with employees or partners who don’t have Salesforce accounts – or even to the general public. After taking a peek at the API on the developer site, I don’t think it would be much more effort. The Google calendar wouldn’t offer direct links to the Salesforce objects, but it would be a lot more flexible than the calendar in Salesforce.


One Response  
Jeff Douglas writes:
September 26th, 2009 at 7:59 am

Awesome example Evan! I’m definitely going to use (aka rip-off) your code. Thanks!!

Leave a Reply

»  Substance: WordPress   »  Style: Ahren Ahimsa