nonprofitCRM.org is produced by members of the NPSF (nonprofit salesforce.com) community. We are Salesforce.com administrators and consultants working to help nonprofits understand, better use and leverage Salesforce.com for their organizations. Read More
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.)
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.
Ever since this feature was requested by our old pal Steve Andersen, I’ve been waiting to play around with this. This feature makes looking at a specific Campaign wayyyy more useful. First thing you should do (if you haven’t already) is add the CampaignMember Related List to the Campaign Page Layout. Ta-da! You can now see which Leads or Contacts are members of your Campaign without running that sucky, uncustomizable Campaign Call Down Report. But wait! You can now customize that Campaign Call Down Report too! Woo-hoo!
Okay, so now you’ve got the report of your Campaign Members. Wouldn’t you love to indicate payment information on each of those members? Of course you do! Sure, you could customize the Campaign Member statuses to indicate RVSP, Paid, etc. But I’m sure you’d love to capture their payment details. Well, now you can go ahead and add custom fields to the Campaign Member object, like a Check #, Credit Card #, Amount, etc. Then, whenever you have events that you want to see if people have paid for, you can use the Campaign Member customization you just did. There are all sorts of ways to use this, and I’ve only touched on one. How are you using Campaign Member customization? Do you like the new Campaign Membership Management?
I’ve been working on a project that involves the use of CMSForce and since that’s a fairly new offering from Force.com Labs, thought I’d share my experiences so far. The install itself is easy enough, but there are a few post-install instructions that need to be followed for it to work, so be sure to download the documentation available on the AppExchange and read through it.
To use CMSForce, you’ll either need to use one of the Page Templates that comes with the application, or create your own, which involves Visualforce. The idea behind CMSForce, as with any CMS application, is that those templates can be created by someone more technical, and then anyone can go in and create new pages or adjust existing pages without needing a background in Visualforce, HTML, or style sheets. CMSForce definitely accomplishes this, and anyone willing to spend half an hour to an hour getting up to speed on it should quickly be able to start making use of the Sites functionality now native in the Enterprise version of Salesforce.
For the graphic design of the Site I did, my client already had a non-Salesforce web portal whose style they wanted to mimic, so all I had to do was create a Zip file of the CSS and image files (maintaining the folder structure on the web server), upload that as a Static Resource and then refer to the style sheets in the Visualforce Template. And the voila, standard HTML tags like <h1> (heading 1) now display with my client’s branding.
The Page Template is what it sounds like, it provides the styles and the frame for the page, which can include header, footer, menus, etc. In the Visualforce for the Template, you can insert Content Block components which are the placeholders for the content that users will be able to provide. From there you create Page records, which become individual pages in the site. Users then have the ability to update the content in each Content Block within the Page using a GUI editor. One page can be identified as the Home page and is the default when users go to your Site URL without specifying a specific page.
Once you have a Page Template and one or more Pages with Content Blocks created, you’re ready to set up a Site to go public. Once again, just read through the CMSForce documentation for all the configuration that needs to be done.
I’ve hit 2 issues that I’m still trying to resolve in this process. First, the content within the Content Blocks looks great in the preview but isn’t displaying in the public site. This I’m sure is a permission issue, and fortunately one of the Sites PMs from Salesforce will be helping me resolve that tomorrow. If this is a shortcoming in the documentation, I’ll be sure to post the resolution here. The other is that I created Visualforce page to display data from Salesforce and used that as a Page Template, and it isn’t functioning as expected in Sites. I suspect this has to do with the fact that my page is embedded as a component in another page, so once I figure that out I’ll post what I’ve learned here. Revision 8/26: both of these issues were, predictably, due to user error- I hadn’t realized that CMSForce wasn’t deployed yet. Once deployed, everything works as expected!
All in all, another solid application from Force.com Labs.
One of the great promises of using CRM for nonprofit program management is the ability to manage by metrics. In theory, if we could track all efforts and all outcomes in a system, then we could uncover those predictive indicators that lead to desired outcomes. We could then make adjustments to the program operations itself to reach those desired outcomes. To a great extent, this is possible, however, it is much more complex than it sounds. One of the root causes of the complexity is the fact that we often are not looking at all of the variables and are only looking at a subset. Making decisions on a subset of variables can sometimes lead to incorrect conclusions. Since I have seen this logic trail unfold a few times, I thought I would share an abstracted example of such a situation.
Take for example, Acme Org, a human services organization that has implemented a robust CRM solution that tracks almost all aspects of it’s program operations. Below is one of the critical reports reviewed by the Executive Team each month:
The organization is considering the use of these metrics for determining promotions, salary adjustments, and bonuses. After initial review, the Executive team expressed concern about Jon’s performance to Jon’s manager. Jon’s manager on the other hand knew that Jon’s metrics were low because he was always thrown on to the toughest cases. Being on the toughest cases each month meant that Jon would have to spend more time with each client and would be responsible for defining new engagement processes for working through complex cases. Unlike the other client service reps Jon spent a lot of time researching and speaking to industry experts to identify best practices that could be used by Acme to help future clients. In short, Jon was expanding the organizational knowledgebase, capacity, and quality of service delivery. Unfortunately, none of this was being recognized in the monthly report. Jon’s manager decided to add a new metric into the monthly report that would help recognize Jon’s efforts. The revised report is below:
The revised report gave the Executive Team more insight into Jon’s efforts and the demands placed on the Client Services staff.
The point of the example is to proceed with caution when using Metrics for Management level decisions. They can be a very powerful tool when well directed; however, there is usually a story behind every piece of data and it is critical that the entire story is understood before program level decisions are made.
If you haven’t already done so, make sure you get deep into Cross Object Formulas. They have fundamentally changed how we think and work with Salesforce.Com configurations. A Cross Object Formula is a Formula field that can traverse object relationships and pull down data into an object.
Take the following example. Let’s say you are using the Opportunities object to track Inbound Grants. Let’s say that you have a Payments object that has a Master/Detail relationship with Opportunities (Payments is the Detail).
You can setup the following Cross Object Formulas in the Payment object to reference elements from the Opportunity and Account.
Payment Made By (Account) = Opportunities__r.Account.Name Payment For (Opp) = Opportunities__r.Name
The beauty of the Cross Object formulas is that it lifts many of the constraints we previously faced with Salesforce. We can now: - Create more complex reports including more objects than we could in the past - Avoid writing Apex for something as silly as replicating a parent or lookup field - Ease data entry and report building for the end user by making data more accessible
There are some limitations to be aware of: - Limit of 5 cross object relationships per object - You can reference up (from detail to master) or sideways (lookup), however, for references from parent to child, you still need to use Summary Roll Ups.
If you haven’t already used these, you should definitely check them out as they can solve many common problems you may have faced in the past.
If you have any interesting use cases for cross object formulas, please post them in the comments.
We have been engaging in a lot of interesting debates in the NPower office about what makes a CRM solution sustainable for an organization. After a typical CRM deployment project, we do our best to leave the nonprofit with enough knowledge and tools to keep them sustainable. Here is a list of a few of the things we do:
Documentation – We initially started with word docs, however, we are more recently moving towards the use of Help Tips, Recorded Screen Casts, and a Help Tab. The idea behind documentation is that it should be complex enough to educate someone on how to use the CRM; however, not so complex that the documentation cannot be maintained.
Training – This is perhaps the most important element for us. We typically provide our clients with two levels of training. We do an end user training session that lasts between 1 and 3 hours depending on the complexity of the client implementation. We also do an administrator training for the Sys Admin level users at a client site. We also encourage the Sys Admin level users at the client site to attend a formal Salesforce training class.
Community – For clients that are in cities where CRM user groups are held, we encourage them to join and attend. This allows them to learn from their peers and get new insights into what is possible with CRM. We also encourage Sys Admin level users to join the NPSF so they can learn through the engaging technical conversations of the online community.
One of the areas that continues to be a challenge for us is Custom Code. Salesforce (and other CRM tools) provide a variety of means to extend the base platform with Custom Code. In the case of Salesforce this would be Apex, VisualForce, S-Control, and API Code. We are always weary of dropping custom code into an organization that has no means of managing or modifying the code. We do our best to avoid code by using as much standard functionality as possible. When it comes to deploying custom code into an organization that is not able to support it, we work through a simple cost/benefit tradeoff. Is this Code performing a high value business function? Is there a high probability the Code will need to be changed within the next 3 years? If the code needs to be changed, can the organization afford the change? If there is HIGH value for the code, LOW probability of the code needing to be changed, and the organization will likely find the funds for a change, then this alleviates our concerns. Custom Code is not to be feared in a NPO CRM implementation; however, a plan needs to be in place to support it over its lifecycle.
As we all know, Visualforce is going to be released this weekend with the full Summer 08 release. Are you ready and up to speed? If you have a Salesforce Developer Account (which you should because they are free!), take a look at http://wiki.apexdevnet.com/index.php/Force.com_Tutorial:_An_Introduction_to_Visualforce
Also, there is a free webinar next week which you can register for here:
http://wiki.apexdevnet.com/events/visualforcewebinar/registration.php?d=70130000000E15w
Start kicking the Visualforce tires and post comments about your experiences here!
A colleague of mine (Lisa Glass) recently began using encrypted fields in Salesforce. It turns out they are fairly simple to use. Encrypted fields need to be requested through the “Feature Activations Team” at Salesforce. You can just open a Support Ticket and put in a request for this. As per the documentation, this feature is available to all Enterprise and Unlimited edition accounts, which means all NPO’s should be able to access this.
How it works:
Limitations:
Overall, this is a very powerful feature and you should consider leveraging it in your instance if your are storing any sensitive data such as Social Security Number, Credit Card Number, Medical Info, etc.
There is a lot more information on best practices and usage examples within Salesforce Help.
I’ve been busy writing Apex, so no time to post to the blog. One thing I’m learning is that once you have some useful Apex code, like the lead conversion and payment processing tools we’ve created, it is easy to deploy it to multiple organizations — and that is just what we’re doing. However, in every case there are small tweaks to make, and more test methods to write, and more debugging, so it important not to underestimate the time needed to reuse your solutions.
We currently have two open positions in in our growing CRM consulting practice here at NPower Seattle (and many clients ready for help!). If you have Salesforce expertise or other relevant experience and are looking for a opportunity to work with nonprofits on a great team of developers and implementors, please take a look at these.
NPower Seattle serves over 450 nonprofit clients each year by providing technology consulting, education and training. Our clients include arts and environmental groups, human services agencies, food banks and youth-serving organizations.
Find full job descriptions at http://www.npowerseattle.org/get-involved/jobs. Submit resumes and cover letters to Resumes (at) NPowerSeattle.org.
Salesforce is so reliable and available, it is easy to forget to back up your data locally. Lately, I’ve was reminded that it is a good idea to back up your Salesforce data from time to time. Salesforce allows administrators to download a complete export up to once a week.
It isn’t that you have to fear for the Salesforce database crashing or being lost. I recommend downloading for a couple reasons:
To download the backup, go to Setup (Administration) | Data Management | Data Export. Click the button – a while later you’ll get an email with a link to download your data file. Save it somewhere safe.
Alternatively, if you use Demand Tools, you can back up to a file as often as you like. I use this to create a copy of the database in a Microsoft Access file.