Enable Automatic Completion of Case Milestones.

Develop an Apex trigger to automatically designate milestones as “Completed” for cases that meet specific distinctive conditions. Within the trigger, specify the events and relevant case criteria that must be met for a milestone to be marked as “Completed.” A similar trigger can be implemented to automatically complete milestones for work orders.


TIP In Lightning Experience, agents can click the Mark Completed link in the Milestones component to mark a milestone completed. It’s not automation, but it is easy. For more information, see Set Up the Milestone Tracker.

The following triggers mark specific types of milestones Completed when the case they are on meets unique criteria. We’ve also provided a milestone utility Apex class and accompanying unit tests. Define the milestone utility class before you use any of the triggers.Milestone Utility Apex ClassApex classes reduce the size of your triggers and make it easier to reuse and maintain Apex code. To define this Apex class in your org:

  1. From Setup, enter Apex Classes in the Quick Find box, then click Apex Classes.
  2. Click New.
  3. Copy the class text and paste it into the text field.
  4. Click Save.
public class MilestoneUtils {
    public static void completeMilestone(List<Id> caseIds, 
            String milestoneName, DateTime complDate) {  
    List<CaseMilestone> cmsToUpdate = [select Id, completionDate
            from CaseMilestone cm
            where caseId in :caseIds and cm.MilestoneType.Name=:milestoneName 
            and completionDate = null limit 1];
    if (cmsToUpdate.isEmpty() == false){
        for (CaseMilestone cm : cmsToUpdate){
            cm.completionDate = complDate;
            }
        update cmsToUpdate;
        }
    }
}

Apex Class Unit Test

You can set up Apex unit tests in the developer console to scan your code for any issues.

/**
* This class contains unit tests for validating the behavior of Apex classes
* and triggers.
*
* Unit tests are class methods that verify whether a particular piece
* of code is working properly. Unit test methods take no arguments,
* commit no data to the database, and are flagged with the testMethod
* keyword in the method definition.
*
* All test methods in an organization are executed whenever Apex code is deployed
* to a production organization to confirm correctness, ensure code
* coverage, and prevent regressions. All Apex classes are
* required to have at least 75% code coverage in order to be deployed
* to a production organization. In addition, all triggers must have some code coverage.
*
* The @isTest class annotation indicates this class only contains test
* methods. Classes defined with the @isTest annotation do not count against
* the organization size limit for all Apex scripts.
*
* See the Apex Language Reference for more information about Testing and Code Coverage.
*/
@isTest
private class MilestoneTest {

static testMethod void TestCompleteMilestoneCase(){

List<Account> acts = new List<Account>();
Account myAcc = new Account(Name='TestAct', phone='1001231234');
acts.add(myAcc);

Account busAcc = new Account(Name = 'TestForMS', phone='4567890999');
acts.add(busAcc);
insert acts;
Contact cont = new Contact(FirstName = 'Test', LastName = 'LastName', phone='4567890999', accountid = busAcc.id);
insert(cont);

Id contactId = cont.Id;

Entitlement entl = new Entitlement(Name='TestEntitlement', AccountId=busAcc.Id);
insert entl;

String entlId;
if (entl != null)
entlId = entl.Id; 

List<Case> cases = new List<Case>{};
if (entlId != null){
Case c = new Case(Subject = 'Test Case with Entitlement ', 
EntitlementId = entlId, ContactId = contactId);
cases.add(c);
}
if (cases.isEmpty()==false){
insert cases;
List<Id> caseIds = new List<Id>();
for (Case cL : cases){
caseIds.add(cL.Id);
}
milestoneUtils.completeMilestone(caseIds, 'First Response', System.now());
}
}

static testMethod void testCompleteMilestoneViaCase(){

List<Account> acts = new List<Account>();
Account myAcc = new Account(Name='TestAct', phone='1001231234');
acts.add(myAcc);

Account busAcc = new Account(Name = 'TestForMS', phone='4567890999');
acts.add(busAcc);
insert acts;
Contact cont = new Contact(FirstName = 'Test', LastName = 'LastName', phone='4567890999', accountid = busAcc.id);
insert(cont);

Id contactId = cont.Id;

Entitlement entl = new Entitlement(Name='TestEntitlement', AccountId=busAcc.Id);
insert entl;

String entlId;
if (entl != null)
entlId = entl.Id; 

List<Case> cases = new List<Case>{};
for(Integer i = 0; i < 1; i++){
Case c = new Case(Subject = 'Test Case ' + i);
cases.add(c);
if (entlId != null){
c = new Case(Subject = 'Test Case with Entitlement ' + i, 
EntitlementId = entlId);
cases.add(c);
}
}
}
}

Sample Trigger 1

You can create a milestone named Resolution Time that requires cases to be closed within a certain length of time. It’s a great way to enforce case resolution terms in SLAs. This sample case trigger marks each Resolution Time milestone Completed when its case is closed. This way, the support agent doesn’t have to manually mark the milestone completed after closing the case.

