function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Carrlos BoydCarrlos Boyd 

I need help writing a trigger to prevent a record from closing if related records are open

I need help writing a trigger to prevent Obj1 records from being cancelled (Status__c = "Cancelled") or completed (Status__c = "Completed") if there are open activities/tasks that are not completed or cancelled, or if related records from Obj2 or Obj3 are still open. Obj1 may have mutiple related records from Obj2 and Obj3. The look-up field on Obj2 and Obj3 is called 'Obj1__c'.
Best Answer chosen by Carrlos Boyd
Abdul KhatriAbdul Khatri
Here is the code. I have the Object Name as per your specifications but that may drastically change. I hope logic works as per your need.

Please note, since I have faked the Obj, the way you have provided. You may need to change those before run it in your org, specially with the sub SOQL. Please confirm how it go.
 
trigger Obj1Trigger on Obj1__c (before update) {
    
    Map<Id, Obj1__c> availableObj1Map = new Map<Id, Obj1__c>();
    Map<Id, Task> taskObj1IdMap = new Map<Id, Task>();
    
    for(Obj1__c obj1 : trigger.new) {
        
        if(obj1.Status__c != trigger.oldMap.get(obj1.Id).Status__c && (obj1.Status__c = 'Cancelled' || obj1.Status__c = 'Completed'))
            availableObj1Map.put(obj1.Id, obj1);
    }
    
    if(availableObj1Map.isEmpty()) return;
    
    for (List<Task> taskList : [SELECT Id, obj1__c FROM Task WHERE obj1__c IN :availableObj1Map.KeySet() AND Status NOT IN ('Cancelled', 'Completed')]) {
        
        for (Task task : taskList) {
            
            if(!taskObj1IdMap.ContainsKey(task.Obj1__c))
                taskObj1IdMap.put(task.obj1__c, task);
        }
    }

    for ( List<Obj1__c> tempList : [SELECT Status__c, (SELECT Id FROM Obj2__r WHERE Status__c = 'Open'), (SELECT Id FROM Obj3__r WHERE Status__c = 'Open') FROM Obj1__c]) {
        
        for (Obj1__c obj1Rec : tempList) {
            
            if(taskObj1IdMap.containsKey(obj1Rec.Id))
                availableObj1Map.get(obj1Rec.Id).addError('Open Task exists so cannot Cancel or Complete');
            
            if(obj1Rec.obj2__r != null || obj1Rec.obj3__r != null)
                availableObj1Map.get(obj1Rec.Id).addError('Related Object are open so cannot Cancel or Complete');                
        }
        
    }    
    
}

 

All Answers

Abdul KhatriAbdul Khatri
Here is the code. I have the Object Name as per your specifications but that may drastically change. I hope logic works as per your need.

Please note, since I have faked the Obj, the way you have provided. You may need to change those before run it in your org, specially with the sub SOQL. Please confirm how it go.
 
trigger Obj1Trigger on Obj1__c (before update) {
    
    Map<Id, Obj1__c> availableObj1Map = new Map<Id, Obj1__c>();
    Map<Id, Task> taskObj1IdMap = new Map<Id, Task>();
    
    for(Obj1__c obj1 : trigger.new) {
        
        if(obj1.Status__c != trigger.oldMap.get(obj1.Id).Status__c && (obj1.Status__c = 'Cancelled' || obj1.Status__c = 'Completed'))
            availableObj1Map.put(obj1.Id, obj1);
    }
    
    if(availableObj1Map.isEmpty()) return;
    
    for (List<Task> taskList : [SELECT Id, obj1__c FROM Task WHERE obj1__c IN :availableObj1Map.KeySet() AND Status NOT IN ('Cancelled', 'Completed')]) {
        
        for (Task task : taskList) {
            
            if(!taskObj1IdMap.ContainsKey(task.Obj1__c))
                taskObj1IdMap.put(task.obj1__c, task);
        }
    }

    for ( List<Obj1__c> tempList : [SELECT Status__c, (SELECT Id FROM Obj2__r WHERE Status__c = 'Open'), (SELECT Id FROM Obj3__r WHERE Status__c = 'Open') FROM Obj1__c]) {
        
        for (Obj1__c obj1Rec : tempList) {
            
            if(taskObj1IdMap.containsKey(obj1Rec.Id))
                availableObj1Map.get(obj1Rec.Id).addError('Open Task exists so cannot Cancel or Complete');
            
            if(obj1Rec.obj2__r != null || obj1Rec.obj3__r != null)
                availableObj1Map.get(obj1Rec.Id).addError('Related Object are open so cannot Cancel or Complete');                
        }
        
    }    
    
}

 
This was selected as the best answer
Carrlos BoydCarrlos Boyd

