CloudyK Cloud Technology Talk, Tips, Tutorials.

14Oct/116

Digging in to Salesforce Person Accounts

Let me first start by saying that Person Accounts in Salesforce is not my favorite feature. I see it as a necessary evil. The point of this post is to give you an overview on how Person Accounts work and to outline a few gotchas and fixes that I have discovered that can help make the Person Accounts experience a little more tolerable.

What is a Person Account?

Good question. Person Accounts is a feature that you can request to be enabled by Salesforce support. The feature allows you to more easily engage in Business-To-Consumer (B2C) sales and service activities. It does this by removing the related Contacts list on your Account record, and making the Account record representative of an individual (i.e. Bob Smith) versus a company (i.e. ABC Company). It distinguishes itself from regular Accounts by creating a new, special type of Record Type called Person Account.

Enabling Person Accounts requires that you contact Salesforce support to activate.  There will be several steps they ask you to do before they can enable it.  You will also need to confirm that you understand what the implications of implementing Person Accounts are and that the implementation cannot be undone.  Once you install Person Accounts, its there forever.

This does not mean that you no longer have access to regular accounts.  You can actually use both at the same time.  They will just be separated by Record Types.  So worst case scenario, you implement Person Accounts, hate them, and you simply stop using them.  There will, however, be a lot of leftover database changes that you cannot undo.

Digging in a Bit Deeper

Under the hood of the Salesforce platform, a Person Accounts essentially maintains an Account and Contact record in sync with each other, and mashes them together on the Page layout for the Account.  To view a Person Account, you can view them from either the Account or Contact tab.  You will be presented with the same page layout regardless of whether you came in through the Contact or the Account.  Salesforce tries their best to abstract all of this background noise from you, but there are some painful issues that require us to really understand what's going on.

To maintain this synchronization, a series of new fields are added to the Account. For standard Contact fields, you will see new fields named like PersonMobile...which corresponds to the mobile phone field on the Contact Record.  For custom fields on the Contact record, these fields have the same API name when added to the account except for the fact that they are prepended with "__pc" instead of "__c" like you are used to seeing on your custom fields. I believe the "__pc" stands for "Person Contact." These fields map 1 for 1 with fields on the Contact record and are kept in sync so that dependencies within Salesforce for Contact records (i.e. Cases) and for Account records (i.e. Opportunity) always have the necessary information. By doing this, they are able to "mash up" a Contact and Account record into one, giving you the almighty Person Account.  In addition, a relationship between the two records is maintained.  If you look at the fields now on the Account record once you enable Person Accounts, you will see a field called PersonContactId.  This is the Id of the Contact record that corresponds to this particular Account record.  This will become very important later on.

I think the idea of the solution makes sense. I think the implementation was bad, unfortunately.

Two Important Gotcha's

Gotcha1:  Cross Object Formulas

One major issue is cross-object formulas. When using Person Accounts, if you are working from an Object that has a relationship to Account, you would think that you could access the Contact information for the Person Account within a formula.  After all, the Contact fields are copied up to the Account, right?

The answers is:  Only some are accessible.

Take the mobile phone field from Contact.  This is a standard field.  When Person Accounts is enabled, a field called PersonMobilePhone is created on the Account object.  Therefore, this should be accessible in a cross-object formula, right?  Wrong.

No Mobile Phone - Person Accounts

So thats annoying.  Part of the proposed benefit of Person Accounts is not having to deal with both and Account and a Contact record for an individual.  I have come up with a solution for this.  Lets look at the facts.

So we know several things:

  • A Person Account has both an Account and a Contact Record in the system
  • The Account has a field called PersonContactId that tells us what the Contact record Id is

The way I solved the cross-object issues was to create a lookup field on the Account Object to the Contact Object called PA_Contact__c.  Then I wrote a super simple trigger to populate that lookup field with the value in PersonContactId.  This basically just copies down that value to the lookup giving me a relationship to the corresponding Contact record that I can use in formulas to access ANY field on the Contact.  See the code below:

