Monday 4 March 2024

Salesforce - Show Toast Message from Flow

There was a requirement from the customer in which we needed to add a create a case from the Account page but before creating the case, it should check multiple conditions like whether a record exists of a particular record type like an Account has at least one contact and few more conditions.

 

If we use a standard Case related list and create a case then it should not check any condition or we can do it by writing a flow on the Case object. The main requirement from the customer was to show a Toast message if all the conditions are valid and if any condition is not fulfilled then an error toast message will also be shown. But all the things should have happened on the Account Detail page. Last but not least that should also work on the Mobile part as well.

 

Therefore, I created 3 different things.

1.      LWC:  This LWC only show a toast message but the toast contains those parameters which passed from the flow.

2.      Screen Flow: All the business logic contains inside flow.

3.      Quick Action: Call flow from quick action.

 

LWC:

Create a LWC which accepts some parameters from Flow and shows a toast message.

ToastInLighteningFlow.html

<template>

   

</template>

 

ToastInLighteningFlow.js

import { LightningElement, api } from 'lwc';

import { ShowToastEvent } from 'lightning/platformShowToastEvent';

import { FlowNavigationFinishEvent } from 'lightning/flowSupport';

 

export default class ToastInLightningFlow extends LightningElement {

    @api mode;

    @api variant;

    @api message;

    @api title

 

    connectedCallback() {

        this.handleShowToast();

        this.handoverCloseAction();

    }

 

    handleShowToast() {

        const toastEvt = new ShowToastEvent({

            title: this.title,

            mode: this.mode,

            variant: this.variant,

            message: this.message

        });

        this.dispatchEvent(toastEvt);

    }

 

    handoverCloseAction() {

        const navigateNextEvent = new FlowNavigationFinishEvent();

        this.dispatchEvent(navigateNextEvent);

    }

}

 

ToastInLighteningFlow.js-meta.xml

<?xml version="1.0"?>

<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">

    <apiVersion>57.0</apiVersion>

    <isExposed>true</isExposed>

    <targets>

        <target>lightning__FlowScreen</target>

        <target>lightning__RecordAction</target>

    </targets>

    <targetConfigs>

        <targetConfig targets="lightning__FlowScreen">

            <property name="title" type="String" label="Enter Title"/>

            <property name="mode" type="String" label="Enter Mode" default="dismissible"/>

            <property name="variant" type="String" label="Enter Varient"/>

            <property name="message" type="String" label="Enter Toast Message"/>

        </targetConfig>

        <targetConfig targets="lightning__RecordAction">

            <actionType>ScreenAction</actionType>

        </targetConfig>

    </targetConfigs>

</LightningComponentBundle>

 

 

The above Meta file exposed some input parameters from Visualflow and then LWC worked on the basis of these parameters.

 

Screen Flow

Create a screen flow, added some conditions which is the business needs of the customer then at the end add screen as resource which actually shows the LWC component.




The above flow is a sample display of flow, you can add multiple conditions to the flow. The main point is to show toast message when calling the screen flow. Another noticeable point is that the Screen element is introducing at the bottom of the flow. Normally developers’ use screen elements at the start of screen flow.

 

Let’s focus on the the screen element, there are 2 paths one is to show a success toast message and another is to show error toast message. The success toast message screen contains the LWC

 



The left red highlighted area in the screen element contains a parameters which should be passed to LWC. For a sample purpose, a case number was added in toast message.

 

Quick Action

Call the above flow from Quick Action on the Account detail record.

 

 

Result

 


The toast message finally render after clicking the action button

 


Friday 24 November 2023

How to check the Column Name of object using For Loop

 

Sometimes in code, we need to check the column name is match with any string then a developer stuck on this. Sample code of this kind of problem statement is that

 

List<String> liColName = new List<String>();

boolean IsHide = false;

liColName.add('Column 1');

liColName.add('Column 2');

liColName.add('Phone');

 

Account acc = [select Id,Name,Phone from Account LIMIT 1];

Map<String, Object> fieldsToValue = acc.getPopulatedFieldsAsMap();

