Synchronize A Parent Case's Status To The Status Of Its Children
Quickstart: Synchronize A Parent Case's Status To The Status Of Its Children
This is part of the Developing with Service & Support Quickstarts
Use Case
When an organization is using a case hierarchy, it is often useful to apply automations to that hierarchy to assure that certain processes are being followed rigorously. A common desire among customer service organizations is to create parent cases whose statuses are synchronized with their children. That is, the parent will be closed only when all its children are closed; and as long as any child cases are open, the parent will remain open as well.
Background
Accomplishing this type of synchronization across objects requires an Apex trigger. A trigger is a custom piece of logic you express using Force.com's Apex programming language. This logic will be executed whenever a case is created (by whatever means), allowing you to perform any kind of action such as validation and field and record updates. Triggers can be set up to be executed before a record is created which gives you the opportunity to do some validation and if things aren't right, prevent the record from being created. In addition, you can set your trigger up to be executed after the record is created in which case you get a chance to read the field value from the case and perform any kind of update or notification based on them.
When you define a trigger you define three things. First, the name of the trigger - this is an arbitrary name you come up with. Second, the object to which you want the trigger to apply - here it would be the Case object. Lastly, which data events (insert, update, delete, undelete) you want the trigger to be executed before or after. Within the body of the trigger, variables called Trigger.old and Trigger.new are available which represent the collection of records before and after modification respectively. Generally you create a loop through one of the Trigger collections to perform your custom logic.
Sample Code: Trigger that sets the parent case status according to the statuses of its children
This trigger is set to run after one or more cases has been successfully inserted (created) or updated. It loops through the batch of created or updated records, and for each case that has a parent, it adds that parent case to a list of cases to check. It then runs a single query that gathers information about the closed status of each of these parents and all their children cases. Finally, if any of the children cases are open, it ensures that the parent is open, and if all the children are in a closed status then it ensures that the parent case is also closed.
Note that the Status field of Case is generally customized in every Salesforce.com org. The sample code here uses statuses of "New" and "Closed," but these statuses may not exist in your org. Before using this code in your org, ensure that the statuses you are using are valid values in your Case Status field. You can view the valid values of your Case Status field by navigating to Setup | Customize | Cases | Fields and clicking on the link for the Status field.
trigger AutoCloseParentCase on Case (after insert, after update) {
Set<Id> parentCaseIds = new Set<Id>();
//Gather up a set of the parent cases.
//We use Set instead of List because Set will prevent dupes.
for (Case c:Trigger.new) {
if (c.ParentId!=null) {
parentCaseIds.add(c.ParentId);
}
}
if (parentCaseIds.size()>0) {
List<Case> parentCases = [Select c.IsClosed,
(Select IsClosed From Cases)
From Case c
Where c.Id in :parentCaseIds];
List<Case> parentCasesToUpdate = new List<Case>();
for (Case parent:parentCases) {
Boolean allChildrenClosed = true;
//Look through each of the children -- if they're all closed,
//then close the parent, otherwise keep it open
for (Case childCase:parent.Cases) {
//If any child case is not closed, allChildrenClosed will flip to false
//and stay that way.
allChildrenClosed = allChildrenClosed && childCase.IsClosed;
}
//If the parent's closed status doesn't match its children's then change it
if (parent.IsClosed!=allChildrenClosed) {
//INSERT VALID STATUSES FOR YOUR ORG HERE
parent.Status = allChildrenClosed?'Closed':'New';
parentCasesToUpdate.add(parent);
}
}
//Now we do our bulk update
if (parentCasesToUpdate.size()>0) {
update parentCasesToUpdate;
}
}
}
Sample Code: Test method for this trigger
It is always important to have test methods that provide automated test coverage for your triggers. The test method here provides 100% test coverage for the trigger given in the above sample code.
As with the trigger above, the sample code here uses statuses of "New" and "Closed," but these statuses may not exist in your org. Before using this code in your org, ensure that the statuses you are using are valid values in your Case Status field.
@isTest
private class TestAutoCloseParentCase {
public static List<Case> createCaseHierarchy(String startingStatus) {
List<Case> cases = new List<Case>();
Case parent = new Case(Subject='foo',Origin='Phone',Status=startingStatus);
insert parent;
cases.add(parent);
Case child1 = new Case(Subject='child1',Origin='Phone',Status=startingStatus,ParentId=parent.Id);
insert child1;
cases.add(child1);
Case child2 = new Case(Subject='child2',Origin='Phone',Status=startingStatus,ParentId=parent.Id);
insert child2;
cases.add(child2);
return cases;
}
public static testMethod void testCloseParent() {
List<Case> cases = createCaseHierarchy('New');
String parentId = null;
//Update one of the children and see that the parent stays open
for (Case c:cases) {
if (c.ParentId!=null) {
parentId = c.ParentId;
c.Status = 'Closed';
update c;
break;
}
}
//Shouldn't be closed yet
Case parent = [select IsClosed from Case where Id=:parentId];
System.assert(parent.IsClosed==false);
for (Case c:cases) {
if (c.ParentId!=null && c.Status!='Closed') {
parentId = c.ParentId;
c.Status = 'Closed';
update c;
break;
}
}
//Now the parent should be closed
parent = [select IsClosed from Case where Id=:parentId];
System.assert(parent.IsClosed==true);
//Now let's reopen one of the children
for (Case c:cases) {
if (c.ParentId!=null && c.Status=='Closed') {
parentId = c.ParentId;
c.Status = 'New';
update c;
break;
}
}
//Now the parent should be reopened again
parent = [select IsClosed from Case where Id=:parentId];
System.assert(parent.IsClosed!=true);
}
}
Further Reading
This is an introduction to a moderately advanced trigger; check out the further reading links below for more information.
- Triggers Section in the Apex Code Language Reference documentation
- Autocreating A Contact From Web To Case Or Email To Case from the Service & Support blog
- Allowing Customer Portal Users To Edit Their Own Contact Information from the Service & Support blog
- An Introduction to Apex from the Core Resources section of Developer Force
- Apex page in the DeveloperForce Wiki
- The Force.com Cookbook downloadable book