trigger AccountTrigger on Account (after insert, after update) {

	/*
	*  This trigger is to circumvent a cross-object
	*  formula limitation introduced by
	*  Person Accounts.  This populates a lookup
	*  to the mirrored Contact record for a
	*  Person Account so that the fields are all
	*  accessible with a cross-object formula
	*/

	Set<String> accIds = new Set<String>();

	for(Account a : Trigger.new) {

		if(a.PersonContactLookup__c == null && a.PersonContactId != null) {
			accIds.add(a.Id);
		}

	}

	List<Account> accs = [SELECT Id, PersonContactId,
		PersonContactLookup__c FROM Account WHERE Id IN :accIds];

	for(Account a : accs) {

		a.PersonContactLookup__c = a.PersonContactId;

	}

	if(!accs.isEmpty()) {
		update accs;
	}

}

Boom, now you just need to invoke this trigger.  Might I suggest using this solution?

Once all of the existing records are updated, this trigger will update all of your Person Accounts going forward.  Now you can access anything on the Contact.

Cross Object Formula After Trigger ImplementationGotcha 2: Appexchange Packages

This is kind of a biggie...

There are MANY popular Appexchange packages that DO NOT support Person Accounts.  Just know that by implementing Person Accounts, you might be eliminating a lot of great solutions that are out there.  Before deciding to implement this solution, contact as many Appexchange vendors as you can for technologies that you may be interested in to find out if they support PA's.

Also, I have been told that you cannot package code that references Person Accounts "special" fields.  I would need to verify this though.

Other Resources

VN:F [1.9.14_1148]
Rating: 8.3/10 (4 votes cast)
VN:F [1.9.14_1148]
Rating: +1 (from 3 votes)
16Sep/110

How I Created “I Integrated Salesforce to my House”

I posted this video a couple of days ago on Vimeo and Youtube and have had a lot of questions on it.  In the video, I showed an example of how you could integrate Salesforce.com to your home automation setup.  Most people want to know what equipment I used at my house.  Here is what was involved...

My Home Automation Setup

My home automation setup utilizes a technology called Insteon, which is a dual-band communication protocol for networking home automation devices like light switches, sensors, doorbells, thermostats, etc.  It is considered dual-band because the network operates over both the power lines in your house, and radio frequencies.  It's considered more reliable than other home automation technologies because of this redundancy.

My setup consists of a central controller, and a ton of devices.  The two pieces of equipment talked about in the video are as follows:

ISY 99i Central Controller

Central Controller:  ISY-99i
Manufacturer:  Universal Devices
Role:  In the demo in the video, I wrote a program that simply commands one of the plug-in light switches in my house to turn on for 10 seconds, then turn off.   The ISY-99i can then be configured to be controlled remotely over RESTful web services.  All I needed to do was to open a port on my router at home, authenticate with my admin username and password, then call this program that I created.

Insteon ApplianceLinc

Switch:  Insteon ApplianceLinc
Manufacturer:  Smarthome
Role:  The ApplianceLinc module is a plug-in module that provides on/off switching for appliances.  Some models allow for dimming, but my scenario required a hard on and a hard off.  This ApplianceLinc module is controlled by my ISY-99i controller.

ISY-99i Setup

I'm not going to go into huge detail about the internal workings of the ISY-99i because there is a lot to cover.  What I did want to show is the program that  put together.  In the screenshot below, you will see a really simple program.  The device labeled 'Bed Lamp Right' is the lamp module that I took from my bedroom and hooked up to the rotating light.  What I like about programming in the ISY is how readable and intuitive your programs are.  Non-technical people can easily see what this program is doing.

ISY-99i Salesforce Test Program

Salesforce

The salesforce portion of this was a breeze.  I simply created a Apex Trigger on Opportunities that looked at two criteria.  (1)  Was the deal over $100,000 and (2) was the deal set to "Closed/Won".  The trigger is below.

trigger OpportunityTrigger on Opportunity (after insert, after update) {

	//Dont run on bulk updates, just single updates
	if(Trigger.new.size() == 1) {

		Opportunity opp = Trigger.new.get(0);

		if(opp.StageName == 'Closed Won' && opp.Amount >= 100000) {

			//Make sure we haven already run this!
			if(Trigger.isInsert || (Trigger.isUpdate &&
					Trigger.oldMap.get(opp.Id).StageName != 'Closed Won')) {

				ISYService.notifyBigDealWon();

			}

		}

	}

}

You will notice that this calls a class called ISYService and a static method called notifyOnBigDealWon.  The ISY uses http basic authentication so I am passing in my username and password in the header.  The ISY supports SSL, but I dont have a signed certificate that Salesforce will accept so I am using http.  Not ideal, but this is just a test scenario.

