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
jacoclockedjacoclocked 

Apex Trigger - Related Object Check

I have a project object and a related task object.  A Project can have N number of tasks.  

 

Rule: Do not allow the closure of a project if it has open tasks.  

 

This should be super simple but Salesforce makes this extremely difficult to accomplish without solid knowledge of Apex programming.  Any help provided would be much appreciated. 

 

*Note* I do not have the luxury of a master-detail relationship in this situation *Note* 

 

 

 

Best Answer chosen by Admin (Salesforce Developers) 
sfdcfoxsfdcfox

Here you go:

 

trigger preventProjectClose on Project__c (before update) {
  set<id> closingprojects = new set<id>();
  for(project__c p:trigger.new)
    if(p.status__c=='Closed'&&trigger.oldmap.get(p.id).status__c!='Closed')
      closingprojects.add(p.id);
  for(AggregateResult ar:[SELECT Project__c Id,COUNT(Id) FROM Task__c WHERE Project__c IN :closingprojects AND Status__c != 'Closed' GROUP BY Project__c])
    trigger.newmap.get( (id)ar.get('Id')).status__c.adderror('You cannot close this project until all tasks are closed.');
}

As with all free code, YMMV, but this is probably the most straight-forward means of accomplishing your goal.

All Answers

sfdcfoxsfdcfox

If tasks are master-detail with projects (i.e. both are custom objects and tasks has a master-detail relationship to projects), you can place a roll up summary field on the project (e.g. COUNT:Tasks) with a condition (e.g. Status not equal to Closed), and then place a validation rule on the project's status field that prevents closure as long as Open Tasks (the rollup summary field) is not zero. No coding required for this if you've got your data model built correctly.

jacoclockedjacoclocked

I don't have the luxury of a master-detail relationship for the problem i'm trying to solve.   Had the same idea as you but fail, definitely need code for this one.  Thanks for the quick reply fox!

 

 

sfdcfoxsfdcfox

Here you go:

 

trigger preventProjectClose on Project__c (before update) {
  set<id> closingprojects = new set<id>();
  for(project__c p:trigger.new)
    if(p.status__c=='Closed'&&trigger.oldmap.get(p.id).status__c!='Closed')
      closingprojects.add(p.id);
  for(AggregateResult ar:[SELECT Project__c Id,COUNT(Id) FROM Task__c WHERE Project__c IN :closingprojects AND Status__c != 'Closed' GROUP BY Project__c])
    trigger.newmap.get( (id)ar.get('Id')).status__c.adderror('You cannot close this project until all tasks are closed.');
}

As with all free code, YMMV, but this is probably the most straight-forward means of accomplishing your goal.

This was selected as the best answer
jacoclockedjacoclocked

I need a bit more help writing my first test class.  Below is the code that i've modified for our environment.  I've also included the start of my test case.   I've got my test class working now. I've pasted the updated class below.  I'm having a problem getting more than 66% code converage.  When I look at my code coverage in my trigger my test class doesn't cover the two lines below in bold.  Please advise.  

 

closingprojects.add(p.id);

 

trigger.newmap.get( (id)ar.get('Id')).pse__Is_Active__c.adderror('This is a customer Project - You cannot set project to inactive until all Milestones are closed.');


How do I test these above lines in my test class?
----

The Trigger

 

// Looks at active projects and checks to see if any milestones are open
// If a milestone is open and a user tries to set a project to inactive then return a message to user


trigger OpenMilestoneTrigger on pse__Proj__c (before update) {


// Creates set of all active projects
set<id> closingprojects = new set<id>();
for(pse__Proj__c p:trigger.new)


// if(p.RecordTypeId == '012Z00000000JWKIA2'&&(p.pse__Stage__c=='Completed'&&trigger.oldmap.get(p.id).pse__Stage__c!='Completed'))
// sets the RecordTypeID for the Customer record

if(p.RecordTypeId == '012Z00000000JWKIA2'&&(p.pse__Is_Active__c == false && trigger.oldmap.get(p.id).pse__Is_Active__c == true))


// Adds active projects to the closingprojects set
closingprojects.add(p.id);


// Checks to see if there are any open milestones within the closing projects set
for(AggregateResult ar:[SELECT pse__Project__c Id,COUNT(Id) FROM pse__Milestone__c
WHERE pse__Project__c IN :closingprojects AND pse__Status__c != 'Approved' GROUP BY pse__Project__c])

// If there are open milestones then add them to the newmap and send error to user
trigger.newmap.get( (id)ar.get('Id')).pse__Stage__c.adderror('This is a customer Project - You cannot set project to inactive until all Milestones are closed.');


}

 