Thank you for the help Abdul. Can you please tell me how to add options for the open statuses of my related records? The three statuses are actually "In Progress" and "New".

for ( List<Obj1__c> tempList : [SELECT Status__c, (SELECT Id FROM Obj2__r WHERE Status__c = 'Open'), (SELECT Id FROM Obj3__r WHERE Status__c = 'Open') FROM Obj1__c]) {

 
Abdul KhatriAbdul Khatri
for ( List<Obj1__c> tempList : [SELECT Status__c, (SELECT Id FROM Obj2__r WHERE Status__c IN ('New','In Progress')), (SELECT Id FROM Obj3__r WHERE Status__c IN ('New','In Progress')) FROM Obj1__c]) {

 
Carrlos BoydCarrlos Boyd
I'm getting this error message when saving:
Error Error: Compile Error: Expression cannot be assigned at line 8 column 119

 
Abdul KhatriAbdul Khatri
If you can share the code, it will help me understand.
Carrlos BoydCarrlos Boyd
This is my line 8:
if(OnboardingForm.Status__c != trigger.oldMap.get(OnboardingForm.Id).Status__c && (OnboardingForm.Status__c = 'Cancelled' || OnboardingForm.Status__c = 'Completed'))
My Object1 is called
  • Name: Onboarding Form
  • Plural name: Onboarding Forms
  • API name: Onboarding_Form__c
Abdul KhatriAbdul Khatri
if(OnboardingForm.Status__c != trigger.oldMap.get(OnboardingForm.Id).Status__c && (OnboardingForm.Status__c == 'Cancelled' || OnboardingForm.Status__c == 'Completed'))

 
Carrlos BoydCarrlos Boyd
Error: Compile Error:
SELECT Id, Onboarding_Form__c FROM Task WHERE
 ^
ERROR at Row:1:Column:12
No such column 'Onboarding_Form__c' on entity 'Task'. If you are attempting to use a custom field, be sure to append the '__c' after the custom field name. Please reference your WSDL or the describe call for the appropriate names. at line 14 column 32
The task uses the "related to" field (WhatId) to relate the task to the object record. Could that be the issue here?
 
Carrlos BoydCarrlos Boyd
I got it to work by making the following changes:

Line 14 old code that generates error:
for (List<Task> taskList : [SELECT Id, Onboarding_Form__c FROM Task WHERE Onboarding_Form__c IN:availableOnboardingFormMap.KeySet() AND Status NOT IN ('Cancelled', 'Completed')])

Line 14 with new code:
for (List<Task> taskList : [SELECT Id, WhatId FROM Task WHERE WhatId IN:availableOnboardingFormMap.KeySet() AND Status NOT IN ('Cancelled', 'Completed')])

Let me know if this is ok to do please.
Abdul KhatriAbdul Khatri
Well it was complaining about the following Custom Field which you need to see if you have it or not as per your requirements and if you need it later and why?
 
Onboarding_Form__c

 
Carrlos BoydCarrlos Boyd
I have that custom field on Obj2 and Ojb3 but it is not on the Task object. The task utilizes the "Related to" (WhatId) field instead.
Abdul KhatriAbdul Khatri
Therefore, how do you want to use that field. Let me know
Carrlos BoydCarrlos Boyd
I tested the trigger with open related items and no open related items and this is the Line 31 error I got each time:
availableOnboardingFormMap.get(OnboardingFormRec.Id).addError('Related Object are open so cannot Cancel or Complete');