I also have all of my settings like the ip/host, username, password and port stored in a Custom Settings object that I created in Salesforce.  For those that don't use custom settings, you should.  They are awesome.

Finally, I am using the @future annotation because doing an http callout from a Trigger requires that you do it asynchronously, so @future will do that for us.  I am also adding the (callout=true) parameter to specify that I will be using callouts.

That class looks like this.

public class ISYService {

	@future (callout=true)
	public static void notifyBigDealWon() {

		ISY_Settings__c settings = ISY_Settings__c.getInstance('Default');

		String address = settings.ISY_Address__c;
		String usr = settings.ISY_Username__c;
		String pwd = settings.ISY_Password__c;
		String port = String.valueOf(Integer.valueOf(settings.ISY_Port__c));

		Http http = new Http();

		HttpRequest req = new HttpRequest();

		req.setMethod('GET');
		req.setEndpoint('http://' + address + '/rest/programs/001C/runThen');

		Blob headerValue = Blob.valueOf(usr + ':' + pwd);
		String authorizationHeader = 'BASIC ' + EncodingUtil.base64Encode(headerValue);
		req.setHeader('Authorization', authorizationHeader);

		System.debug('Trying ' + req.getEndpoint());
		System.debug('Auth Header: ' + req.getHeader('Authorization'));

		HttpResponse resp = http.send(req);

		System.debug('XML RESPONSE: \n\n' + resp.getBody());

	}

}

And here is the video one more time!

VN:F [1.9.14_1148]
Rating: 10.0/10 (3 votes cast)
VN:F [1.9.14_1148]
Rating: 0 (from 0 votes)
14Jul/110

The Apex SuperMap for Force.com

I put this together a while back to get around the 1000 element limitation on Map collections in Salesforce.  Without paramaterized classes, this is the best I could come up with.  It works similar to a map, but there should be some caution taken.  You can hit script statement limits if you go nuts with this.

This is a great solution when you have a need for a map of 1000 - 5000 elements.  This is not a perfect solution by any means.

Also, since there is no such thing as parameterization in Apex, you must cast back when you call the get() method.  I also locked in String as the key.

Repo is here on GitHub.

Enjoy!

global class SuperMap {

	private List<Map<String, Object>> mapList;
	private static final Integer MAX_MAP_SIZE = 999;

	global SuperMap() {

		this.mapList = new List<Map<String, Object>>();
		mapList.add(new Map<String, Object>());

	}

	global Boolean containsKey(String key) {

		Boolean doesContainKey = false;

		for(Map<String, Object> thisMap : mapList) {
			if(thisMap.containsKey(key)) {
				doesContainKey = true;
				break;
			}
		}

		return doesContainKey;

	}

	global void put(String key, Object val) {

		for(Integer i=0; i < mapList.size(); i++) {

			Map<String, Object> currentMap = mapList.get(i);
			Integer currentMapSize = currentMap.keySet().size();

			if(currentMap.containsKey(key) ||
				(!currentMap.containsKey(key) && currentMapSize < MAX_MAP_SIZE)) {
				currentMap.put(key, val);
				break;
			} else if(i == mapList.size() - 1) {

				Map<String, Object> newMap = new Map<String, Object>();
				newMap.put(key, val);
				mapList.add(newMap);

				System.debug('Added new Map for a total of ' + mapList.size());

				break;
			}

		}

	}

	global Object get(String key) {

		for(Map<String, Object> currentMap : mapList) {
			if(currentMap.containsKey(key)) {
				return currentMap.get(key);
				break;
			}
		}

		return null;

	}

	global List<Set<String>> keySet() {

		List<Set<String>> keyset = new List<Set<String>>();

		for(Map<String, Object> currentMap : mapList) {
			keyset.add(currentMap.keyset());
		}

		return keyset;

	}

	global List<List<Object>> values() {

		List<List<Object>> values = new List<List<Object>>();

		for(Map<String, Object> currentMap : mapList) {
			values.add(currentMap.values());
		}

		return values;

	}

}

 

And some usage examples...

 

SuperMap mySuperMap = newSuperMap();

mySuperMap.put('mykey', new Lead());

Boolean test = mySuperMap.containsKey('mykey');

