Salesforce Formula.recalculateFormulas Usage

Many Salesforce developers are not familiar with the method Formula.recalculateFormulas and possible was never needed to use it. Actually I'm developing in the Salesforce platform for years and was never needed it until recently.

The idea behind the method is that it recalculate all formula fields on the record for you without the need from you to query or changing the database.


Formula might be very complex, but to demonstrate the usage I created simple checkbox formula on account:

Consider the following code:

Account demoAcc = [	SELECT Id,Is_Account_Valid__c,Website FROM Account WHERE Website= null LIMIT 1];

//Website is empty, therefore the formula will be false
system.assertEquals(false, demoAcc.Is_Account_Valid__c); demoAcc.Website = 'www.testsite.com'; //assertion will fail, although website is now not empty. system.assertEquals(true, demoAcc.Is_Account_Valid__c);


The last assertion is failing, although the Website is populated which should change the formula to true. The formula is still false because this is the value when I query the record.

It will work if I will invoke Formula.recalculateFormulas after setting the site

Account demoAcc = [	SELECT Id,Is_Account_Valid__c,Website FROM Account WHERE Website= null LIMIT 1];

//Website is empty, therefore the formula will be false
system.assertEquals(false, demoAcc.Is_Account_Valid__c); demoAcc.Website = 'www.testsite.com'; Formula.recalculateFormulas(new List<Account>{demoAcc}); //assertion is fine as recalculateFormulas changed the formula system.assertEquals(true, demoAcc.Is_Account_Valid__c);


Important note that should take under consideration - the recalculate clear all reference fields.

Consider this code:

Account demoAcc = [	SELECT Id,Is_Account_Valid__c,Website,Owner.Name FROM Account WHERE Website= null LIMIT 1];

System.debug(demoAcc.Owner.Name);	//output: Liron Cohen

Formula.recalculateFormulas(new List<Account>{demoAcc});
System.debug(demoAcc.Owner.Name);	//output: null


The Owner.Name is null after the method invocation. This will happen for any field from reference and might be issue if you have logic based on that data.

To solve the issue, I can store the reference data and use it after the recalculate. 

For example:

Account demoAcc = [	SELECT Id,Is_Account_Valid__c,Website,Owner.Name FROM Account WHERE Website= null LIMIT 1];

User accOwner = demoAcc.Owner;

System.debug(demoAcc.Owner.Name);	//output: Liron Cohen

Formula.recalculateFormulas(new List<Account>{demoAcc});

demoAcc.Owner = accOwner;
System.debug(demoAcc.Owner.Name);	//output: Liron Cohen


Another important thing to note, is that the method actually does re-query the data, therefore it is counted as additional Soql query in the transaction limit and the records that processed are counted in the query rows limit.

If I will check the debug log from the last piece of code, I can see 2 Soql and 2 query rows. First for my query and second from the method recalculateFormulas.



Approval Process Reminders (Without Code)

I published recently open source code for setting approval process reminder alert. The code uses free app - Asynchronous Process Manager / Creator - to schedule its processing. In this post I will show alternative option for setting such alerts, without custom code, but only usage of the app.

Keep in mind:

1.The solution require few setup actions per each approval process

2.It provide less capabilities than the solution in the open source code, but should be enough for most use cases


What setup is needed?

Per each approval that we want to use will need:

  1. Date/time field that indicate how long record is pending for approval
  2. Checkbox formula that indicate if reminder should be send for the record
  3. In the approval process fields update that set/clear the date/time field


For the demo, I added approval process on opportunity therefore I created in opportunity:

-Custom date/time field: Approval Start Time

-Formula checkbox: Approval Send Reminder

        The first part calculate the time passed since the Approval Start Time (in hours) and if it is greater than 48 then the formula will be evaluated to true


In my approval process I set 2 approval steps and use 2 fields update:

    • Set Approval Start Time to Now
    • Clear Approval Start Time


What is next?

Use the app to configure a process for sending the alerts.

  • Before starting the next steps: go to Setup->Custom Metadata Types. Click Manage next to Module Standard Object Option, click edit next to record Async Template. In the Available Standard Objects add at the end 'ProcessInstance;ProcessInstanceStep;' and click Save. This settings will allow us to access those objects types in the next steps.