NOTE This trigger references the milestone utility test class, so be sure to define the test class first.

To define this trigger in your org:

  1. From Setup, enter Case Triggers in the Quick Find box, then click Case Triggers.
  2. Click New.
  3. Copy the trigger text and paste it into the text field.
  4. ClickSave.
trigger CompleteResolutionTimeMilestone on Case (after update) {
    if (UserInfo.getUserType() == 'Standard'){
        DateTime completionDate = System.now(); 
            List<Id> updateCases = new List<Id>();
            for (Case c : Trigger.new){
                    if (((c.isClosed == true)||(c.Status == 'Closed'))&&((c.SlaStartDate 
                        <= completionDate)&&(c.SlaExitDate == null)))
        updateCases.add(c.Id);
        }
    if (updateCases.isEmpty() == false)
        milestoneUtils.completeMilestone(updateCases, 'Resolution Time', completionDate);
    }
}

Sample Trigger 2

You can create a milestone named First Response that requires agents to get in touch with a case contact within X minutes or hours of the case’s creation. It’s a nice way to ensure that your support team is communicating with case contacts as soon as possible. This sample email trigger marks a First Response milestone Completed when an email is sent to the case contact. That way, the support agent doesn’t have to manually mark the First Response milestone Completed after they email the case contact.

NOTE This trigger references the milestone utility test class, so be sure to define the test class first.

To define this trigger in your org:

  1. From Setup, enter Email Triggers in the Quick Find box, then click Email Triggers.
  2. Click New.
  3. Copy the trigger text and paste it into the text field.
  4. ClickSave.
trigger CompleteFirstResponseEmail on EmailMessage (after insert) {
    if (UserInfo.getUserType() == 'Standard'){
        DateTime completionDate = System.now();
        Map<Id, String> emIds = new Map<Id, String>();
        for (EmailMessage em : Trigger.new){
            if(em.Incoming == false)
                emIds.put(em.ParentId, em.ToAddress);
        }
        if (emIds.isEmpty() == false){
            Set <Id> emCaseIds = new Set<Id>();
            emCaseIds = emIds.keySet();
                List<Case> caseList = [Select c.Id, c.ContactId, c.Contact.Email,
                    c.OwnerId, c.Status,
                    c.EntitlementId,
                    c.SlaStartDate, c.SlaExitDate
                    From Case c where c.Id IN :emCaseIds];
            if (caseList.isEmpty()==false){
                    List<Id> updateCases = new List<Id>();
                    for (Case caseObj:caseList) {
                        if ((emIds.get(caseObj.Id)==caseObj.Contact.Email)&&
                            (caseObj.Status == 'In Progress')&&
                            (caseObj.EntitlementId != null)&&
                            (caseObj.SlaStartDate <= completionDate)&&
                            (caseObj.SlaStartDate != null)&&
                            (caseObj.SlaExitDate == null))
                                updateCases.add(caseObj.Id);
                    }
                    if(updateCases.isEmpty() == false)
                        milestoneUtils.completeMilestone(updateCases, 
                                'First Response', completionDate);
            }
        }
    }        
}

Sample Trigger 3

While the previous trigger dealt with email messages, this sample case comment trigger marks a First Response milestone Completed when a public comment is made on the case. You can use it if a public case comment is a valid first response in your support terms.


NOTE This trigger references the milestone utility test class, so be sure to define the test class first.

To define this trigger in your org:

  1. From Setup, enter Case Comment Triggers in the Quick Find box, then click Case Comment Triggers.
  2. Click New.
  3. Copy the trigger text and paste it into the text field.
  4. Click Save.
trigger CompleteFirstResponseCaseComment on CaseComment (after insert) {
    if (UserInfo.getUserType() == 'Standard'){
        DateTime completionDate = System.now();
        List<Id> caseIds = new List<Id>();
        for (CaseComment cc : Trigger.new){
                if(cc.IsPublished == true)
                caseIds.add(cc.ParentId);
        }
        if (caseIds.isEmpty() == false){
            List<Case> caseList = [Select c.Id, c.ContactId, c.Contact.Email,
                    c.OwnerId, c.Status,
                    c.EntitlementId, c.SlaStartDate,
                    c.SlaExitDate
                    From Case c
                    Where c.Id IN :caseIds];
        if (caseList.isEmpty() == false){
            List<Id> updateCases = new List<Id>();
            for (Case caseObj:caseList) {
                if ((caseObj.Status == 'In Progress')&&
                        (caseObj.EntitlementId != null)&&
                        (caseObj.SlaStartDate <= completionDate)&&
                        (caseObj.SlaStartDate != null)&&
                        (caseObj.SlaExitDate == null))
                    updateCases.add(caseObj.Id);
                }
                if(updateCases.isEmpty() == false)
                    milestoneUtils.completeMilestone(updateCases, 
                    'First Response', completionDate);
            }
        }
    }
}