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
AchtungAchtung 

apex trigger before update

Hello! I'm creating a before update Trigger that won't allow changing the agreement_start_date and agreement_end_date of an agreement when the jobs associated with it are not within the duration of the new dates.

Background:
- An agreement needs an agreement_start_date and agreement_end_date as its duration. 
- An agreement can have multiple Jobs assigned to it and every Job only has one service agreement assigned.
- Every Job has a job_start_date and job_end_date and is within the duration of the agreement_start_date and agreement_end_date.

Agreement_ABC
agreement_start_date = June 1, 2018
agreement_end_date = July 31, 2018

1. Job_01: 
job_start_date = July 1, 2018
job_end_date = July 15, 2018

2. Job_02:
job_start_date = June 2, 2018
job_end_date = June 30, 2018

Agreement_ABC New Dates
agreement_start_date = June 5, 2018
agreement_end_date = July 30, 2018
---> display an error disallowing to save as the new start date conflicts with Job_02 job_start_date

Scenario:
I want to update the agreement_start_date and agreement_end_date with new date values, however, a Trigger will not allow them to save the new dates if jobs associated to it are not within the new date duration.

How can I maximise the use of Set, List, and Map to develop the Trigger? Thanks.
Best Answer chosen by Achtung
Abhishek BansalAbhishek Bansal
Hi Danilo,

As per your requirement I have written the trigger below:
trigger checkDates on Agreement_ABC__c(before update) {
	
	Set<Id> setOfAgreements = new Set<Id>();
	for(Agreement_ABC__c agreement : trigger.new){
		//This conditon will check if start date or end date is updated on Agreement
		if(trigger.oldMap.get(agreement.id).agreement_start_date__c != agreement.agreement_start_date__c || trigger.oldMap.get(agreement.id).agreement_end_date__c != agreement.agreement_end_date__c) {
			//This set will store ids of only those agreements where start date or end date is updated
			setOfAgreements.add(agreement.id);
		}
	}
	
	//If there is any agreement whose start date or end date is updated then we will execute this block
	if(setOfAgreements.size() > 0) {
		
		//This will contains the ids of all the agreements which consists of an error.
		Set<Id> setOfAgreementWithError = new Set<Id>();
		
		Date agreementStartDate;
		Date agreementEndDate;
		Integer counterForStartDate;
		Integer counterForEndDate;
		
		//This will query all the agreements with their respective Jobs
		//Replcae Jobs__r with child relationship name
		for(Agreement_ABC__c agreement : [Select Id, (Select job_start_date__c, job_end_date__c from Jobs__r ) from Agreement_ABC__c where Id IN :trigger.new]) {
			
			//Fetch updated start date and  end date from trigger.new map
			agreementStartDate = trigger.newMap.get(agreement.id).agreement_start_date__c;
			agreementEndDate = trigger.newMap.get(agreement.id).agreement_end_date__c;
			counterForStartDate = 0;
			counterForEndDate = 0;
			
			for(Job__c job : agreement.Jobs__r) {
				if(agreementStartDate <= job.job_start_date__c) {
					counterForStartDate++;
				}
				if(agreementEndDate >= job.job_end_date__c) {
					counterForEndDate++;
				}
			}
			
			if(agreement.Jobs__r.size() > 0 && (counterForStartDate != agreement.Jobs__r.size() || counterForEndDate != agreement.Jobs__r.size()) ) {
				setOfAgreementWithError.add(agreement.id);
			}
		}
		
		for(Agreement_ABC__c agreement : trigger.new) {
			if(setOfAgreementWithError.containsKey(agreement.id)) {
				agreement.addError('Enter your Error Message here');
			}
		}
	}
}
//Please take care of the API names for all the field and objects.
Please let me know if you face any issue with this and if you need any further help or information on this.

Thanks,
Abhishek Bansal.

All Answers

Abhishek BansalAbhishek Bansal
Hi Danilo,