for(string str:liColName)

{

 

    for (String fieldName : fieldsToValue.keySet()){

        if(fieldName == str)

         System.debug('field name is ' + fieldName + ', value is ' + fieldsToValue.get(fieldName));

              

    }

}

 

In the Above code, getPopulatedFieldAsMap() returns the Map in which it returns the column name in Key and its value in Value of the Map


Friday 12 April 2019

Overrite the Action Link in MVC



In my topic where I reused the view inside another main view, the problem occurred on an Action link, because we added the list views of contact and opportunity in the model view of Account, therefore when you see the edit, detail, delete or even Create hyperlink, you would see that it is pointing to the Account view itself which is not the right thing, it should redirect to the contact and opportunity views, like below screenshot



Means, you are inside the account detail page and if you mouse over the edit, detail, delete or even Create hyperlink then you would see the Account/Edit/2, see above red highlighted area. Now, we should also fix this by overwriting the Action Link, Link should be “/Contacts/Edit/2”. For this fix, created a class of URLs and added a static method like below one

public class URLs
{
 public static string ContactDetail(int ContactId,string Type)
 {
        string URL = string.Empty;
        if (Type == "Edit")
              URL = "/Contacts/Edit/" + ContactId.ToString();
        else if (Type == "Details")
              URL = "/Contacts/Details/" + ContactId.ToString();
        else if (Type == "Delete")
              URL = "/Contacts/Delete/" + ContactId.ToString();

        return URL;
 }
}

Above code shows how we overwrite the Edit, Detail and Delete URL, now this method called inside the list views of contact, a similar method can be made for an opportunity to replace the Action Link



Above screenshot is the combination of 2 pictures, the right side is the top view code which shows how we use the URLs class and overwrite the CREATE ACTION LINK and left side of the picture shows how we overwrite the detail, edit and delete action link.

Call main view inside the view like partial view in MVC



In MVC, normally users create a partial view and reuse the partial view in any of the main view. The developer generally creates a partial view using point and click option.




  
And when we generate this partial view, there is nothing in the view which identifies that this is a partial view like below



I was new in MVC pattern and worked on asp.net web forms, therefore I asked my developer fellows why we mark to check that it is partial view and use this view as a partial view, why we can’t reuse the normal view as a partial view, I got no answer. Therefore I tried something which I wanted to do 😊. My scenario is that I have an Account model which is the parent table, Contact and Opportunity are the children of Account. I wanted to show the list views of Contact and Opportunity in the Account Detail view, already made a complete set of views of Contact & Opportunity which are behaving independently. Let’s take the example of Contact, Contact list view is driven from the stored procedure and looks like below



 To call this contact list view inside the detail view of account, use the below code in Account Detail view



Above snapshot shows the complete code which is rendering the list views of contact & opportunity as a partial view inside the detail view of Account view. Firstly I called the same stored procedures and pass the account id which actually filters out the contacts and opportunity based on the account id then pass the generic list of contact & opportunity to the list view of contact & opportunity. This code makes our life easy because we only changing the code only at this level, no need to update the list views of contact & opportunity. The actual detail view of the Account now looks like below screenshot





We have learned how we reuse the main views inside any views, no need to create separate partial views, we can create partial views based on requirements

Tuesday 18 September 2018

Salesforce: Get the latest date record from object list against each Map Key

There is a scenario in which we have a territory object and against each territory, we have one to many sales rep. So there is a child object named SalesRep_To_Terr__c. This child object contains an effective date field and isActive flag. For any territory, there might be more than 1 active record in this object but I was needed to get the latest among all the active record. Latest means which has the most recent effective date. So to get that latest effective date first I maintained a Map contains the Sales Rep Id as key and list of SalesRep_To_Terr__c.

Initially, I have maintained a set of all territory

map<String , list<SalesRep_to_Terr__c>> MapUserSRT = new map<String , list<SalesRep_to_Terr__c>>();
Set<Id> sTerr = new Set<Id>();   // contains all the Territory
List<datetime> LstDT ;
datetime maxTDate ;