Lead fetchedLead = (Lead) mySuperMap.get('mykey');
VN:F [1.9.14_1148]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.14_1148]
Rating: 0 (from 0 votes)
14Jun/111

Super Easy Apex Batch to Fire Your Triggers

So I had a good conversation today with a fellow Salesforce developer regarding the different use cases for Batch Apex in Salesforce.  While there are a ton of complex uses, the following code sample is a really simple one that solves a persistent problem.

There have been countless times that I have written an Apex Trigger or developed a workflow rule that fires on a record update, and after completing it, I wanted to force it to fire on all records.  What I used to do was to export all of the records using the Apex Data Loader, then do another Data Loader Update job to invoke the trigger.  Sound familiar?  While this works, this is time intensive.

Batch Apex to the rescue!

Below is a "utility" class that I keep in my tool belt.  Its a fairly simple class that takes the Salesforce sObject name as a string in the constructor, then executes a Apex batch that updates all of the records.  It basically runs a "non-destructive" update by not changing any data.  I usually run this from the "Execute Anonymous" function within the debug log or from the same function within the Force.com IDE.  Here is the class...

global class InvokeUpdateTriggerBatch implements Database.Batchable<sObject> {

	private String sObjectName;

	global InvokeUpdateTriggerBatch(String sObjectName) {
		this.sObjectName = sObjectName;
	}

	global Database.QueryLocator start(Database.BatchableContext BC){

		String query = 'SELECT Id FROM ' + sObjectName;
		return Database.getQueryLocator(query);		

	}

	global void execute(Database.BatchableContext BC, List<sObject> scope){		

		//Just update the records.  That's all!
		update scope;

     }

	global void finish(Database.BatchableContext BC){

   		System.debug('Batch Process Complete');

	}

}

Then to run the code, open up the Debug Log and run this...

InvokeUpdateTriggerBatch batch = new InvokeUpdateTriggerBatch('Lead');
Id batchId = Database.executeBatch(batch);

Keep this class handy because it makes this process of firing triggers and workflow rules really easy.

VN:F [1.9.14_1148]
Rating: 8.7/10 (7 votes cast)
VN:F [1.9.14_1148]
Rating: +1 (from 5 votes)
20Jan/110

Clean Printable Views in Salesforce Using Conga Composer

I am a huge fan of Conga Composer.  Like many say, "it's like mail merge on steroids".  I wanted to share a super-duper easy button for creating a clean, printable view of a record in Salesforce.

I had a request for a custom button on Activities that would give a clean printable view of an activity, like an email.   Outside sales people like to print stuff like this before heading out to a meeting.  This was perfect for Conga.  Here is how it's done.

Create a new custom button.  They will be mostly printing emails so I created a new custom Task button.  To do this, go to...

Setup -> Customize -> Activities -> Task Buttons and Links

From here, create a new custom button.  Make the button and Detail Page Button and adjust the window open settings to the following

  • Width:  300  (Note:  You might want to make this 450 in the beginning while testing the returned data)
  • Height:  150  (Note:  You might want to make this 300 in the beginning for the same reasons)
  • Uncheck all checkboxes
The settings for the printable view

The settings for the printable view

Now we need to code the button.  I ususally like to us an On-Click Javascript button, but for simplicity sake, I'll create a URL button.

You'll want to become familiar with the Introduction to Pointmerge documentation for Conga Composer.  Your merge operations will consist of a URL string with many parameters.  You can tell Conga what you want merged, what data to use, how you want it merged, where you want it saved, if you want it emailed, etc.

First we need to create a template though.  There are different instructions for the different types of documents you can use for merging.  I'm not going to get into the details template creation today, besides, its pretty straightforward as the Conga documentation is pretty good.

First, you will need to actually create a button to get a sample of the data it returns.  To do that, set the URL in your new button to the following.

https://www.appextremes.com/apps/Conga/PointMerge.aspx
?SessionId={!API.Session_ID}
&ServerUrl={!API.Partner_Server_URL_80}
&Id={!Task.Id}

Now do this...

  1. Save the button
  2. Add it to your page layout
  3. Go to an existing task record.  A completed Email task is good
  4. Click your button

A popup window will appear.  You want to look for a small link that says View Data.  This is your example data that is available for merging into the document.  The column headers in this excel workbook will be the merge field names you will use.  Create your merge fields using these values.  Below is the template I created in Word to merge the data from the Task record.

