Blog

Track first response time on Cases using Entitlements

When working with Cases in Salesforce, a common question is “How much time does it take our team to respond to incoming customer cases?” Often, customers have specific SLAs on required response times, and team managers want to understand how efficient their team is at addressing new issues. In this blog post, we are going to cover how we can use Entitlement Management with Cases to track this metric.

 

Here’s a step-by-step walk through of how to set this up in Salesforce:

Enable Entitlement management

Go to Setup -> Entitlement Management -> Entitlement Settings

On this page, you will see a checkbox called Enable Entitlement Management. Check this and click Save. Once Entitlement Management is enabled, you will see other options on this page like Entitlement-Related Lookup Filters on Case Fields, Milestone Feed Items, Milestone Tracker Time Settings and Milestone Time Settings. These options aren’t relevant to this setup, but you can learn more about them from Salesforce’s Help Docs.

Setup milestones and entitlement process

Now we need to create the first response Milestone, create the Entitlement process and add the milestone to the Entitlement process.

Go to Setup -> Entitlement Management -> Milestones -> Click New Milestone button

Screen Shot 2018-05-21 at 12.51.45 pm.png

Set Name = First Response, Recurrence Type = No Recurrence and click Save to create the new milestone.

Go to Setup -> Entitlement Management -> Entitlement Processes, create a new Entitlement process for Case. We are going to use a basic Entitlement process to demonstrate here:

Screen Shot 2018-05-21 at 12.58.20 pm.png

Next, add the Milestone to an Entitlement process.

In this example, we are assuming that the first response should be sent within 4 hours of the arrival of the case. So, we have added a Milestone to our Entitlement process with the number of Minutes to Complete Milestone value.

Screen Shot 2018-05-21 at 1.01.53 pm.png

Setup trigger to assign entitlement process to incoming cases

If you are using Email-to-Case functionality, the Entitlement from Account won’t be automatically assigned to the incoming cases. Therefore, we need to add a trigger to associate an active Entitlement from an Account to the incoming Case.

Here is the sample code for this:

// trigger on Case object

trigger CaseTrigger on Case (before insert, before update) {
    if(Trigger.isBefore){
        if(Trigger.isInsert || Trigger.isUpdate){
            CaseEntitlementHelper.applyEntitlements(Trigger.new);
        }
    }
}

// helper class

