Salesforce How You need to update a related child record whenever a parent record’s status changes, but only if the status is “Closed Won.” How would you design this in Apex?
•46 min read

best-practice solution for updating child records when a parent's status changes to "Closed Won":
In this article, we will explore best practices in Trigger design for Salesforce Apex.
Understanding Trigger Design
Understanding the principles of Trigger design is essential for effective record management.
This section delves into the core aspects of Trigger design.
Trigger Handler Pattern
apex
// Trigger
trigger OpportunityTrigger on Opportunity (after update) {
OpportunityTriggerHandler.handleAfterUpdate(Trigger.new, Trigger.oldMap);
}JavaScriptHandler Class
apex
public class OpportunityTriggerHandler {
public static void handleAfterUpdate(List<Opportunity> newOpps, Map<Id, Opportunity> oldMap) {
Set<Id> closedWonOppIds = new Set<Id>();
// Collect only opportunities that changed to Closed Won
for (Opportunity opp : newOpps) {
Opportunity oldOpp = oldMap.get(opp.Id);
if (opp.StageName == 'Closed Won' &&
opp.StageName != oldOpp.StageName) {
closedWonOppIds.add(opp.Id);
}
}
if (!closedWonOppIds.isEmpty()) {
updateRelatedOpportunityLineItems(closedWonOppIds);
}
}
private static void updateRelatedOpportunityLineItems(Set<Id> oppIds) {
List<OpportunityLineItem> lineItemsToUpdate = new List<OpportunityLineItem>();
// Query child records
for (OpportunityLineItem oli : [
SELECT Id, Status__c, OpportunityId
FROM OpportunityLineItem
WHERE OpportunityId IN :oppIds
]) {
oli.Status__c = 'Active';
oli.Last_Updated_Date__c = System.today();
lineItemsToUpdate.add(oli);
}
// Bulk update with error handling
if (!lineItemsToUpdate.isEmpty()) {
try {
update lineItemsToUpdate;
} catch (DmlException e) {
System.debug('Error updating line items: ' + e.getMessage());
// Add error to parent record if needed
for (Integer i = 0; i < e.getNumDml(); i++) {
System.debug('Failed record: ' + e.getDmlId(i));
}
}
}
}
}JavaScriptKey Design Considerations
1. After Update Context
- Use
after updatesince you're modifying related records - Parent record is already committed to database
2. Bulkification
apex
// Collect IDs in a Set for bulk processing
Set<Id> closedWonOppIds = new Set<Id>();JavaScript3. Status Change Detection
apex
// Only process when status CHANGES to Closed Won
if (opp.StageName == 'Closed Won' &&
opp.StageName != oldOpp.StageName)
4. SOQL Best Practice
apex
// Single SOQL query outside loop
for (OpportunityLineItem oli : [SELECT...]) {
// Process records
}
With Bypass Mechanism (for your integration scenarios)
apex
public class OpportunityTriggerHandler {
@TestVisible private static Boolean bypassTrigger = false;
public static void handleAfterUpdate(List<Opportunity> newOpps, Map<Id, Opportunity> oldMap) {
// Check bypass
if (bypassTrigger || TriggerBypassManager.isBypassed('Opportunity')) {
return;
}
// Rest of logic...
}
}JavaScriptTest Class
apex
@isTest
private class OpportunityTriggerHandlerTest {
@testSetup
static void setup() {
// Create test data
Opportunity opp = new Opportunity(
Name = 'Test Opp',
StageName = 'Prospecting',
CloseDate = System.today().addDays(30)
);
insert opp;
OpportunityLineItem oli = new OpportunityLineItem(
OpportunityId = opp.Id,
PricebookEntryId = /* your price book entry */,
Quantity = 1,
UnitPrice = 100,
Status__c = 'Pending'
);
insert oli;
}
@isTest
static void testClosedWonUpdatesChildren() {
Opportunity opp = [SELECT Id, StageName FROM Opportunity LIMIT 1];
Test.startTest();
opp.StageName = 'Closed Won';
update opp;
Test.stopTest();
OpportunityLineItem oli = [SELECT Status__c FROM OpportunityLineItem LIMIT 1];
System.assertEquals('Active', oli.Status__c, 'Child record should be updated');
}
@isTest
static void testBulkUpdate() {
// Test with 200 opportunities
List<Opportunity> opps = [SELECT Id FROM Opportunity];
Test.startTest();
for (Opportunity opp : opps) {
opp.StageName = 'Closed Won';
}
update opps;
Test.stopTest();
// Assert all children updated
}
}JavaScriptAlternative: Asynchronous Processing
For heavy child record volumes:
apex
private static void updateRelatedOpportunityLineItems(Set<Id> oppIds) {
if (!System.isBatch() && !System.isFuture()) {
updateChildRecordsAsync(oppIds);
}
}
@future
private static void updateChildRecordsAsync(Set<Id> oppIds) {
// Same update logic
}JavaScriptThis design handles your typical Salesforce scenarios while maintaining governor limits and following your platform architect best practices!
Related Posts

How to Automatically create a follow-up Task when a Lead is converted

How You need to update a related child record whenever a parent record’s status changes, but only if the status is “Closed Won.” How would you design this in Apex?