1.Go to tab Async Job Template and click New

2.Provide Name, set status to Live and click Save



3.Click the button Set Actions. This process will have 5 steps, so we can click Add Action button*5 and provide the action names + type


Now lets fill the detail for each step:

Step1- find opportunities record based on the new checkbox field. We should retrieve all
opportunities were Approval Send Reminder is true.
Click button Set Action Params next to the first step, select Related Object as Opportunity,
click + icon to add filter and compare the Approval Send Reminder equal to true (checked)
Click button Close

Step 2 - search Process Instance related to the opportunities.
Click button Set Action Params next to the second step. Select the Related Object as Process
Instance, add filter, select the field Target Object Id equal, click the green filter icon,
choose filter by Find Opportunities for Alert and select the Opportunity Id


Step 3 - select the Related Object as Process Instance Step, add filter Process
Instance Id equal, click the green filter icon , select Filter By Find Related Process
Instance and select the field Process Instance Id

Step 4 - we will create Log Message records in order to send an alerts, which is part of
the package functionalities.
Fill the input
Data Source: Action
Action Source: Find Related Process Instance Steps
Object to Insert: Log Message

In the Field Setup section, select the following fields and set their values as follow:



Last step simply update the opportunity Approval Start Time to now, in order to reset the
counter.

Click Save button above the action list.


Now lets schedule the process.
Click button Create Async Job
Potentially- provide email to get summary email when the process complete
Set Time to Run
Check the Is Repeated Option
Repeated Type: Minutes
Repeated Interval: [Minutes per your needs]

Click Next



Video for the configuration of the Async Job Template:



Tip. If you would like to use email template for the alert, just modify the forth step in the
Async Job Template. Instead of setting the fields Subject and Message Email set the following
fields (Email Template Id should be your email template Id):








Using Custom Milestone Process in Salesforce


I formerly worked for a company that had a complicated, multi-steps approval process. One of the issues was that the process would frequently come to a halt at some point, and the approver would postpone their approval for days, if not weeks. The causes for the delay can vary, but the solution we implemented was pretty straightforward: we establish deadlines for each stage of the approval process, warn the approver several hours beforehand with email reminder, and if he does not approve, send him and his direct manager another reminder.

Amazingly, the alerts has dramatically reduced the approval times.

It appears that we frequently need due dates and phases in our process (breaking a complex task into steps, sounds familiar?), and occasionally we need enforcement actions to remind us of our tasks. The milestone concept and their functionalities include all of this.


The application Customized Timeline Process was designed exactly for that kind of tasks. Building processes per the organization need with few button clicks.

The application support setting milestone steps for any data type in the system (standard/custom) as well as automation actions for enforcing the times. In addition, the app provides reports to analyze the results and identify where there is room for improved performance.

As an example lets look at a milestone process on the Account.

I set first step as: Set Required fields, which as the name indicate it require to fill some essentials data on the account.
Per the configuration the user should fill the fields Rating, Industry and Website in order to complete this step and it should be done within 1 day.



In addition, I set an alert 
action to be sent to the account manager 1 hour before the due date.


Continue with step 2: Create First Opportunity 
(expected to be done within 3 days)

And later Step 3 and 4: Schedule a Demo Meeting and Close the Opportunity, will eventually the result with the following configuration:


After activating the process, any time new account will be created the application will automatically generate its related steps, automatically complete each step and execute the relate actions, as can be seen in the short video







Salesforce Time Logger



I saw similar question is some cases - we want to allow the user to report working hours directly in Salesforce.

Each implementation usually has unique specification, so I will share here the basic concept that can be used as basic and then customized/extended.


General idea is a custom component where user can report their hours + and optional component for manager to assign users with total hours they allowed to report. 

Therefore, it can be use in either way:

  • Placing Time Log component in record page and setting the property 'use assignment' to false,  allowing users to report hours on the record without limitation.
  • Placing Time Log component in the record page + Time Log Assignment (expose it for relevant users) and set 'use assignment' to true. This way, users can report hours only if they were assigned to.


Notice, we can add in the object Time Log lookup field for the object you want to links the reported hours and this will automatically populated. This way can use the standard components to display logs related list.

in the record page, without the lookup field only the the field Related_Record_Id will be set which is just an text field.


Rollups?

