Tuesday, September 27, 2011

MultiSelect Picklist Solution for a VisualForce Page

I had to use a VF Page for Person Accounts. On adding an InputField bound to a picklist to the VisualForce Page - I kept getting this error 'The first validation error encountered was "Record Type ID: value not valid for the entity: Account".' on loading the VF Page. I had to resort to using a picklist solution as given here

But when it comes to MultiSelect Picklists, the UI was not much great, having multiselect = "True" on the VF SelectList Component - It just served the purpose. The UI required, using the Shift + a Select with your mouse or Ctrl + a Select with your mouse. Not very User-Friendly!! :(

A multiselect component which has two list boxes - one containing the available values and the other containing the chosen values and a VF Page which uses the same for displaying the multiselect values is worth a try. A User-Friendly Solution! Just works like how any multi-select field would behave on an Edit Page! :)

The component for MultiSelect can be used on the Person Account VF Page, just like any other component.

<c:MultiselectComponent AvailableList="{!availableList}" ChosenList="{!chosenList}"/>


The below code can be used to get the picklist values

Schema.DescribeFieldResult optionFieldDescription = Account.MultiSelect__c.getDescribe();
for(Schema.PicklistEntry pleOptions : optionFieldDescription.getPicklistValues()){
availableList.add(new SelectOption(pleOptions.getvalue(),pleOptions.getLabel()));
}


When there is a need for an update to the field value:
The multi-select picklist options are saved in salesforce as a string, separated by a semi-colon. A method can be used to combine the options before saving into the database.

private static String MULTIPICKLIST_SEPERATOR = ';';
private String combineOptions(List<SelectOption> values) {
String result = '';
for(SelectOption s: values) {
result = result == '' ? s.getValue() : result + MULTIPICKLIST_SEPERATOR + s.getValue();
}
return result.length() > 0 ? result.substring(0, result.length()): result;
}


The multiSelect component

<apex:component controller="MultiSelectComponentController">
<apex:attribute name="AvailableList" type="selectOption[]" description="Available List from the Page" assignTo="{!options}" required="True"/>
<apex:attribute name="ChosenList" type="selectOption[]" description="Chosen List from the Page" assignTo="{!selectedOptions}" required="True"/>

<script type="text/javascript">
function selection() {
selection();
}
function deselection() {
deselection();
}
</script>
<!-- Apex function called to move the selected values from available list to chosen list and vice versa -->
<apex:actionFunction name="selection" action="{!selecting}" reRender="multiselect"/>
<apex:actionFunction name="deselection" action="{!deselecting}" reRender="multiselect"/>
<apex:outputPanel id="panel">
<apex:pageBlockSection columns="4" >
<apex:selectList multiselect="true" size="5" value="{!selected}">
<apex:selectOption value="{!Available}"/>
<apex:selectOptions value="{!options}" />
<apex:actionSupport event="ondblclick" action="{!selecting}" rerender="panel" status="waitingStatus" />
</apex:selectList>
<apex:pageBlockSection columns="1">
<apex:commandButton reRender="panel" id="select" action="{!selecting}" value=">" status="waitingStatus"/>
<apex:commandButton reRender="panel" id="deselect" action="{!deselecting}" value="<" status="waitingStatus"/>
</apex:pageBlockSection>
<!-- An action status to show that the operation of moving between the lists is in progress--->
<apex:actionStatus id="waitingStatus" startText="Please wait..." stopText=""/>
<apex:selectList multiselect="true" size="5" value="{!deselected}">
<apex:selectOption value="{!Chosen}"/>
<apex:selectOptions value="{!selectedOptions}" />
<apex:actionSupport event="ondblclick" action="{!deselecting}" rerender="panel" status="waitingStatus" />
</apex:selectList>
</apex:pageBlockSection>
</apex:outputPanel>
</apex:component>


Corresponding Apex functions in the MultiSelectComponentController:

public void selecting() {
for(String toSelect: selected) {
Integer i = 0;
While(i<options.size()) {
if(options[i].getvalue()==toSelect) {
selectedOptions.add(new SelectOption(toSelect,toSelect));
options.remove(i);
}
i++;
}
}
}

public void deselecting() {
for(String toDeselect: deselected) {
Integer i = 0;
While(i<selectedOptions.size()) {
if(selectedOptions[i].getvalue()==toDeselect) {
options.add(new SelectOption(toDeselect, toDeselect));
selectedOptions.remove(i);
}
i++;
}
}
}

Monday, September 19, 2011

Calculate Case Age considering Salesforce Business Hours