An example of a simple Conga Composer merge document for a Task/Activity in Salesforce

An example of a simple Conga Composer merge document for a Task/Activity in Salesforce

Now, I'll show you the final version of the URL string I created for the custom button.  I added a bunch of parameters to add some automation to the merge.  Take a look and I'll break it down for you.

https://www.appextremes.com/apps/Conga/PointMerge.aspx
?SessionId={!API.Session_ID}
&ServerUrl={!API.Partner_Server_URL_80}
&Id={!Task.Id}
&TemplateId=a1wA00000009W6s
&DefaultPDF=1
&FP0=1
&DS0=1
&DS4=1
&DS7=3
&PS0=1
&PS1=1

The first two lines contain the Conga API endpoint where the merge operation takes place, and passes it your session Id and API URL to authenticate you.  If your license or trial is valid, you should have no issues.

The following lines break down like this:

  • ID:  This is the "Master" record that will be used in the merge operation.  The data from this record will be available for your merged document
  • TemplateId:  This is the Id of the pre-built template we will be using.  In this case, it is a word document.  We have this stored in the Conga Template manager which is free to download if you are a Conga subscriber.  You can also store your merge templates where normal mail merge documents are stored.
  • DefaultPDF=1:  This says that I want the document rendered as a PDF and makes PDF the default
  • FP0=1:  This locks the merge to PDF format only
  • DS0=1:  This disables the controls.  Normally, the user can change stuff about the merge operation when the window pops up.  I am preventing this to eliminate confusion and get the user to the merged document faster.
  • DS4=1:  This locks the template choices to the one that I set above.
  • Ds7=3:  This enables "background mode" and merges everything automatically without the user having to click through the menus.  The "3" tells the system what background mode to use.  In this case, "3" just delivers this to the browser.  I could have changes this value and had the document saved to Salesforce, or even automatically emailed out to someone.
  • PS0=1:  This enables PDF encryption.  I needed this for the next parameter.
  • PS1=1:  This allows for the PDF to be printed.

This will run the merge, generate the document, and deliver a PDF for printing right to your user....with one click!

VN:F [1.9.14_1148]
Rating: 10.0/10 (2 votes cast)
VN:F [1.9.14_1148]
Rating: +5 (from 5 votes)
24Nov/102

FinancialForce – Query Matched Cash to Sales Invoices

This week, I did some work on a customized Sales Invoice for a FinancialForce client.  Their requirements were to build a PDF version of a Sales Invoice that also showed Payments that the client had already made to that invoice.

In FinancialForce, the payments that the client made would be entered in as a Cash Entry.  Then the individual line item(s) on the cash entry would be "matched" to the corresponding Sales Invoice.  Once this matching was completed, the Outstanding Amount on the Sales Invoice would be updated to reflect the payments made.

The only difficulty here was the fact that there was no direct child relationship on the Sales Invoice to the Cash Entry line items.  This means that there is no Related List of matched cash entries.

On the invoice I was building, I needed to show individual Cash Entries so the invoice would show something like

Invoice#: SIN000024
PART QTY UNIT TOTAL
PID003 1 $1,000 $1,000
PID012 2 $2,000 $4,000
Invoice Total $5,000
PAYMENTS DATE TOTAL
CSH000023 11/10/2010 $-1,000
Outstanding Total $4,000

While this seems quite easy to do, it's actually rather complicated. The query must actually traverse MANY objects in order to generate a list of payments. To assist my brain, I drew an object diagram to help make sense of it.  The diagram is below.

FinancialForce Objects for Sales Invoice Cash Entries

FinancialForce Objects for Sales Invoice Cash Entries

So let me walk you through the steps to get your query working.

  1. Query to get the related Transaction for your Sales Invoice
  2. From the transaction, select the Transaction Line Item with the type equal to Account.  This is the line that corresponds to Accounts Receivable.
  3. The Transaction Line Item will have one or more Cash History entries.  NOTE:  This is not the list of entries you want to use.  I'll explain in a bit.

    Cash Histories

    Cash Histories

  4. Now you want to write a query against the Matching references you find in this list.  Create a set of these Matching References.
  5. Now remember, the Matching Reference ties together the Cash Matching History from the Sales Invoice Side to the Cash Matching History on the Cash Entry side.  Your next query should select all Cash Histories associated to the array of Matching References that you get in the Cash History entries from step 3, as well as the related Transaction Line Item including the Transaction Name.  Your query should also select only the negative amounts, and exclude anything that has an "Undo Match" field value.  You need to exclude the Undo Matches as this means that the Cash Entry was previously matched, but someone unmatched it.  Here is the Matching reference record  for Matching Reference 11 above...

    Matching Reference 11 - Example

    Matching Reference 11 - Example

  6. Completing this query, you will now have a list of the Cash Matching History records that correspond to matched cash lines from a Cash Entry.  What we can do now is use the Transaction Name from these records to build a Map of the Transactions.  The Transaction will have useful information including the type of the Transaction, and the Document Number of the Cash Entry (Or Credit Note too).