for(SalesRep_to_Terr__c terr :  [select Effective_Date__c,Sales_Representative__c ,Active__c  from SalesRep_to_Terr__c where Effective_Date__c != null  and Territory__c IN:  sTerr and active__c= true order by Effective_Date__c LIMIT 1])
{
      if(MapUserSRT.containsKey(terr.Sales_Representative__c))
      {
            list<SalesRep_to_Terr__c> lstDWP = MapUserSRT.get(terr.Sales_Representative__c);
            lstDWP.add(terr);
            MapUserSRT.put(terr.Sales_Representative__c, lstDWP);
              
      }
      else
      {
            MapUserSRT.put(terr.Sales_Representative__c, new List<SalesRep_to_Terr__c> { terr   });
       }
}

Now the sole of this topic to get the latest among each territory is given below

for(String UserRec : MapUserSRT.keyset())
{
            LstDT = new List<datetime>();
            for(SalesRep_to_Terr__c Terr : MapUserSRT.get(UserRec))
            {
                LstDT.add(Terr.Effective_Date__c);
            }
           
            LstDT.sort();
            maxTDate = LstDT.get(LstDT.size()-1);
           
            for(SalesRep_to_Terr__c Terr : MapUserSRT.get(UserRec))
            {
                if(Terr.Effective_Date__c == maxTDate)
                {
                    if(Terr.Active__c)
                    {
                        MapUserLTerritory.put(Terr.Territory__c,UserRec);
                        break;
                    }
                }
                    
            }
  }

The yellow part above actually describes to first fill the list of datetime for each user and then sort it. The default sort function sort the list to Ascending order means the recent date would be at the end of the list on the last index. MaxTDate variable holds the recent date. Then against each Sales rep Territory record check the date and Active flag. Lastly maintained a Map contains the Territory as Key and Sales rep as the value


Salesforce.com Event Overlap handling through trigger

Workflow or Process Builder is not capable of handling the overlapping of OOB event timing, therefore, it requires a trigger. The scenario was to show an error message to the user on creating an event that this start time and end date overlaps with another event of the same owner and the user should get the link of that previously saved event. The trigger should be written on Before Insert and Before Update to prevent it from saving the event record. I also asked to only check the overlap event on some particular record types. Therefore first filled a SET of those record types and match that event has the same record types or not.

The Trigger code is given below



Trigger EventValidation on Event(before insert, before update)
{
    
   EventUtility Utility = new EventUtility();
   set<id> RtIds = new set<id>();
  
    for(RecordType rt : [SELECT Id FROM RecordType WHERE DeveloperName in ('Test_RecordType1',' Test_RecordType2')])
        RtIds.add(rt.id);
   
    system.debug('RtIds :'+RtIds);
    List<Event> selectedRtEvent = new list<Event>();
    for(Event t : trigger.new)
    {
        system.debug('t.recordtype.name' + t.recordtypeid);
       
        if(RtIds.contains(t.recordtypeid))
            selectedRtEvent.add(t);
    }
    system.debug('selectedRtEvent ::' + selectedRtEvent );
    if(selectedRtEvent.size() > 0)
    {
        system.debug('selectedRtEvent : ' + selectedRtEvent);
        Utility.ValidateRecord(selectedRtEvent);
    }
 }


The bottom yellow highlighted lines call the utility class which validates the overlapping of event timings.

Code of Utility Class given below

