Sophisticated DateTime “Formula Fields” with Apex and Field-Level Security

What do you do when you want to calculate a formula-like field but a regular formula won't work?

Salesforce CRM's formulas handle dates very well. If you want to enter a date value and have formula fields display, for instance, mydate__c + 21 days, that's simple. Just use mydate__c + 21.

Side note: If you try going the long way around and use DATE( YEAR( mydate__c ), MONTH( mydate__c ), DAY( mydate__c ) + 21 ) and mydate__c = 09/17/2010, Salesforce returns #Error! because there's no date 09/38/2010. Similarly, adding three months to a date like 1/31/2010 will also give an error. More about this in a future post.

DateTime fields are like Date fields, but they include... wait for it... a time component (and can be created in the running user's local time zone or in GMT).

Here's a use-case for a DateTime formula field:

A photography studio schedules photo shoots, and different packages include different durations. Similarly, we could use a hair salon which offers different services, each with a different duration, a dentist... you get the idea.

Requirements:

  1. Enter a DateTime for an appointment start time (starttime__c)
  2. Enter a duration (though in a production system, I'd include a value on the Product2 sObject, we'll just enter a value here) (minutes__c)
  3. Display a read-only DateTime field with the end time (endtime__c)
  4. The end time must be read-only to all users, like any formula field

Here's what won't work:

  • A formula field won't work because there are no MINUTE(), HOUR(), SECOND() formula functions
  • Workflow won't work because it depends on formulas to fill new values for date/datetime fields

That leaves Apex. First, the configuration:

  1. Create DateTime field starttime__c
  2. Create DateTime field endtime__c
  3. Set endtime__c field-level security to Read-Only for all profiles
  4. Create Number (18,0) field minutes__c
  5. Create a trigger on the sobject

Here's the trigger:

trigger timeTrigger on TestObject__c (before insert, before update) {
    for (TestObject__c t : Trigger.New){
    	if(t.StartTime__c != null && t.minutes__c != null){
        datetime myDateT = t.StartTime__c;
        double d = t.minutes__c;
        Integer shootmins = d.intValue();
        if(mydateT != null && shootmins != null)
        	t.EndTime__c = myDateT.addminutes(integer.valueof(shootmins));
       	}
    }
} 

Regular readers will note that I do usually split triggers into a trigger and a class, but I've not done so here purely for the sake of brevity.

Here's the test code:

public without sharing class shootTimesTriggerTest {

    private static testMethod void ShootCalculateEndTime_PositiveTestCases() {
        TestObject__c to;
        TestObject__c l;    
        test.starttest();
        l = new TestObject__c (name = 'test');
        datetime myDateTime = datetime.newInstance(2008, 12, 1, 12, 30, 2);
        l.StartTime__c = myDateTime;
        l.minutes__c = 90;
        insert l;
        to = [SELECT id, EndTime__c FROM TestObject__c WHERE id = :l.id];
        datetime newDateTime = datetime.newInstance(2008, 12, 1, 14, 0, 2);
        system.assertequals(to.EndTime__c, newDateTime);
        l.minutes__c = 45;
        update l;        
        to = [SELECT id, EndTime__c FROM TestObject__c WHERE id = :l.id];
        newDateTime = datetime.newInstance(2008, 12, 1, 13, 15, 2);
        system.assertequals(to.EndTime__c, newDateTime);
        test.stoptest();
    }

    private static testMethod void OppCalculateEndTime_NegativeTestCases() {
        test.starttest();
        TestObject__c l = new TestObject__c (name = 'test');
        l.minutes__c = null;
        insert l;
        system.assertequals(l.EndTime__c, null);
        test.stoptest();
    } 
}

A few points about how this works:

  • Triggers run in System mode, so they don't respect field-level security. Thus, we can set a field to read-only for all profiles, and the EndTime__c field will still be updated.
  • The test code runs in System mode as well, avoiding any potential problems if the field were set to invisible to a profile and we used System.RunAs() to test for various profiles.
  • Although I'm not a fan of using SOQL queries this often, I used these in the interest of saving time. Keep in mind that if you had quite a few queries in your regular code, adding these two might put you over the limit, so use queries sparingly!
  • This is the only way I know of to add minutes to a DateTime.

Did I miss anything? Please let me know in the comments.

Filtered Lookups, Validation Rules, and Order of Execution

Reading the cheatsheet for Filtered Lookup (beta), I noticed an interesting line:

Lookup filters function similarly to validation rules when you save a record. That is, actions that cause related records to save, such as changes to a roll-up summary fields, also trigger the lookup filters on the related record and block the save.

The implications for this are massive. Let's explore two examples:

Example 1: Filter as Validation Rule from Parent Record

  • We create a lookup on a Child object to Parent.
  • We filter the lookup to EXCLUDE Parent.Status = 'Closed' (Parent.Status is only Open or Closed.)
  • We can edit the Child records as long as the Parent Status is not Closed.
  • When Parent.Status is changed to Closed, existing related Child records are not affected...
  • BUT if we attempt to edit a Child when the Parent is Closed, Force.com will throw an error (which we can customize) beause that the Lookup is invalid.
  • (and clearly we cannot add new Child records either)

Conclusion: Thus, Filtered Lookups act much like Validation Rules. A quick experiment shows that Filtered Lookup errors actually fire before Validation Rules.

Example 2: Filter as Validation Rule on Roll-Up Summary (from Child Record) - what the line above was referencing

  • Use the above example, but change the lookup to a master-detail relationship
  • Create a Roll-Up Summary field to count all child records
  • Prevent saving more than 10 child records for one parent record

Here, we have triggered a filter error without touching a parent record, yet we throw an error based on a value on the parent record.

This second example is significant because we could already prevent more than 10 child records from saving, but doing so required a Roll-Up Summary field on the parent object AND a Validation Rule on the child object. Now we can replace the Validation Rule with the Lookup Filter, though we still need the Roll-Up Summary field. Whether or not this simplifies things is definitely up for debate...

Conclusion

This is a very powerful feature! Thanks to salesforce.com for rolling it out, even in beta form.

Real world example: The above example would be great for Time Sheet Entry and Time Sheet Header objects, as they would create, in effect, a validation rule on the Header record preventing editing of any child records. Awesome!

For further reading, check Salesforce Help's Lookup Filters examples.

Preparing a New Org

September 16, 2009 · Filed Under Configuration, Native Application, Tips and Tricks, salesforce.com · 7 Comments 

With the impending arrival of the Winter 2010 (aka 162 or Winter'10) edition of Salesforce CRM, as with every other release, comes a prerelease org. (You can get one at https://www.salesforce.com/form/trial/prerelease_winter10.jsp.)

Every time one encounters a fresh org, there are maintenance tasks to perform. I usually go through an org (whether a Developer Edition org or a Prerelease version) and do the same tasks, generally in no particular order. This time, however, I wrote down what I did as I did it. Looking at the list, it's hardly in any "best practices" order at all - it's just how I did it.

There's no need to follow every step, and it is not a complete list of all possibilities, but this should give you some idea of the possibilities and available tweaks: (*** indicates some of the new features in WInter '10)

  1. Save login with 1Password/Roboform
  2. Reset (Set) Security Token
  3. Administration Setup | Security Controls
    • Session time 8 hrs
    • Passwords never expire
  4. Create Record Types (and Business Processes) for Lead, Opportunity, Case
  5. (Campaigns were not enabled in this prerelease org) - would have configured them here, similarly
  6. Activities section: Calendar link on sidebar
  7. Download latest versions of Connect for Outlook, Office Edition
  8. Opportunities:
    • Enable Similar Opportunities
    • Enable Opportunity Teams
  9. Create Account Master Record Type
  10. Enable Account Teams
  11. Create Contact Master Record Type
    • Note: Asked to add to page layout. Not asked for Opportunities.
  12. Enable Case Teams
  13. Enable Public Solutions
  14. Solutions:
    • Enable Solution Browsing
    • Enable Solution HTML
    • Could have created a Solution Process & Record Type
    • Did not enable multilingual solutions
  15. Enable Self-Service
  16. Enable Web-to-Case
  17. Create default Owner, etc (auto prompted)
  18. Enable PRM and Partner Portal (though have no licenses)
  19. Salesforce to Salesforce
    • Enabled S2S
    • Set up S2S Connection Finder ***
    • Added fields to page layout - Kept read-only for all profiles except System Administrator
    • Enable Public & Private Tags
  20. Enable Console for all Profiles
  21. Search Settings - Enable Enhanced Lookup & Auto-Complete
  22. User Interface
    • Separate loading of related lists
    • Spell Checker on Tasks & Events
    • Collapsible Sidebar
    • Custom Sidebar on all Pages
    • Enhanced Profile Management ***
  23. Set myself as default Workflow User
  24. Looked at Develop | Custom Settings ***
  25. Created a Default Queue and added myself
  26. Set all Sharing Rules to Private
  27. Update Home Page to the way I like it
    • Order of wide section (top down): Calendar, Tasks, Items to Approve, Dashboard
    • No changes to narrow section

Other things that may be possible in other orgs:

  • Enable Customer Portal
  • Customize Campaigns
  • Set up Sites

Again, this is not meant to be a complete list. Also, it is not intended to be a how-to; for more information you may search the Help link at the top of every org page, check Salesforce Community, or Developer Force.

Happy configuring!

Dashboards are Improved AND New in Summer 09

The Summer09 prerelease orgs are here, so get yours now! Upon first look, something cool stood out and merits immediate posting:

Dashboards are improved. The colors are more vivid, there’s detail in the bars and pie chart wedges, and… pie charts can now display the actual and percentage values!

Dashboards are also new. Visualforce pages can now be included as dashboard components, and there’s a new “Color-Blind Palette on Charts” setting for each user. Here are before and after shots.

Dashboard with regular color scheme

Salesforce dashboard with regular color scheme

Salesforce dashboard with color-blind/alternate color scheme

Dashboard with color-blind/alternate color scheme

Flexible Field Labels

March 23, 2009 · Filed Under Configuration, Native Application, Visualforce, salesforce.com · 2 Comments 
While making a Visualforce page to display a Zip/Country -> City, State, County application, it became obvious that labeling fields manually will not satisfy all users, especially international ones. There are a few ways to include a field and its label on a Visualforce page. Read more

Next Page »