Code Examples

Here is the code I used.

The query from step 5.  This assumes that I have already created a set of the Matching References (matchingRefs) from Step 4:

Set<id> transIds = new Set<Id>();

List<c2g__codaCashMatchingHistory__c> cashHistLines =[SELECT
	c2g__TransactionLineItem__r.c2g__Transaction__c,
	c2g__TransactionLineItem__c, c2g__Account__r.Name,
	c2g__AccountValue__c,c2g__MatchingReference__r.Name,
	c2g__TransactionLineItem__r.Name, c2g__LineNumber__c,
	c2g__TransactionLineItem__r.c2g__LineDescription__c,
	c2g__Action__c,c2g__UndoMatchingReference__r.Name
	FROM c2g__codaCashMatchingHistory__c
	WHERE c2g__MatchingReference__r.Id IN :matchingRefs
	AND c2g__UndoMatchingReference__c = Null
	AND c2g__Action__c = 'Match'
	AND c2g__AccountValue__c < 0
	ORDER BY c2g__TransactionLineItem__c DESC];

for(c2g__codaCashHistory__c cash : cashHistLines)
	transIds.add(cash.c2g__TransactionLineItem__r.c2g__Transaction__c);
}

Now I just need to make a Map of the transactions using the Transaction Id set (transIds):

Map<Id, c2g__codaTransaction__c> transMap =
	new Map<Id, c2g__codaTransaction__c>([SELECT Id, Name,
		c2g__TransactionDate__c, c2g__TransactionType__c,
		c2g__DocumentNumber__c
		FROM c2g__codaTransaction__c WHERE Id IN :transIds]);

And now I can iterate through the Cash Matching History lines and add the Document number and other information from the Transaction, using the Transaction Map.

for (c2g__codaCashMatchingHistory__c cash : cashHistLines) {

	c2g__codaTransaction__c trans =
	     transMap.get(cash.c2g__TransactionLineItem__r.c2g__Transaction__c);

	System.debug('*********CASH ENTRY LINE**********');
	System.debug('Document No: ' + trans.c2g__DocumentNumber__c);
	System.debug('Date: ' + trans.c2g__TransactionDate__c);
	System.debug('Amount: ' + cash.c2g__AccountValue__c);
	System.debug('Description: ' +
		cash.c2g__TransactionLineItem__r.c2g__LineDescription__c);
	System.debug('************END LINE**************');

}

Though this is complex, it does work. My guess is that FinancialForce will create a solution for this as showing Matched-Cash-to-Invoice is a common request and the FinancialForce development team has done a great job of responding to end-user requests.

And as always, if you have a better solution, please let me know!

VN:F [1.9.14_1148]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.14_1148]
Rating: +1 (from 1 vote)
13Nov/100

Welcome Morgan

As some of you know, on 11/11 I became a new Dad.  I couldn't be happier.  It was an awesome experience and my new daughter, Morgan, is absolutely perfect.  Thanks to everyone for the support through the whole process.  My family, friends, co-workers, and colleagues have been very helpful.

Here is my favorite pic so far....

Morgan

Morgan - Born 11/11/2010

VN:F [1.9.14_1148]
Rating: 10.0/10 (2 votes cast)
VN:F [1.9.14_1148]
Rating: 0 (from 0 votes)
Filed under: My Life No Comments
4Nov/100

The final days…

At this point there is no turning back.  In just a few days from now, I will be a new father.  The feelings of excitement, anxiousness, and overwhelming fear make for interesting combination.  I have prepared as much as I can...I think.

I was sent this video and told to study it carefully as it will prepare me for what's ahead.  I am willing to take any further advice.