public with sharing class EventUtility
{   
   
    public void EventUtility()
    {}
   
    public void ValidateRecord(List<Event> LstEvent)
    {
        map<Id,List<Event>> mEvent = new map<Id,List<Event>>();
        set<Id> sOwner = new set<Id>();
        for(Event t : LstEvent)
        {
            sOwner.add(t.ownerid);
        }
       
        for(Event t: [SELECT OwnerId, WhoId, WhatId, StartDateTime, EndDateTime FROM Event where OwnerId in :sOwner])
        {
            if(mEvent.containsKey(t.OwnerId))
            {
                list<Event> lst = mEvent.get(t.OwnerId);
                lst.add(t);
                mEvent.put(t.OwnerId,lst);
            }
            else
            {
                list<Event> lst = new list<Event>();
                lst.add(t);
                mEvent.put(t.OwnerId,lst);
            }
        }
       
       
        String errorMsg = 'Record Already Exists : ';
        String baseUrl = URL.getSalesforceBaseUrl().toExternalForm();
        boolean IsOverlap = false;
        for(Event newRecords : LstEvent)
        {
           
           
            if(mEvent.containsKey(newRecords.ownerid))
            {
                for(Event userrecords : mEvent.get(newRecords.ownerid))
                {
                   
                    IsOverlap = false;
                    IsOverlap = TimePeriodOverlap(newRecords.StartDateTime,newRecords.EndDateTime,userrecords.StartDateTime,userrecords.EndDateTime);
                    system.debug('IsOverlap: ' + IsOverlap);
                   
                    if (IsOverlap == true && (userrecords.ownerid == newRecords.ownerid))
                    {
                        newRecords.addError(errorMsg + '<a target="_blank" href =' +baseUrl+'/'+userrecords.id+'> (Duplicate Record)</a>',false);
                    }
                   
                }
            }
        }
       
       
    }      
        public boolean TimePeriodOverlap(DateTime BS, DateTime BE, DateTime TS, DateTime TE)
        {
            // More simple?
            // return !((TS < BS && TE < BS) || (TS > BE && TE > BE));

            // The version below, without comments
            /*
            return (
                (TS >= BS && TS < BE) || (TE <= BE && TE > BS) || (TS <= BS && TE >= BE)
            );
            */

            return (
                // 1. Case:
                //
                //       TS-------TE
                //    BS------BE
                //
                // TS is after BS but before BE
                (TS >= BS && TS < BE)
                || // or

                // 2. Case
                //
                //    TS-------TE
                //        BS---------BE
                //
                // TE is before BE but after BS
                (TE <= BE && TE > BS)
                || // or

                // 3. Case
                //
                //  TS----------TE
                //     BS----BE
                //
                // TS is before BS and TE is after BE
                (TS <= BS && TE >= BE)
            );
        }
       
   
}


Monday 15 February 2016

Manual Sharing for Partner community using Apex



Salesforce

This topic is related to the sharing in communities. I need to setup the sharing on partner communities and there is no point and click option for sharing any object for partner communities object. First you should setup the communities in the ORG than go to the setupàcommunity settings and add a new sharing sets, see the below image 1


In above red highlighted box, you would not able to see the partner community profiles, so this is really a main point for which I used manual sharing by apex code. We can add a sharing rule for customer community user license. So if we are making customer community than this would be pretty mush simple to configure. Now moving forward, my task was to share a newly created case in the community only for same account, Means let’s say if I have created a Case1 and adding this case against any contact, obviously Account is there for that contact, so this Case1 should be share among all those community user which have that same Account Id and if another account user login to the community than that Case1 should not be seen for that User. So this is the user case which I need to follow here. So I have made a trigger on case and on that trigger, newly created case should be share with all those user which are of same account. Please keep this in mind if let’s say we update that case and link that case with another Account than we must un-share the case with previous users and that case would be share with new community user. So, I have just written a sample here which is perfect for the created case if we change the Account on updating than few more lines needs to be added here, please see the below trigger code here.