Common request with such functionality is to have rollup from the time logs to the parent object. For such request will need to set the lookup field in Time Log as suggest above.

Might consider the following points:

-In case it will be used only for 1 object, can simply set the relation to the object as master/detail and use build-in rollup summary fields.

-In case it will be used for few objects and there are several rollups needed might replicate the Time Log object per each case (Time Log Opportunity, Time Log Case...) and still use the mater/detail relation and the rollup fields.

-In other cases, were we ending with the lookup relation field, will need to handle the rollup calculations with customization.


Full code can be found in git:

https://github.com/liron50/shared-code/tree/main/timelog

Running Test Classes From Apex Code


Salesforce provides a few objects for working with test classes directly from apex code.

It provides the ability to schedule test runs with a list of test classes and the ability to query the test result. Seems the main part which is not supported is the code coverage. The result contains only information regarding success/failure and errors in case of failure. For the code coverage, we will need to use another API (for example, tooling API). 

Basically, to set up a test run, all we need to do is to insert ApexTestQueueItem records. Each record indicate a test class that should be running. Creating several items in the same transaction will link them all under the same test.


In the following demo, I tried to keep it simple. Therefore, I'm using native apex without an API. I'm using a web components that allow me to select a list of test classes, set specific intervals and schedule it to run at a specific time.

Clicking the Run Test button set up a job to run which first creates the ApexTestQueueItem items and then set a second job to check the result in 5 minutes, as the tests run asynchrounsly. 

When the job running, it first checks if all the related ApexTestQueueItem items were completed. If it does, then it queries the result and reports any failure that was found during the test. If not all tests are completed, it set job to check it again in 5 minutes. 

  • 5 minutes might be too low value if I'm running all tests, but for the demo purpose it is fine.


Another issue that you need to consider: the code using the standard object ApexClass to get a list of classes, but it cannot indicate if a specific class is a test class. In the code, I'm getting the class content and check if it contains the text @istest, but it is not 100% correct.


Also note that for the solution I'm a free app: Asynchronous Process Manager / Creator, which allow to easily set and monitor background jobs and it provides some other core components that are used and saves some efforts.


Can view the full code in git


How to Use Namespace in Web Component

Using namesapce in your org, which is must if you developing managed package, can cause some issues. Especially, when using objects/fields with their API names inside lightning web component (similar issues exist also in Visualforce pages...).


Consider the following example: I have org with namespace appsolu_utils and custom object Log_Message__c, therefore the object full name is: appsolu_utils__Log_Message__c

The object have custom fields: 

appsolu_utils__Subject__c 

appsolu_utils__Log_Type__c 


Now, writing simple apex code that retrieve the object records and return it as list:

public with sharing class ViewLogMessageController {    
    @AuraEnabled
    public static List<Log_Message__c> getAllLogs(){
        
        
        return [    SELECT Id, Subject__c,Log_Type__c
                    FROM Log_Message__c];
    }
}


As you can see we don't have to use the namespace in apex code (although we can)


Using this function in web component, and using the object fields in web component html file:

import { LightningElement, track } from 'lwc';
import getAllLogs from '@salesforce/apex/ViewLogMessageController.getAllLogs';

export default class ViewLogMessages extends LightningElement {

    @track logMessages;

    connectedCallback(){
        if(! this.logMessages){
            getAllLogs().then(
                result => {
                    console.log('result:: ' + JSON.stringify(result));
                    this.logMessages = result;
                }
            );
        }
    }
}

<template>
    <template if:true={logMessages}>
        <table>
            <thead>
                <tr>
                    <th>Type</th>
                    <th>Subject</th>
                </tr>
            </thead>
            <tbody>
                <template for:each={logMessages} for:item="msg">
                    <tr key={msg.Id}>
                        <td>{msg.Log_Type__c}</td>
                        <td>{msg.Subject__c}</td>
                    </tr>
                </template>
            </tbody>
        </table>
    </template>
</template>

However if I will load the component it will show table with empty values, and by looking at console log the issue is clear- the object/fields does have namespace, therefore the fields with values are: appsolu_utils__Subject__c,  appsolu_utils__Log_Type__c




In fact, if you will debug the server code you will notice that the namespace exists also there, only in the server Salesforce does some "magic" so you don't have to write it. The "magic" unfortunately doesn't happen also in the javascript/html code.


