Pages

Monday, October 24, 2011

Automated Phone Calls Via Workflow???

Sure, you've got four workflow actions that you can choose from when setting up your fancy new automated processes.
  • Tasks
  • Email Alerts
  • Field Updates
  • Outbound Messages
What if I said you could extend this to include:
  • Automated Calls
    • Pre-recorded messages
    • Robot voice messages (text-to-speech)
  • Text messages
Pretty sweet right?  Check out Call-em-All.  Let's get it out of the way now... I'm not affiliated with them and they were just a lucky Google search result that I found when searching for broadcast messaging solutions. Sign up for an account and then register for API authorization to get some test credits.

Through the GUI, you can setup call broadcasts; essentially the same as a mass e-mail, but with phone calls.  It's an easy service as all you do is upload your calls list (first name, last name, phone number) and record a message to be played.

They've got a great API that comes loaded with documentation, code samples, and a staging environment. 

You can leverage this to create some Apex that can be initiated by a button/link click, scheduled, or yes...even a trigger.

In the following sample, I will be initiating a call broadcast from my Opportunity by updating the Stage. Utilizing their text-to-speech feature, the call will dynamically include the Opportunity's name and stage. There will be two parts; the class and the trigger.

First, the class (excuse the body as it's lengthy):
public class Call_em {
@future (callout=true)
    public static void callMe(string a, string b){
        string Name = a;
        string stage = b;
        string username = 'username';
        string pin = 'pin';
        string broadcast_type = '1';
        string phone_number_source = '3';
        string broadcast_name = 'SAMPLE API Broadcast';
        string caller_id = 'caller_id';
        string PhoneNumbers = 'phonenumbers';
    string TTSText = 'This is a test call for the' + Name +' opportunity. The stage has been set to ' + stage;
        string proxy = 'http://staging-api.call-em-all.com/webservices/CEAAPI_v2.asmx?WSDL';

        HttpRequest req = new HttpRequest();
        req.setMethod('POST');
        req.setEndpoint('http://staging-api.call-em-all.com/webservices/CEAAPI_v2.asmx?WSDL');
        req.setHeader('Host', 'api.call-em-all.com');
        req.setHeader('Content-Type', 'text/xml; charset=utf-8');
        req.setHeader('SOAPAction', 'http://call-em-all.com/ExtCreateBroadcast');
        string bod = ''+username+''+pin+''+TTSText+''+TTSText+''+broadcast_name+''+broadcast_type+''+caller_ID+''+PhoneNumbers+''+phone_number_source+'';

        req.setbody(bod);

        Http http = new Http();
        HTTPResponse res = http.send(req);

        System.debug('These were the results: ' +res.getBody());
    }
}


And the trigger:
trigger Opportunity on Opportunity (before update){
    for(Opportunity o: trigger.new){
        if(yourcriteriahere!){
            Call_em.callMe(o.Name,o.StageName);
        }
    }
}


Explanations: I've kept the class as simple as possible for now. Ideally, you would probably not want to create a new broadcast every time the trigger was initiated. You would want to create your broadcast in advance and add the new number to it.
Note that the method is annotated with @future and callout=true. This is required as the trigger will make the callout asynchronously. The first part of the class is just setting up some variables. Looking at it, I don't think I used the proxy variable. My trigger will feed it two strings (yum); the Opportunity Name and the Opportunity Stage. I then create the new POST HTTPRequest, decalre the endpoint, and set up a few headers. Notice that my endpoint is set to their "staging" URL. I would recommend that this be set to a variable so that you could toggle between their staging and production environments relatively easily.

The trigger speaks for itself. You can use the "Call_em.callMe(o.Name,o.StageName)" line as an action for a button or even modify it to be used in a schedule Apex class.

Once you get this sample working, dig around in their API a little bit. You should be able to leverage existing broadcasts, modify lists, send text messages and so much more!

API Documentation: http://www.call-em-all.com/dev/docs.aspx

</end sales pitch> ...just kidding

Tuesday, October 18, 2011

Business Hours and Holidays!

The holidays are coming and do you know when your tasks are due?  There's an ongoing issue with Salesforce that I've inquired about throughout the years.  Although you can set business hours (even multiple profiles for hours) and holidays, these aren't taken into consideration when workflow actions are applied or due.

A few years ago, the answer to my inquiry was to research the IdeaExchange.  The idea already existed then and unfortunately is still just an idea today.

IdeaExchange Link:  http://success.salesforce.com/ideaView?c=09a30000000D9xtAAC&id=08730000000Bpq3AAC

What a shame!

Is there anything you can do?  Absolutely, but I hope you like Apex Triggers.

There is a BusinessHours Class that you can read about in the Apex Developer's Guide:  http://www.salesforce.com/us/developer/docs/apexcode/index.htm?businesshours

The first thing you want to do before you start playing around with that, though, is to set up your business hours by going to Setup --> Administration Setup --> Company Profile --> Business Hours

For my example, I'll have a default schedule of Monday through Friday, 8AM to 7PM.

I've also set up some holidays through Setup -->  Administration Setup --> Company Profile --> Holidays.  Don't forget to go back to your Business Hour schedule and add the holidays to it by clicking on the "Add/Remove" button.

Got that configured?  Now you're ready to give it a whirl.

Of course you'll want to work this into a trigger, but for demonstration purposes I've just created a test class and pulled in the system date/time (system.now()).

The VF Page:

<apex:page controller="test_business" >
    <apex:form >
        Current Date: {!oldDate}
            <br /><br />
        New (w/ GMT): {!newDateGMT}
    </apex:form>
</apex:page>

Explanation:  Just two simple getter methods from the test class.
  • oldDate is simply pulling in the current date/time using the system.now() method. 
  • newDateGMT is returning the adjusted date/time according to the line w/in that getter method

The Class:

public with sharing class test_business {
    public DateTime getoldDate(){
        DateTime oldDate = system.now();
    return oldDate;
    }

    public DateTime getnewDateGMT(){
        BusinessHours bh = [SELECT Id FROM BusinessHours WHERE IsDefault=true];
        Datetime oldDate = system.now();
        Datetime newDate = BusinessHours.addGMT(bh.id, oldDate, 3 * 60 * 60 * 1000L);
        return newDate;
    }
}

Class Explanation: 
  • getoldDate just retrieves the system.new() method as explained above.  This is the full GMT value, not adjusted for your time zone.
  • getnewDateGMT has two main parts
  1. The query is creating a BusinessHours object named bh after retrieving the one I created earlier and set as the default.
  2. Where I declare the newDate Datetime, I'm taking advantage of the addGMT method in the businesshours class.  I need to feed to it the ID of a BusinessHours object, a starting date, and my increment (in milliseconds).  Broken down, that looks like this:
    1. 3 - # of hours
    2. 60 - 60 seconds in a minute
    3. 60 - 60 minutes in an hour
    4. 1000L - 1000 milliseconds in second

Monday, October 17, 2011

Summary Report Export: Too Many Details!!!

Say you're perusing a few reports you haven't seen since the dawn of your SF Org, thanks to the new Winter '12 report folder interface, and you come across an oldie but a goodie summary report.

"Awesome," you think as you quickly click on the "Export Details" button to work some Excel magic and show off your report's findings. Wait a second, that once dusty report's Excel file equivalent looks nothing like what you saw in your browser window. What's going on here?

The "Export Details" button does exactly that... it exports the details of the report; meaning every line that makes up your report will be rendered as a new line in your .xls or .csv. This is regardless of whether or not you have clicked on the "Show Details" or "Hide Details" button. Of course, you could rework the resulting spreadsheet and do the grouping yourself, but why would you do that?

Rather than eagerly clicking on the "Export Details button," take the "Printable View" button for a test drive. It will render your report again as an .xls file, but retain the grouping as you see it on your report.

If you want to see the details, click on the "Show Details" button prior to clicking on the "Printable View" button. This will show each line item as well as the summarized headers. If you don't want details, make sure that you've clicked on the "Hide Details" button.

In case you were wondering, this also works for Matrix reports!

Wednesday, October 12, 2011

Error: Invalid API Version... Come Again?

One thing that I love about Salesforce is the sneak peak that we get in our sandboxes prior to each new release. On occasion though, I've worked on something new in one of these developer preview sandboxes and wanted to deploy my code to production. Only to receive an "Invalid API Version" error message.

Right now, my production environment is Summer '11, but my sandbox is Winter '12. If I were to create a new Visualforce page in my production environment, the Salesforce API version would be version 22.0. If I do the same within my sandbox, the API version is 23.0. The dilemma, upon deployment, is trying to push version 23.0 into a platform that only supports up to version 22.0; hence the "Invalid API Version" error message. I've done this with pages, but I imagine it is quite possible to experience with classes, triggers, templates, etc.

How do you fix this? Wait for the update to be applied to your production environment. Just kidding...
Within Salesforce, you can go to "Setup" --> "Develop" --> "Pages" --> and click on your page's name. From there click on the "Edit" button and then on the "Version Settings" tab. Drop your version down to that which matches your production instance. Click on "Save" and try your deployment again.
From within the Force.com IDE/Eclipse, expand your project and browse to the page, class, trigger, whatever file you're having issues deploying. Below that file, you should see an identically named file, appended with -meta.xml... edit that. In the editor pane, you should see an xml file structure. You may need to click on the "Source" tab below. Look for the tags and replace the version number with that which matches your production instance. Save it down and try deployment again.

Example:
<?xml version="1.0" encoding="UTF-8"?>
<ApexPage xmlns="http://soap.sforce.com/2006/04/metadata">
  

<apiVersion>23.0</apiVersion>

<description>Default Force.com Change Password page</description> <label>ChangePassword</label> </ApexPage>

While that should fix your problem, keep in mind you are reducing the API version, some it is possible that if you are utilizing some of the new release's features - your file won't save after the API version number change and those features won't work.

Speaking of version numbers, one question you might have is whether or not you need to go through all of your old code to update the API Version after a new update comes along. Once Winter '12 comes out, am I going to update all of my classes, triggers, and pages to be version 23.0? No - you don't have to and you can leave them as is as Salesforce will maintain backwards compatibility. If you want to use some nifty new tags or features that didn't exist in that version set, you might have to change it down the road - but you'll know when you have to.

Friday, October 7, 2011

Reduce Deployment Pains (pt. 2): E-mail Templates

Here we are, nearing the end of the week, and as promised, here is part 2 of the "Reduce Deployment Pains" series. See what we did there with record types? Guess what? You can use the same idea for e-mail templates too. Say you're working with a method that is responsible for sending an e-mail. You can specify the individual parts of an email, the subject and the body, or you can specify an e-mail template. You'll want to shy away from using an ID again as sometimes you end up in situations where you have an ID mismatch between production and your sandbox.

Instead, like we did for obtaining a record type ID, we'll query the template by its name and use it later within a SingleEmailMessage.

public EmailTemplate template = [select Id,Name,Subject,body from EmailTemplate where name ='Your template's name here'];

...

Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
mail.setTemplateID(template.id);
...

Thursday, October 6, 2011

Reduce Deployment Pains (pt. 1): Record Types

Let's get this ball rolling right away. One amateur mistake that continually haunts my early code is the hard coding of RecordTypeIDs. Remember, I'm new to the development game and have preached and have been preached at to not hard code anything.

Yeah, well under a tight deadline I was more than happy to throw that to the wind. Now everytime I go to deploy some old code from a sandbox, I'll get nagged with some sort of error message that the RecordTypeID doesn't exist in production.

Ah rats, busted. I created the record type outside of the sandbox or after the sandbox was created and now my record type IDs don't match up. What I would do before is just update the ID, deploy, and be on my merry way.

Wrong.

The better way to specify things like IDs will always be to query them so you have the latest and greatest, despite your environment.

Check out this little ditty:

public list<recordType> RecordTypes= new list<recordType>();
public map<string,id> RecordTypes_Map = new map<string,id>();

public YourClass(ApexPages.StandardController controller) {
    RecordTypes = [select id, name from recordtype where sObjectType = 'YourObjectAPI__c'];
    if(RecordTypes.size()>0){
        for(recordtype r:RecordTypes){
            RecordTypes_Map.put(r.name,r.id);
        }
    }
}

I'm creating a map of all the names and IDs of recordtypes for a particular object (don't forget to chance 'YourObjectAPI__c').

Then within the section of code I want to retrieve the ID, I just use:
NB_RecordTypes_Map.get('Name of Your Record Type'));

The obvious caveat here is if your record type name doesn't exist or has changed between your production and sandbox instances. Salesforce will probably puke all over itself in that event, so it probably wouldn't hurt to include some safe guard catches in your code.

Post Dreamforce Life: Crazy

So it has been a while since my last updates. If you couldn't tell or haven't caught any of the hype that is the monsoon of fun called Dreamforce; it was awesome. If you ever have a chance to go... do it! Worth every penny.

The "9-5" has more often than not been a "9-9+" after the conference due to one of the largest and fastest paced projects the company has taken on. A multi-server hosted platform moved in-house to all new hardware on a brand new network. The teams's still alive and are sorting out the system's "Post Move Blues." However, this blog's not about that the life of an admin; I just wanted to throw out my excuses for being a lazy blogger.

I've got some great Salesforce projects in the pipeline (120+ work related and about 20 non-work related to be precise). I'll be wrapping of this week with a new series on how to make life easier when it comes to deploying a project from a Sandbox to Production.

Onwards and upwards!