trigger trShareCase on Case (after insert, after update) 
{
    map<Id,Id> mCase_Account = new map<Id,Id>();
    map<Id,Id> mUser_Account = new map<Id,Id>();
    map<Id,List<Id>> mCase1 = new map<Id,List<Id>>();
    Map<Id,List<User>> mUser = new Map<Id,List<User>>();
            for(Case cs:trigger.new)
            {
                        if (cs.accountid != null)
                        {
                                    mCase_Account.put(cs.Id,cs.accountid);
                        }
            }

            for (user u : [select Id,AccountId from user where IsActive= true And AccountId in :mCase_Account.values()])
            {
              mUser_Account.put(u.Id,u.AccountId);
            }
           
            List<Id> liUser ;
            for(Id caseId: mCase_Account.keyset())
            {
                         for(Id userId: mUser_Account.keyset())
                         {
                                    if(mCase_Account.get(caseId) == mUser_Account.get(userId))
                                    {
                                        if (!mCase1.containsKey(caseId))
                                                {
                                                    liUser = new List<Id>();
                                                    liUser.add(userId);
                                                            mCase1.put(caseId,liUser);
                                                }
                                                else
                                                {
                                                    liUser = new List<Id>();
                                                            liUser = mCase1.get(caseId);
                                                            liUser.add(userId);
                                                            mCase1.put(caseId,liUser);
                                                }
                                    }
                         }

            }
            List<CaseShare> csShareList = new List<CaseShare>();
            Id TestId ;
            for(Case cs:trigger.new)
            {
              
               for (Id caseId: mCase1.keyset())
               {
                 if (cs.Id == caseId)
                         {
                            liUser = mCase1.get(caseId);
                                    for (Id UserId: liUser)
                                    {
                                                CaseShare csShare = new CaseShare();
                                                // Give Read write access to that user for this particular case record.
                                                csShare.CaseAccessLevel = 'Read';
                                                // Assign case Id of case record.
                                                csShare.CaseId = cs.id;
                                                // Assign user id to grant read write access to this particular case record.
                                                TestId = UserId;
                                                csShare.UserOrGroupId = TestId;
                                                //csShare.RowCause = Schema.Project__Share.RowCause.Manual;
                                                csShareList.add( csShare );
                                    }
                         
                         }

               }

              
            }
           
            insert csShareList;
}
In above code, first yellow highlighted code is showing a map which consist Case Id and Account Id, second green highlighted code showing map in which it is saving User Id and Account Id, and I used SOQL to fetched all those user which have Account Ids from first map value. In third gray highlighted code, it is than maintaining very important map. This map contains a signature of Case Id and List of user Ids. So I am using 2 for loops which actually maintain User list with single case, if the map do not contain any key than it would first add the value in the map with Case Id as key and List of User Id and in second if condition, if the same key exists than it is altering the map value against same key that is case id. Last but not least in the later part the case sharing object comes into play and adding the entries into the list having 1 to many relation means 1 case might contains one or multiple users.
The Community plus license also needs the same way of manual sharing but if you want to play the same game against customer community user license than go to setpàcommunity settings and define the sharing set and add the same rule means Case1 should be share against all those user which are under the umbrella of same Account, see below image for your help

So this is the case of adding any new case, let’s say if we are making any new partner community user than what should we do? Because we have earlier worked on Case object. So, for this we need to add a trigger on User as well but the important punch over here is that if we add a trigger on user and on that trigger if we are manually share the object like Case, so if User is added and then hit that trigger, it would give an exception of MIXED_DML_OPEARATION, reason being we are getting the exception because the same time we are using the System object that is user for DML operation and on the other way we are using non-system object which is CaseSharing object, so it would create an issue. So to handle this we should need some time difference between these 2 operation, so I have made a custom formula date time field in user and set the time 55 minutes before the created date. See below image


Why I added this field because I want to add a time base workflow and in time base workflow, it need minimum an hours to execute. I have also added a custom field in user object, a checkbox “Show in Community” , so if the workflow executes after 5 min than it would update this checkbox field and on update the user record than the trigger would execute on user. So the purpose of this so to give some time elapse between user object creation and case sharing object. See the below time dependent workflow snapshot.



The rule criteria is mentioned in red box which show that if the custom field is not checked than it would enter into the workflow. Secondly, the red box showing 1 hour after the custom date time formula field, we do not have any option to execute the workflow under an hour. Third one the field update, it would mark check on the custom checkbox field. Immediately if it mark the checkbox than it would hit the trigger and trigger will share the object with the newly created user. I am not writing the User trigger here as this trigger would be prettly kuch similar to the above trigger, only the difference would be that in this trigger, we should maintain the final map with User Id and list of case, the opposite logic to the above trigger, so happy coding guys J