So how can we solve it?

Of course, it can be fixed by using the object + fields with there namespace in the web component code, but this will probably hit you back later, if you will create new dev org without namespace or different namespace and you will have to change all the places were the namespace is hardcode. Therefore, hardcoding the namespace should be the last option.


Other solutions?

1.Using wrapper class instead of the object/fields API names

Very common solution is to use wrapper class. I can modify the controller code to:

public with sharing class ViewLogMessageController {    

    @AuraEnabled
    public static List<LogMessage> getAllLogs(){
        List<LogMessage> logList = new List<LogMessage>();

        for(Log_Message__c log : [  SELECT Id, Subject__c,Log_Type__c
                                    FROM Log_Message__c]){
            logList.add(new LogMessage(log));
        }
        
        return logList;
    }

    public class LogMessage{
        @AuraEnabled
        public String Subject;
        @AuraEnabled
        public String Log_Type;

        public LogMessage(Log_Message__c log){
            Subject = log.Subject__c;
            Log_Type = log.Log_Type__c;
        }
    }
}


In the html file, just change the iteration reference to:

<template for:each={logMessages} for:item="msg">
    <tr key={msg.Id}>
        <td>{msg.Log_Type}</td>
        <td>{msg.Subject}</td>
    </tr>
</template>


And it will work. 

Disadvantages of this approach:

-Might be lots of code, as we will need such wrapper for each custom object

-Require maintenance when adding/modifying fields.


2.Stripe the namespace in the server

We can think of each record as a map of field=>value (which is actually the case), and convert the SObject record to a real Map. Only the key that will be used for the map, will be without namespace.

See the updated code, where I'm removing the namespace in the server with a code that can be reused for any object type:

public with sharing class ViewLogMessageController {    
    @AuraEnabled
    public static List<Map<String, object>> getAllLogs(){
        
        ApexClass cls = [SELECT NamespacePrefix FROM ApexClass WHERE Name = 'ViewLogMessageController'];
        String namespace = String.isBlank(cls.NamespacePrefix) ? '' : cls.NamespacePrefix + '__';

        List<Log_Message__c> logList = [    SELECT Id, Subject__c,Log_Type__c
                                            FROM Log_Message__c];
        
        return getListAsMap(logList, namespace);
    }


    //Get object as map. This is needed as in lwc the name getting with namespace, therefore need to convert it to map before sending response
    public static Map<String, object> getObjectAsMap(sObject rec, string namespace){
        Map<String, object> mapData = new Map<String, object>();

        map<String, Schema.SObjectField> fieldMap = Schema.getGlobalDescribe().get(rec.getSObjectType().getDescribe().getName()).getDescribe().fields.getMap();

        for(String field : rec.getPopulatedFieldsAsMap().keyset()) {
            //If it is not reference get the field value
            //if it is reference then either lookup with field or list of records
            if(! field.endsWith('__r')){
                mapData.put(field.replace(namespace, ''), rec.get(field));
            }
            else if(fieldMap.containsKey(field.replace('__r', '__c'))){
                mapData.put(field.replace(namespace, ''), getObjectAsMap(rec.getSObject(field), namespace));
            }
            else{
                mapData.put(field.replace(namespace, ''), getListAsMap(rec.getSObjects(field), namespace));
            }
        }
        
        return mapData;
    }

    public static List<Map<String, object>> getListAsMap(List<sObject> recList, String namespace){
        List<Map<String, object>> listData = new List<Map<String, object>>();

        for(sObject rec : recList){
            listData.add(getObjectAsMap(rec, namespace));
        }

        return listData;
    }
}


At the beginning of the method getAllLogs I'm using query to find current org namespce, it is probably good idea to store it in metadata/label and save this query.

After that I'm calling method getListAsMap which convert the list of records to list of maps. 


3.Remove the namespace in the javascript code

Similar to the second approach, only here we will remove the namespace in the javascript code.

In such solution the apex code, remain as the original code

public with sharing class ViewLogMessageController {    
    @AuraEnabled
    public static List<Log_Message__c> getAllLogs(){
        
        
        return [    SELECT Id, Subject__c,Log_Type__c
                    FROM Log_Message__c];
    }
}