VN:F [1.9.14_1148]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.14_1148]
Rating: 0 (from 0 votes)
26Oct/102

Private Cloud Analogy…with Pretty Pictures!

The other day, I was tasked with explaining several key cloud concepts to a group of executives.  In typical fashion, I started out way too technical.  Sometimes I have a hard time explaining technology concepts to people who don't write code for a living.  I am able to recognize this now, just not able to correct it most of the time.

The question was to explain what a Private Cloud is.  Easy, right?

So I began by explaining the recent evolution in datacenter virtualization and advancements in hypervisor technology, network, and server architecture.  After a quick glance around the room, those that weren't nodding off were checking email or playing solitare on their Blackberries.  I've done it again.  Now everybody is bored and I am nerd.  What now?

In a moment of inspiration, I started drawing pretty pictures and created an analogy that everyone could understand.  I thought I would share it with you in case you find yourself in the same situation as me, or you are a non-technical person who wants to understand what "Private Cloud Computing" means for your business.  I didn't create a fictitious company like I did in this example, but thought it might be fun to do so for this post.

NOTE:  While I am openly public about my dislike for the term "Private Cloud Computing", there is no denying that it has become an industry term.  I am under the belief that the "Private Cloud" is merely the next evolution in server virtualization, and that the only true "clouds" are in the public realm.  Regardless of whether I like the term or not, it still needs to be explained so that the benefits and drawbacks can be understood.  This example highlights the benefits.

ABC Product Company (highly creative, I know)

ABC Product Company had a somewhat typical product manufacturing business.  Their Manufacturing division creates a wide array of consumer products while their Shipping division maintains a large fleet of trucks to carry loads of these products out to clients.  Their model was simple, when Manufacturing had new goods to ship out, they put in the request to the Shipping department to buy a new truck.  Each type of product required it's own type of truck.  The process worked well for a while.

ABC Product Company - Figure 1

ABC Product Company - Figure 1

Over time though, managing the fleet of trucks became a huge headache.  They were constantly adding trucks whenever they introduced a new product, or increased the amount of products that they were shipping.  If they needed to double the shipments, they doubled the number of trucks.  The maintenance crew was overburdened with repairs to trucks.  There were so many of them, it was hard to keep track of.

And to complicate things further, each truck was specially built for a particular product.  If a particular product stopped selling, that truck would sit idle.  ABC would love to reuse that truck for other products that were increasing in shipping volume, but instead, they would have to buy another truck.  They knew that they were wasting money.

It also took a long time to order a truck.  After all, it had to be specially made for the type of product it was carrying.  The time it took to get a new truck just to increase shipments of a product, or to start shipping a new product was costing ABC a lot of money.  They were missing out on big business opportunities.

The manufacturing side of ABC was growing increasingly frustrated with the shipping team.  Shipping started hearing comments like "Shipping takes FOREVER to get me a truck for my new products" or "we have a new product, why do we not have trucks ready for it?"

To measure this, ABC began tracking the utilization of the trucks to see where they were.

ABC Product Company - Figure 2

ABC Product Company - Figure 2

Senior management knew they had to do something.  They decided to overhaul their shipping processes.  They made a list of goals that they wanted to achieve:

  1. Reduce their spending on maintenance, trucks, and other overhead by increasing efficiencies in their shipping fleet.
  2. Make it easier and faster for the business to introduce new products
  3. Make it easier to quickly increase, or scale-up the volume of products that they are shipping.
  4. Leverage the investment they had already made in their trucks

Luck for ABC, a new concept for shipping had recently been conceptualized in the industry...Cloud Shipping.

Cloud Shipping was really taking the logistics industry by storm.  Cloud Shipping allowed companies to pool their truck resources creating one big shipping service to be shared across the entire business.  The concept was simple.

Advancements in truck technology allowed companies to combine trucks to make one big truck for everyone in the business to use.  Because all of the maintenance crew were now focused only on one truck, they were able to build a really fast truck that was incredibly well built.  The best part was the fact that they could re-use their old trucks to create the big truck.  Shipping departments could now manage their shipping centrally, further reducing their costs.  This helped keep the Cloud Shipping costs down.

Eventually, the ABC would begin to outgrow this big truck as new product shipping requirements popped up.  This was no longer a headache for the Shipping department.  All they needed to do was to add more truck parts to the big truck.  This allowed their shipping service to continue to scale without disruption to Manufacturing.  Manufacturing could simply place their products on the big truck and they were shipping in no time.  They weren't even aware that Shipping had made any changes.