As per your requirement I have written the trigger below:
trigger checkDates on Agreement_ABC__c(before update) {
	
	Set<Id> setOfAgreements = new Set<Id>();
	for(Agreement_ABC__c agreement : trigger.new){
		//This conditon will check if start date or end date is updated on Agreement
		if(trigger.oldMap.get(agreement.id).agreement_start_date__c != agreement.agreement_start_date__c || trigger.oldMap.get(agreement.id).agreement_end_date__c != agreement.agreement_end_date__c) {
			//This set will store ids of only those agreements where start date or end date is updated
			setOfAgreements.add(agreement.id);
		}
	}
	
	//If there is any agreement whose start date or end date is updated then we will execute this block
	if(setOfAgreements.size() > 0) {
		
		//This will contains the ids of all the agreements which consists of an error.
		Set<Id> setOfAgreementWithError = new Set<Id>();
		
		Date agreementStartDate;
		Date agreementEndDate;
		Integer counterForStartDate;
		Integer counterForEndDate;
		
		//This will query all the agreements with their respective Jobs
		//Replcae Jobs__r with child relationship name
		for(Agreement_ABC__c agreement : [Select Id, (Select job_start_date__c, job_end_date__c from Jobs__r ) from Agreement_ABC__c where Id IN :trigger.new]) {
			
			//Fetch updated start date and  end date from trigger.new map
			agreementStartDate = trigger.newMap.get(agreement.id).agreement_start_date__c;
			agreementEndDate = trigger.newMap.get(agreement.id).agreement_end_date__c;
			counterForStartDate = 0;
			counterForEndDate = 0;
			
			for(Job__c job : agreement.Jobs__r) {
				if(agreementStartDate <= job.job_start_date__c) {
					counterForStartDate++;
				}
				if(agreementEndDate >= job.job_end_date__c) {
					counterForEndDate++;
				}
			}
			
			if(agreement.Jobs__r.size() > 0 && (counterForStartDate != agreement.Jobs__r.size() || counterForEndDate != agreement.Jobs__r.size()) ) {
				setOfAgreementWithError.add(agreement.id);
			}
		}
		
		for(Agreement_ABC__c agreement : trigger.new) {
			if(setOfAgreementWithError.containsKey(agreement.id)) {
				agreement.addError('Enter your Error Message here');
			}
		}
	}
}
//Please take care of the API names for all the field and objects.
Please let me know if you face any issue with this and if you need any further help or information on this.

Thanks,
Abhishek Bansal.
This was selected as the best answer
AchtungAchtung
Hi Abhishek. Thanks a lot for the help and I'll try it using the appropriate API names and let you know of the outcome shortly.
AchtungAchtung
Hi Abhishek, I forgot to mention.. There is only a lookup field from Job to Agreement.. but not the other way around. This may potentially conflict with this part of your code:
 
​for(Agreement_ABC__c agreement : [Select Id, (Select job_start_date__c, job_end_date__cfrom Jobs__r ) from Agreement_ABC__c where Id IN :trigger.new])

In addition, with several FOR Loops included, will it still adhere with bulkify code (thus the intended use of Map and List)? 

Thanks again.
Abhishek BansalAbhishek Bansal

Hi Danilo,

As per the information given by you i.e. :
Background:
- An agreement needs an agreement_start_date and agreement_end_date as its duration. 
- An agreement can have multiple Jobs assigned to it and every Job only has one service agreement assigned.


This means that the Agreement is the parent object and it can have multiple Jobs as childs. If this is correct then there will be no conlficts in the code provided by me. If this  is not the case, then please help me to understand  the relationship again.

Yes, it will work for the bulk records as well. This code contains the minimum use of for loops, maps and list. Let me know if you have any idea for a better solution.

Thanks,
Abhishek Bansal.

AchtungAchtung
Hi Abishek,

Both the Agreement and Job are child objects and they are linked to the Contact parent object. When booking for a Job, it is done via the Contact object and an Agreement needs to be selected and associated with the Job. In that sense, I think the Jobs_r field relationship may not work in the query.