But in the javascript will implement few changes. First, I'm storing the namespace in custom label and import it. Later, I 'm using it to remove the namespace from each field in the result

import { LightningElement, track } from 'lwc';
import namespaceLabel from '@salesforce/label/c.Namespace_Prefix';
import getAllLogs from '@salesforce/apex/ViewLogMessageController.getAllLogs';

export default class ViewLogMessages extends LightningElement {

    @track logMessages;

    label = {
        namespaceLabel
    }

    connectedCallback(){
        if(! this.logMessages){
            getAllLogs().then(
                result => {
                    let namespace = this.label.namespaceLabel == 'na' ? '' : this.label.namespaceLabel;
                    this.logMessages = [];

                    for(let log in result){
                        let logItem = {};

                        for(let field in result[log]){
                            logItem[field.substring(field.indexOf(namespace) + namespace.length)] = result[log][field];
                        }

                        this.logMessages.push(logItem);
                    }
                }
            );
        }
    }
}


Note that in both method the conversion should happen also in the other direction - if the web component send the data back to the server to be saved then the wrapper/map should be converted back to custom object.


Also notice that you might get namespace issues in other areas in your web component. For example, using Navigate to object home tab, won't work if it is missing the namespace

this[NavigationMixin.Navigate]({
    type: 'standard__objectPage',
    attributes:{
        objectApiName: 'Log_Message__c',
        actionName: 'home'
    }
});

To solve this case, you can store the namespace either in metadata/label, retrieve it in the javascript and use it whenever you referencing the object name

this[NavigationMixin.Navigate]({
    type: 'standard__objectPage',
    attributes:{
        objectApiName: (namespace + 'Log_Message__c'),
        actionName: 'home'
    }
});


Approval Process Reminders

Few years ago I published code for approval process reminders, recently I modified this code with some alignment/improvements + convert the relevant UI to web component.

The main request is that if approval process is pending on the same person for more than X hours a reminder should be sent to this person (+potential other recipients), and then after every X hours additional reminder should be sent until the approval received.


For the updated solution I'm using my free app: Asynchronous Process Manager / Creator, which allow to easily set and monitor background jobs and it provide some other core components that being used and save some efforts.


For the reminder process using the following components (all available in git): 

  • Custom object: Approval Process Reminder- Setup for specific approval process
  • Custom object: Approval Reminder Record- Reference for specific record that is pending approval
  • Custom tab: Manage Approval Reminders- shows the current reminders setup + option to modify each.
  • Web Component: ManageApprovalProcessReminders - used in the tab
  • Few apex classes for the server side processing
    • ManageApprovalProcessReminderCotroller - main logic for the web components
    • ApprovalProcessReminderService - logic for sending alert
    • ApprovalReminderBatch - batch process that run over all active process and search for pending approval processes


How to install:

1.Install free app Asynchronous Process Manager / Creator

2.Go to tab MBA Settings and click Start Scheduler 

3.Install the Approval Process Reminders or get from the code from Git

4.Set up your approval reminder as in the demo.




Considerations/Limitations:

  • The process assume you won't have hundreds of records pending approval under the same process. If you do have you might need to do some code refactoring to support that.
  • The process doesn't support Queues, only users. If your approval assigned to queue, you might need to apply some changes, depending on what you expect to happen.
  • The approval process data (ProcessInstance) is a setup data that cannot be created in test classes. Therefore the tests trying to use the first existing approval process. If your org doesn't have any approval process code coverage might be lower, but still should be enough for installing.
  • When starting the process you can configure the minutes interval. Although you can set lower minutes value, I recommend to have at least 30 minutes interval.
  • Some users in the company will dislike you :-)

Calculate Mathematical Text Expression in Apex

 I recently came across the following issue. I created custom metadata, where I can set mathematical expression with combination of fixed values and merge fields. 

For example: (5+{!Total_Days__c})*10

Then, in apex code, I needed to calculate this expression based on the record values.

Replacing the merge fields with actual value was very easy, using String methods like substring/replace, but calculating the expression was less easy, as there is no build in method that handling it. 

When searching for similar solution in other language, most of the suggestion are based on Javascript method eval.

Eventually I took some time and wrote such service myself.

It might missing some error handling for wrong input, so when using it might want to wrap the function call with try-catch, but generally with correct input it does produce the correct number.