When Manufacturing needed to stop shipping a product, this was no problem either.  In the past, this would have caused a truck to sit idle.  Not anymore.  Since they were using Cloud Shipping, the portion of the shipping services that were no longer needed would be made available for other product shipments, automatically!

ABC Product Company - Figure 3

ABC Product Company - Figure 3

Shipping began focusing on providing the best possible shipping service to Manufacturing rather than focusing on repairing and buying new trucks.  The shipping service was always available and Manufacturing never had to wait for new trucks to be added to begin shipping a new product, or more of an existing product.  In fact, they no longer had to even talk about trucks.  They knew that the Shipping department would make sure that there were shipping services readily available for their products whenever they needed them.  After all, Manufacturing doesn't really care about trucks, they just care about shipping services being available.

Applying this to Private Cloud Computing

So here is a really simple way to convert this story into a Private Cloud Computing example.

Replace these terms and concepts.

  • Trucks = Servers
  • Products = Applications and Workloads
  • Shipping Department = Information Technology
  • Manufacturing Department = Any Line of Business (IT Consumers)
  • Cloud Shipping = Private Cloud Compting
VN:F [1.9.14_1148]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.14_1148]
Rating: 0 (from 0 votes)
11Oct/102

Handling Holidays in Salesforce

For anyone trying to create workflows and custom code that is aware when a date value falls on a holiday knows the pain I am currently feeling.

Here is an example.  We have built an object that has a due date field that is set by the user, but this date cannot fall on a weekend or a holiday.  Seems pretty simple, right.  And I also remember seeing the ability to populate Salesforce with our holiday information.  Hmmm...I should be able to use this.

Within Salesforce, holiday setup can be found under company information.  It's a pretty robust setup, with the ability to set recurring holidays with all sorts of parameters.

Setup -> Company Profile -> Holidays

Salesforce.com Holiday Setup Screen

Salesforce.com Holiday Setup Screen

So you go through the process.  You create all of your holidays, even the recurring ones.  Everything is set up just perfect.  Now its time to write some validation rules or some checks in an Apex trigger.  The idea is to access this holiday data and figure out if a date value falls on one of these holidays.

To your dismay, you will not find any date methods to use in Apex.  You will also not find any useful functions to use in your validation rules.  Bummer!  It seems so easy to add someting in Apex like this:

Date d = System.today();

if(d.isHoliday() == TRUE) {
     //It's a holiday!
} else {
     //It's not a holiday!
}

Well, I wish there were a simple method to call in Apex, but there is not.  Instead, we have attacked this like most other companies have.  We built a separate custom object called Holiday__c and populated that with all of our calendar holidays for 2010.  Then in our Apex trigger, we SOQL that Holiday object and if the results are null, we know its not a holiday.  If the results are true, it's a holiday and we take action.

I am curious to see if there are some other creative ways Salesforce gurus have found to better handle holidays?  I am thinking about writing a solution that populates my custom Holiday__c object automatically by checking the Holiday object in Salesforce, and generating the dates by doing the following:

  1. Grab all of the variable holidays (i.e. holidays that do not recur on a certain date every year and are entered manually)
  2. Calculating all of holidays that recur on a certain date  (i.e.  Christmas falls on the 25th of each year.  This is tracked in the Holiday section.
  3. Calculating all holidays that recur based on other paramaters.  (i.e. Thanksgiving is one.)

This seems annoying though and even if I do it, the only problem I am solving is generating a static list of holidays that I can use to check against.  I still have to write code that queries this object and then process the results.

The other option would be to use the Holiday functionality in Salesforce and dynamically calculate the holidays on-demand.  Calculating all of these dates on the fly within Apex seems too resource intensive and will probably hit governor limits, or severely limit the amount of actual work I can do in my trigger.

If you have any other solutions, let me know and I'll post it up.

Let's get this resolved.  Help out by voting for this idea.

https://sites.secure.force.com/ideaexchange/ideaView?c=09a30000000D9xt&id=08730000000JRxN

VN:F [1.9.14_1148]
Rating: 7.0/10 (1 vote cast)
VN:F [1.9.14_1148]
Rating: 0 (from 0 votes)