Requirement being Salesforce Business Hours need to be considered while calculating a Case Age, and Business hours can change anytime:
Case 1: The Default Business Hours are Mon -9 am to 5 pm, tue -9 am to 5 pm, wed -9 am to 5 pm, thu -9 am to 5 pm, fri -9 am to 5 pm.
A case opened on Monday 10 am when checked on the following tuesday 9 am should show 8 hours
Case 2: The Default Business Hours are Mon -10 am to 6 pm, tue -8 am to 4 pm, wed -9 am to 6 pm, thu -9 am to 5 pm, fri -8 am to 4 pm
A case opened on Monday 10 am when checked on the following tuesday 9 am should show 9 hours

Also, if national holidays or public holidays [which might occur on weekdays] need to be excluded while calculating the Case - A scheduled apex is the only way. The following code snippet gives a solution to Case Age considering default Salesforce Business Hours.

global class ScheduledApexOnCase implements Schedulable {

global void execute(SchedulableContext sc) {

Date today = system.today();
Decimal caseAge;
List newCaseList = new List();

BusinessHours stdBusinessHours = [select id from businesshours where isDefault = true];
newCaseList = [Select id, CreatedDate, Status, CaseAge__c from Case]; //CaseAge__c is a Text field
for(Case eachCase: newCaseList) {
if(eachCase.Status != 'Closed')
caseAge = BusinessHours.diff(stdBusinessHours.id, eachCase.CreatedDate, today) / 1000 / 3600;
else
caseAge = BusinessHours.diff(stdBusinessHours.id, eachCase.CreatedDate, eachCase.ClosedDate) / 1000 / 3600;
eachCase.CaseAge__c = String.valueOf(caseAge) + ' hours';
}
update newCaseList;
}
}


Note:
Business Hours can be modified on Admin SetUp | Company Profile | Business Hours
BusinessHours.diff(stdBusinessHours.id, eachCase.CreatedDate, today) / 1000 / 3600; // This can be further divided by 8 or 9 hours to get the number of days [considering :working hours of 8 or 9 per day]
The ScheduledApex should run once in an hour to show the case age in number of hours

Sunday, September 11, 2011

Calculate Case or Task Age in Business Hours

This requirement which popped up a few weeks back - a field which can give the case age in business hours [excluding saturdays and sundays]. There was no function in salesforce which provided me the day given the date. I came about with a round about logical solution - a formula(number with 0 digits after decimal point) field which excludes saturdays and sundays when calculating the case age.

CASE(
MOD(DATEVALUE(CreatedDate) - DATE(1900, 1, 7), 7),
0, (TODAY() - DATEVALUE(CreatedDate)) - 1 - FLOOR((TODAY() - DATEVALUE(CreatedDate))/7)*2,
1, (TODAY() - DATEVALUE(CreatedDate)) - FLOOR((TODAY() - DATEVALUE(CreatedDate))/7)*2,
2, (TODAY() - DATEVALUE(CreatedDate)) - FLOOR((TODAY() - DATEVALUE(CreatedDate))/7)*2,
3, (TODAY() - DATEVALUE(CreatedDate)) - FLOOR((TODAY() - DATEVALUE(CreatedDate))/7)*2,
4, (TODAY() - DATEVALUE(CreatedDate)) - FLOOR((TODAY() - DATEVALUE(CreatedDate))/7)*2,
5, (TODAY() - DATEVALUE(CreatedDate)) - 2 - FLOOR((TODAY() - DATEVALUE(CreatedDate))/7)*2,
6, (TODAY() - DATEVALUE(CreatedDate)) - 2 - FLOOR((TODAY() - DATEVALUE(CreatedDate))/7)*2,
null)

Exclude Saturdays and Sundays while calculating the Due Date

Requirement: Calculate Due Date from Start Date and Number Of Business Days
Due Date = Start Date + Number of Days [excluding Saturdays and Sundays]
An apex trigger to calculate the Due date on an insert or update is rather time consuming than just creating a formula field with some logic.

The below formula field calculates the Due Date [which is the new formula] provided the Start Date [StartDate__c] and Number Of Days to be added to the Start Date [NumberOfDays__c] excluding Saturdays and Sundays.

CASE(
MOD(StartDate__c - DATE(1900, 1, 7), 7),
0, (StartDate__c) + NumberOfDays__c + FLOOR((NumberOfDays__c-1)/5)*2,
1, (StartDate__c) + NumberOfDays__c + FLOOR((NumberOfDays__c)/5)*2,
2, (StartDate__c) + NumberOfDays__c + FLOOR((NumberOfDays__c+1)/5)*2,
3, (StartDate__c) + NumberOfDays__c + FLOOR((NumberOfDays__c+2)/5)*2,
4, (StartDate__c) + NumberOfDays__c + FLOOR((NumberOfDays__c+3)/5)*2,
5, (StartDate__c) + NumberOfDays__c + CEILING((NumberOfDays__c)/5)*2,
6, (StartDate__c) - IF(NumberOfDays__c>0,1,0) + NumberOfDays__c + CEILING((NumberOfDays__c)/5)*2,
null)