----

The Test Class

@isTest
private class OpenMilestoneTriggerTestClass {

private static testMethod void validateOpenMilestoneTriggerNegative() {

//Setup the Project Record
pse__Proj__c p = new pse__Proj__c();
p.RecordTypeId = '012Z00000000JWKIA2';
p.Name='TestProject';
p.pse__Region__c = 'a0wZ0000000WMArIAO';
p.pse__Is_Active__c = true;

insert p;

//Setup the Milestone Record
pse__Milestone__c m = new pse__Milestone__c();
m.Name = 'TestMileStone';
m.pse__Project__c = p.ID; //[Select pse__Proj__c.Name from pse__Proj__c where pse__Proj__c.Name = :pse__Proj__c.id];
m.pse__Milestone_Amount__c = 0;
m.pse__Target_Date__c = Date.today();

insert m;

Test.startTest();

//Set the project status to inactive
try
{
p.pse__Is_Active__c = false;

}
Catch (System.DMLException e)
{
System.assert(e.getMessage().contains('Project cannot be closed with Open Milestones'));
}
Test.stopTest();
}
private static testMethod void validateOpenMilestoneTriggerPositive() {

//Setup the TestProject2 Record
pse__Proj__c p = new pse__Proj__c();
p.RecordTypeId = '012Z00000000JWKIA2';
p.Name='TestProject2';
p.pse__Region__c = 'a0wZ0000000WMArIAO';
p.pse__Is_Active__c = true;

insert p;

//Setup the Milestone2 Record
pse__Milestone__c m = new pse__Milestone__c();
m.Name = 'TestMileStone2';
m.pse__Project__c = p.ID;
m.pse__Milestone_Amount__c = 0;
m.pse__Target_Date__c = Date.today();
m.pse__Actual_Date__c = Date.today();

insert m;

Test.startTest();
//Set the milestone status to approved and the project to inactive
try
{
m.pse__Status__c = 'Approved';
p.pse__Is_Active__c = false;
}
Catch (System.DMLException e)
{
System.assert(e.getMessage().contains('Project can be closed if there are no Open Milestones'));
}
Test.stopTest();
}
private static testMethod void validateOpenMilestoneTriggerClosingProjects() {


//Setup the Project Record
pse__Proj__c p = new pse__Proj__c();
p.RecordTypeId = '012Z00000000JWKIA2';
p.Name='TestProject3';
p.pse__Region__c = 'a0wZ0000000WMArIAO';
p.pse__Is_Active__c = false;

pse__Proj__c p1 = new pse__Proj__c();
p1.RecordTypeId = '012Z00000000JWKIA2';
p1.Name='TestProject4';
p1.pse__Region__c = 'a0wZ0000000WMArIAO';
p1.pse__Is_Active__c = true;

insert p;
insert p1;
// Creates set of all active projects
set<id> closingprojects = new set<id>();

// sets the RecordTypeID for the Customer record and adds previous value to oldmap
if(p.RecordTypeId == '012Z00000000JWKIA2'&&(p.pse__Is_Active__c == false))
// Adds active projects to the closingprojects set
closingprojects.add(p.id);

Test.StartTest();
Try
{
if(p.RecordTypeId == '012Z00000000JWKIA2'&&(p.pse__Is_Active__c == false))
closingprojects.add(p.id);
}
Catch (System.DMLException e)
{
System.assertEquals(closingprojects.size(),1);
}
Test.StopTest();
}
}