Tag Archives: Salesforce

Access the User’s Time Zone in a Formula Field

The solution takes advantage of a confusing inconsistency between two out of the box Salesforce functions, namely DATEVALUE and DATETIMEVALUE.

The first evaluates under the user’s timezone, while the latter evaluates as GMT. We can take advantage of this inconsistency and derive the user’s timezone as follows. Paste below code into a new numeric formula field named “UserTimezoneOffset”:

(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 00:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 01:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 02:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 03:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 04:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 05:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 06:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 07:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 08:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 09:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 10:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 11:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 12:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 13:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 14:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 15:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 16:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 17:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 18:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 19:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 20:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 21:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 22:00:00")) - TODAY()) + 
(DATEVALUE(DATETIMEVALUE(TEXT(TODAY())+" 23:00:00")) - TODAY())

This formula works because for every hour offset from GMT, the answer for “what day is today?” differs by one. In other words, the DATEVALUE of any literal hour of the day interpreted as GMT will be a day off from the user’s TODAY() based on their timezone setting on their User record. Adding together each of these discrepancies hour by hour ends up yielding the same answer as their timezone offset.

Formula Field to Find Time Zone From State Field in Salesforce

IF(CASE(BillingState, 'CA', 1, 'NV', 1,'OR', 1, 'WA', 1, 0) >=1, "Pacific Standard Time", null)+ 
IF(CASE(BillingState, 'AZ', 1, 'CO', 1,'ID', 1, 'MT', 1, 'NM', 1, 'UT', 1, 'WY', 1, 0) >= 1, "Mountain Standard Time", null)+ 
IF(CASE(BillingState, 'AL', 1, 'AR', 1, 'IL', 1, 'IA', 1,'KS', 1, 'LA', 1,'MN', 1,'MS', 1,'MO', 1,'NE', 1,'ND', 1, 'OK', 1,'SD', 1,'WI', 1, 0) >= 1, "Central Standard Time", null)+ 
IF(CASE(BillingState, 'CT', 1, 'DE', 1, 'GA', 1, 'ME', 1, 'MD', 1, 'MA', 1,'MI', 1, 'NH', 1, 'NJ', 1, 'NY', 1, 'NC', 1, 'OH', 1, 'PA', 1, 'RI', 1, 'SC', 1, 'VT', 1, 'VA', 1, 'WV', 1, 0) >= 1,"Eastern Standard Time", null)+ 
IF(CASE(BillingState, 'AK', 1, 0) >=1, "Alaskan Standard Time", null)+ 
IF(CASE(BillingState, 'HI', 1, 0) >=1, "Hawaiian Standard Time", null)+ 
IF(BillingState = 'FL', IF(MID(Phone,2,3) = "850","Central Standard Time","Eastern Standard Time"),null)+ 
IF(BillingState = 'IN', IF(MID(Phone,2,3) = "219","Central Standard Time","Eastern Standard Time"),null)+ 
IF(BillingState = 'KY', IF(MID(Phone,2,3) = "270","Central Standard Time","Eastern Standard Time"),null)+ 
IF(BillingState = 'TX', IF(MID(Phone,2,3) = "915","Mountain Standard Time","Central Standard Time"),null)+ 
IF(BillingState = 'TN', IF(CASE(MID(Phone,2,3),"865",1,"423",1,0)>=1,"Eastern Standard Time", "Central Standard Time"),null)

How to write batch apex class in Salesforce?

What is Batch Apex Class:

The class that implements Database.Batchable interface can be executed as a Batch Apex Class. Batch jobs can be programmatically invoked at runtime using Apex.

Batch Apex Governor Limits:

  • All methods in the class must be defined as global or public.
  • Up to five queued or active batch jobs are allowed for Apex.
  • A user can have up to 50 query cursors open at a time. For example, if 50 cursors are open and a client application still logged in as the same user attempts to open a new one, the oldest of the 50 cursors is released. Note that this limit is different for the batch Apexstart method, which can have up to 15 query cursors open at a time per user. The other batch Apex methods have the higher limit of 50 cursors.
  • Cursor limits for different Force.com features are tracked separately. For example, you can have 50 Apex query cursors, 50 batch cursors, and 50 Visualforce cursors open at the same time.
  • A maximum of 50 million records can be returned in the Database.QueryLocator object. If more than 50 million records are returned, the batch job is immediately terminated and marked as Failed.
  • If the start method returns a QueryLocator, the optional scope parameter of Database.executeBatch can have a maximum value of 2,000. If set to a higher value, Salesforce chunks the records returned by the QueryLocator into smaller batches of up to 2,000 records. If the start method returns an iterable, the scope parameter value has no upper limit; however, if you use a very high number, you may run into other limits.
  • If no size is specified with the optional scope parameter of Database.executeBatch, Salesforce chunks the records returned by the start method into batches of 200, and then passes each batch to the execute method.Apex governor limits are reset for each execution of execute.
  • The start, execute, and finish methods can implement up to 10 callouts each.
  • Batch executions are limited to 10 callouts per method execution.
  • The maximum number of batch executions is 250,000 per 24 hours.
  • Only one batch Apex job’s start method can run at a time in an organization. Batch jobs that haven’t started yet remain in the queue until they’re started. Note that this limit doesn’t cause any batch job to fail and execute methods of batch Apex jobs still run in parallel if more than one job is running.

Why we use Batch Class:

We use batch Apex to build complex DML operations for bulk records or long-running processes that run on thousands of records on the Force.com platform.
The Database.Batchable interface contains three methods that must be implemented.

start method:

global Database.QueryLocater start(Database.BatchableContext bc) {} 
  • The start method is called at the beginning of a batch Apex job.
  • This method is Used to collect the records or objects to pass to the interface method execute.
  • This method returns either a Database.QueryLocator object or an Iterable that contains the records or objects passed into the job.
  • Query executed in the start method will return maximum 5,00,00000 records in a transaction.

execute method:

global void execute(Database.BatchableContext bc , List scope) {} 
  • The execute method is called for each batch of records passed to the method.
  • This method is used for do all the processing for data.
  • This method takes the following:
    • A reference of Database.BatchableContext object.
    • A list of sObject records.
  • Batches of records are not guaranteed to execute in the order they are received from the start method.

finish method:

global void finish(Database.BatchableContext bc) {}
  • The finish method is called after all batches are processed.
  • This method is used to send confirmation emails or execute post-processing operations.
  • This method takes only one argument a reference of Database.BatchableContext object.
  • This method is called when all the batches are processed.

Each execution of a batch Apex job is considered a discrete transaction. For example, a batch Apex job that contains 1,000 records and is executed without the optional scope parameter from Database.executeBatch is considered five transactions of 200 records each. The Apex governor limits are reset for each transaction. If the first transaction succeeds but the second fails, the database updates made in the first transaction are not rolled back.

Here is an example of batch class.

Sample Code:

global class batchAccountUpdate implements Database.Batchable<sobject> {
 
    global Database.QueryLocator start(Database.BatchableContext bc){
     
        String query = 'SELECT Id, Name FROM Account';
        return Database.getQueryLocator(query);
    }
     
    global void execute(Database.BatchableContext bc, List<account> scope) {
     
        for(Account a : scope) {
            a.Name = a.Name + 'Updated';
        }
        update scope;
    } 
     
    global void finish(Database.BatchableContext bc) {
     
    }
}

If you want to notify admin on successful run, then in that case, create an email using this code below and send email by adding this code on finish method.

global void finish(Database.BatchableContext bc){
	Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
	mail.setToAddresses(new String[] {email});
	mail.setReplyTo('info@biswajeetsamal.com');
	mail.setSenderDisplayName('Account Batch Processing');
	mail.setSubject('Account Batch Process Completed');
	mail.setPlainTextBody('Account Batch Process has completed');
	Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}

Call the Batch Class:

batchAccountUpdate batchAcc = new batchAccountUpdate();
database.executebatch(batchAcc, 10);

Note: If batch size is not mentioned the default batch size is 200 and the maximum batch size is 2000.

Restrict multiple approval for a single user to a single record in Salesforce

I had a requirement to restrict multiple approval for a single user to a single record. There were multiple steps of approval process and the approver may be in different queue or as a manager for a user.

So, I implemented it using a trigger. In this article I’ll demonstrate how to implement it.
Here Application__c is the object, on which 3 steps approval process has implemented.
A custom check box field Check_Approver__c has set value to true, in all 3 steps field update action.

In below trigger I filtered the ProcessInstanceStep object records with current user Id in ActorId and the object Id (here object Id means record Id) in ProcessInstance.TargetObjectId.

  • If an User will approve or reject a record, then the SOQL query will return a single record.
  • If the SOQL query return a record, then trigger will throw an error message.

Trigger source code:

trigger ApplicationTrigger on Application__c (before Update)
{
    if(trigger.isUpdate){

        Id currentUserId = UserInfo.getUserId();
        for(Application__c sf: trigger.new){   
                  
            if(sf.Check_Approver__c == true){
              
                List<processinstancestep> existingApprovals = [SELECT ActorId
                                    FROM ProcessInstanceStep WHERE ProcessInstance.TargetObjectId = :sf.Id
                                    AND (StepStatus = 'Approved' OR StepStatus = 'Rejected')
                                    AND ActorId = :currentUserId];
                                      
                if(existingApprovals != null){
                  
                    if(existingApprovals.size() > 0){

                        sf.addError('You have already approved or rejeted the record.');
                    }
                }                  
             }
         }                         
    }
}