public with sharing class CaseEntitlementHelper {

    /**
     * Apply entitlements from contact or account.
     */
    public static void applyEntitlements(List cases){
        List contactIds = new List();
        List acctIds = new List();
        for (Case c : cases){
            if (c.EntitlementId == null && c.ContactId != null && c.AccountId != null){
                contactIds.add(c.ContactId);
                acctIds.add(c.AccountId);
            }
        }
        if(!contactIds.isEmpty() || !acctIds.isEmpty()){
            List entitlementContacts = [
                select e.EntitlementId, e.ContactId, e.Entitlement.AssetId from EntitlementContact e
                where e.ContactId in :contactIds
                and e.Entitlement.EndDate >= TODAY and e.Entitlement.StartDate <= TODAY
            ];
            if(!entitlementContacts.isEmpty()){
                for(Case c : cases){
                    if(c.EntitlementId == null && c.ContactId != null){
                        for(EntitlementContact ec : entitlementContacts){
                            if(ec.ContactId==c.ContactId){
                                c.EntitlementId = ec.EntitlementId;
                                if(c.AssetId==null && ec.Entitlement.AssetId!=null)
                                    c.AssetId = ec.Entitlement.AssetId;
                                break;
                            }
                        }
                    }
                }
            } else {
                List  entitlements = [
                    select e.StartDate, e.Id, e.EndDate, e.AccountId, e.AssetId
                    from Entitlement e
                    where e.AccountId in :acctIds and e.EndDate >= TODAY and e.StartDate <= TODAY
                ];
                if(!entitlements.isEmpty()){
                    for(Case c : cases){
                        if(c.EntitlementId == null && c.AccountId != null){
                            for(Entitlement e : entitlements){
                                if(e.AccountId == c.AccountId){
                                    c.EntitlementId = e.Id;
                                    if(c.AssetId==null && e.AssetId!=null)
                                        c.AssetId=e.AssetId;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Setup apex triggers to mark milestones as completed

We are going to use triggers on EmailMessage and CaseComment to mark the Milestone when it is completed.

Here is the sample code:

// email message trigger
trigger EmailMessageTrigger on EmailMessage (before insert) {
    if(Trigger.isInsert || Trigger.isAfter){
        if (UserInfo.getUserType() == 'Standard'){
            // complete milestone on valid first response
            EmailMessageHelper.handleFirstResponse(Trigger.new);
        }
    }
}
// email message trigger helper class
public with sharing class EmailMessageHelper {

    public static void handleFirstResponse(List emailMessages){
        DateTime completionDate = System.now();
        Map<Id, String> emIds = new Map<Id, String>();
        for (EmailMessage em : emailMessages){
            if(em.Incoming == false) emIds.put(em.ParentId, em.ToAddress);
        }
        if(emIds.isEmpty() == false){
            Set  emCaseIds = new Set();
            emCaseIds = emIds.keySet();
            List caseList = [
                select Id, ContactId, Contact.Email, OwnerId, 
                Status, EntitlementId, SlaStartDate, SlaExitDate
                from Case 
                where Id IN :emCaseIds
            ];
            if(!caseList.isEmpty()){
                List casesToUpdate = new List();
                for (Case caseObj : caseList) {
                    // consider an outbound email to the contact on the case a valid first response
                    if (emIds.get(caseObj.Id) == caseObj.Contact.Email && caseObj.EntitlementId != null &&
                        caseObj.SlaStartDate <= completionDate && caseObj.SlaStartDate != null && caseObj.SlaExitDate == null) {
                        casesToUpdate.add(caseObj.Id);
                    }
                }
                if(!casesToUpdate.isEmpty()) {
                    MilestoneHelper.completeMilestone(casesToUpdate, 'First Response', completionDate);
                }
            }
        }
    }
}
// case comment trigger
trigger CaseCommentTrigger on CaseComment (after insert) {
    if(UserInfo.getUserType() == 'Standard'){
        CaseCommentHelper.handleFirstResponse(Trigger.new);
    }
}
// case comment trigger helper class
public with sharing class CaseCommentHelper {

    public static void handleFirstResponse(List comments){
        DateTime completionDate = System.now();
        List caseIds = new List();
        for (CaseComment cc : comments){
            // Only public comments qualify
            if(cc.IsPublished == true) caseIds.add(cc.ParentId);
        }
        if(caseIds.isEmpty() == false){
            List caseList = [
                select Id, ContactId, Contact.Email, OwnerId, Status,
                EntitlementId, SlaStartDate, SlaExitDate
                from Case
                where Id IN :caseIds
            ];
            if(caseList.isEmpty() == false){
                List updateCases = new List();
                for (Case caseObj : caseList) {
                if (caseObj.EntitlementId != null && caseObj.SlaStartDate <= completionDate && 
                        caseObj.SlaStartDate != null && caseObj.SlaExitDate == null) {
                     updateCases.add(caseObj.Id);        
                }
            }
            if(updateCases.isEmpty() == false) {
                MilestoneHelper.completeMilestone(updateCases, First Response', completionDate);
            }
         }
      }
   }
}
// milestone helper class
public with sharing class MilestoneHelper {

    public static void completeMilestone(List caseIds, String milestoneName, DateTime completionDate) {
        List toUpdate = [
            select Id, CompletionDate
            from CaseMilestone cm
            where CaseId IN :caseIds 
             and cm.MilestoneType.Name = :milestoneName 
             and CompletionDate = null limit 1
        ];
        if(!toUpdate.isEmpty()){
            for (CaseMilestone cm : toUpdate){
                cm.CompletionDate = completionDate;
            }
            update toUpdate;
        }
    }
}

Create a report to view milestone violations

The last step is to create a report on Cases with Case Milestones to view a list of cases which didn’t meet the first response milestone. Now you can see the cases that are overdue and flag them for followup easily.

Questions? Ask us in the comments!

Happy coding!

SFDX Debug

Stream logs to CLI with apex:log:tail

Line Item Content

CPQ Custom Quote Lines

View File Document

Modify CPQ PDF Header and Footer

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Think we can help you? Reach out