public class StringToNumber{

    public static Decimal convertStringToNum(String expression){

        Decimal val = 0;
        String copyExp=expression;

        while(copyExp.contains('(')){

            String innerLogic = copyExp.substring(copyExp.lastIndexOf('(') + 1).subStringBefore(')');
            
            Decimal innerCalc=getFormulaInnerValue(innerLogic);

            if(copyExp.contains('(' +  innerLogic + ')')){
                copyExp=copyExp.replace('(' +  innerLogic + ')', String.valueOf(innerCalc));
            }
            else{
                copyExp=copyExp.replace('(', '');
            }
        }

        return getFormulaInnerValue(copyExp);
    }

    private static Decimal getFormulaInnerValue(String innerExp){

        Decimal val = 0;
        String copyExp = innerExp;

        //Should not have indexes at this point only numbers and operators
        list<String> l_logicValues = copyExp.split('[-+//*]');


        //Get list of operators
        list<String> l_operators = new list<String>();

        for(Integer formulaIndex = 0; formulaIndex < innerExp.length(); formulaIndex ++){
            if(innerExp.charAt(formulaIndex) == 43
                || innerExp.charAt(formulaIndex) == 45
                || innerExp.charAt(formulaIndex) == 42
                || innerExp.charAt(formulaIndex) == 47){

                l_operators.add(innerExp.substring(formulaIndex, formulaIndex + 1));
            }
        }
        
        Integer nextIndex=0;
    
        //empty value indicate minus value
        if(String.isBlank(l_logicValues.get(0))){
            val = Decimal.valueOf(l_logicValues.get(1).trim()) * (-1);
            l_logicValues.remove(0);
            l_operators.remove(0);
        }
        else{
            val =Decimal.valueOf(l_logicValues.get(0).trim());
        }
            
            
        for(Integer valIndex=1; valIndex<l_logicValues.size(); valIndex++){
            String sign = l_operators.get(nextIndex);
            Decimal nextVal = 0;
            
            //empty value indicate minus value
            if(String.isBlank(l_logicValues.get(valIndex))){
                valIndex++;
                nextIndex++;
                nextVal = decimal.valueOf(l_logicValues.get(valIndex).trim()) * (-1);
            }
            else{
                nextVal = decimal.valueOf(l_logicValues.get(valIndex).trim());
            }
            
            
            val = addFixValue(val, nextVal, sign);
            nextIndex++;
        }
        return val;
    }

    private static Decimal addFixValue(
        Decimal currenctVal,
        decimal nextVal,
        String sign){

        Decimal newVal = currenctVal;
            
        if(sign == '+'){
            newVal = nextVal + currenctVal;
        }
        else if(sign == '-'){
            newVal = currenctVal - nextVal;
        }
        else if(sign == '*'){
            newVal = nextVal * currenctVal;
        }
        else if(sign == '/'){
            if(nextVal == 0){
                newVal = 0;
            }
            else{
                newVal =  currenctVal/nextVal;
            }
        }

        return newVal;
    }

}


Some testing...

System.assertEquals(11, StringToNumber.convertStringToNum('5+6'));
System.assertEquals(1, StringToNumber.convertStringToNum('-5+6'));
System.assertEquals(-0.4, StringToNumber.convertStringToNum('-0.8/2'));
System.assertEquals(-18, StringToNumber.convertStringToNum('6*(-3)'));
System.assertEquals(9, StringToNumber.convertStringToNum('6*(-3)/-2'));
System.assertEquals(-29, StringToNumber.convertStringToNum('-2*(6*(-3)/-2+5.5)'));
System.assertEquals(24, StringToNumber.convertStringToNum('(5+(6/2))*3'));
System.assertEquals(-6, StringToNumber.convertStringToNum('(5+(14/-2))*3'));
System.assertEquals(124, StringToNumber.convertStringToNum('(5+(6/2))*3+100'));
System.assertEquals(74, StringToNumber.convertStringToNum('(5+(6/2))*3+(100/2)'));
System.assertEquals(740, StringToNumber.convertStringToNum('((5+(6/2))*3+(100/2))*10'));


Retire of Permission on Profiles

If you are working as a Salesforce admin/developer you've probably heard somewhere that Salesforce is planning to make a significant cha...