Sorry, I forgot to mention it earlier. Hope this makes it better.
 
Abhishek BansalAbhishek Bansal

Hi Danilo,

That means there is no direct relationship between Agreement and Job object. They are related to each other via Junction Object i.e. Contact.
Which means that on both the Agreement and Job object you have a lookup field of Contact? There is no Agreemnet lookup on Jobs or vice-versa? Is this right? Please confirm.

If this is the right information then trigger needs to be modified and it will get more and more complicated.

Thanks,
Abhishek Bansal.

AchtungAchtung
This is what I initially did but it only checks that in a Job is connected to the Agreement, the agreement_end_date value can't be changed:
 
Set <Id> saIds = new Set <Id>();
        for (Agreement s : Trigger.new)
            saIds.add(s.ID);
       
List<Job> jobsWithAgreement = new List<Job>();
        
jobsWithAgreement= [SELECT ID, 
                      AgreementID,, 
                      Contact__c,
                      job_start_date,
                      job_end_date
                      FROM Job 
                      WHERE AgreementID IN :saIds];  
       
Map<Id, Job> jobs = new Map<Id, Job>();
        for (Job j : jobsWithAgreement){
            jobs.put(j.Service_Agreement__c, j);           
        } 
                    
for (Agreement sa : Trigger.new){
            if (jobs.containskey(sa.Id)){                
                if (agreement_end_date != Trigger.oldMap.get(sa.Id).agreement_end_date) 
                {
                    sa.addError('Agreement is currently associated with a Job. Plan Dates cannot be updated.');
                }
            }
        }

However, now I wanted to dig deeper into every job associated with the agreement and check if the new agreement_end_date will not conflict to all the job_end_dates of its corresponding jobs. 
AchtungAchtung
Hi Abhishek,

Please refer below:

That means there is no direct relationship between Agreement and Job object.
- both are Child object of the Contact object

They are related to each other via Junction Object i.e. Contact.
- Yep

Which means that on both the Agreement and Job object you have a lookup field of Contact?
- Yep

There is no Agreemnet lookup on Jobs or vice-versa? Is this right? Please confirm.
- There is an Agreement lookup on Job
- There is no Job lookup on Agreement
Abhishek BansalAbhishek Bansal
Hi Danilo,

In your code I can see that you have a Service_Agreement__c field on the Job object which will hold the Id fo the agreement. This field will act as the relationship field and make the Job object as child of the Agreement. I think you have the proper relationship maintained and trigger provided by me will work fine for you.

If you open the custom field Service_Agreement__c in your org. You can find the child relationship name in the bottom right corner. You can use that name in the query. Please let me know if you still need any further hekp on this. You can also contact me on:
Gmail: abhibansal2790@gmail.com
Skype: abhishek.bansal2790

Thanks,
Abhishek bansal.
AchtungAchtung
Hi Abhishek,

Erratum: Service_Agreement__c is same as AgreementID
AchtungAchtung

 
Hi,
I'm revising my logic in such a way that only Agreements where either their start date or end date is changed will be stored in the Trigger.new set:
 
Set <Id> saIds = new Set <Id>();
     for (Agreement s : Trigger.new)
     saIds.add(s.ID);


And also, to make the SOQL more efficient, I only want to search for jobs where their agreement Id is found the set above and their job start or end date violates the new agreement start or end date:
 
jobsWithAgreement= [SELECT ID,
      AgreementID,
      Contact__c,
      job_start_date,
      job_end_date
      FROM Job
      WHERE AgreementID IN :saIds];

Can you help revise your code for this? Thanks.
 
Abhishek BansalAbhishek Bansal
Hi Danilo,

The trigger provided be me does the same thing. First it checks whether start date or end date is changed and if yes then it stores the id of that agreement in a set. Now based on the ids presnt in the set the agreement records are queried. I don't know what help are you asking here. Please clarify.

Thanks,
Abhishek Bansal.