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
Snita LalSnita Lal 

Adding validation to an 'Insert Trigger' for single record to be created

Hi,

We've been working on a trigger which creates a contract upon an Opportunity being complete and 'Closed Won'. We've managed to get the record to create and copy over important info, although we can't manage to figure the validation logic. Granted we have little apex knowledge and therefore we've attempted to put most of the logic outside the trigger and within a workflowed checkbox on the Opportunity.

However, this don't eliminate the duplication of records if the user edits a Won opportunity. I know the better versed Salesforce developer would have the list check the record doesn't exist in the first place before the trigger even starts and therefore we're reaching out hoping you guys could support us in finishing this piece of work.

Any help or advice would be gratefully appreciated. 
trigger CreateContract on Opportunity (after insert, after update) {

    List<Contract> conttoinsert = new List<Contract>();

    for (Opportunity opp : Trigger.new) {

        if (opp.StageName == 'Closed Won' && opp.RecordTypeID == '012N00000004pSi' && opp.Record_Complete__c == TRUE) {
            Contract con = new Contract();
            con.Name                  = opp.name;
            con.Account               = opp.Account;
            con.CurrencyIsoCode       = opp.CurrencyIsoCode;
            con.AccountId             = opp.AccountId;
            con.StartDate             = opp.Service_Start_Date__c;
            con.Contract_Value__c     = opp.Total_Performance__c;
            con.Description           = opp.Opportunity_Description__c;
            con.Sales_Lead__c         = opp.OwnerID;
            conttoinsert.add(con);
        } 
    }

    if ( !conttoinsert.isEmpty()) {
        insert conttoinsert;

}
}

 
Best Answer chosen by Snita Lal
Tavva Sai KrishnaTavva Sai Krishna
Hi Snita,

Yes, please check with the modified code below.
 
trigger CreateContract on Opportunity (after insert, after update) {

    List<Contract> conttoinsert = new List<Contract>();
	
	List<opportunity> LstUpdOpp = new List<opportunity>();
	
	Map<String,opportunity> mapOpp = new Map<String,opportunity> ();
	
	if(Trigger.IsInsert || Trigger.Isupdate)
	{
		//Intialising mapOpp
		for(opportunity opp: Trigger.new)
		{
			mapOpp.put(opp.name,opp);
		}
		
		if(Trigger.Isupdate)
		{
			List<opportunity> lstOppNoCont = new List <opportunity>();
			//Removing duplicate creation of contract records.
			for(opportunity opp: Trigger.new)
			{
				if(opp.contract == null)
					lstOppNoCont.add(opp);// list holds the opp records for which contract records are not created.
			}
			
			//if the opportunity doesnt have contract records then will create new contract
			if(!lstOppNoCont.isEmpty())
			{
				// Fetching the contract records to be insert for valid opportunities.
				conttoinsert.addall(newContract(lstOppNoCont));
			}
		}
			
		//It create the contract records for eligible opportunity
		if(Trigger.IsInsert)
		{
			// Fetching the contract records to be insert for valid opportunities.
			conttoinsert.addall(newContract(Trigger.new));
		}
		
		//Create contract records if the list is not empty. also update the opportunity records with the new contract records.
		if ( !conttoinsert.isEmpty()) {
			
			//create contracts.
			insert conttoinsert;
			
			//Update Opportunity with the contract record.
			for(contract Rec: conttoinsert)
			{
				Opportunity oppRec = new Opportunity();
				oppRec = mapOpp.get(Rec.name);
				oppRec.contractid = rec.id;
				oppRec.id = oppRec.id;
				LstUpdOpp.add(oppRec);
				
			}
			
			if(!LstUpdOpp.isEmpty()){
				update LstUpdOpp;
			}
        }
	}
	
	//Create the contract records for eligible opportunity and returns list of contract.
	public list <contract> newContract (list<opportunity> Lstopp)
	{
		List<Contract> lstNewCon = new List<Contract>();
		
		for (Opportunity opp : Lstopp) {
		
			if (opp.StageName == 'Closed Won' && opp.RecordTypeID == '012N00000004pSi' && opp.Record_Complete__c == TRUE) {
				Contract con = new Contract();
				con.Name                  = opp.name;
				con.Account               = opp.Account;
				con.CurrencyIsoCode       = opp.CurrencyIsoCode;
				con.AccountId             = opp.AccountId;
				con.StartDate             = opp.Service_Start_Date__c;
				con.Contract_Value__c     = opp.Total_Performance__c;
				con.Description           = opp.Opportunity_Description__c;
				con.Sales_Lead__c         = opp.OwnerID;
				lstNewCon.add(con);
			} 
		}
		return lstNewCon;
	}
	
	
	
    
}

we need to define the variable outside the for loop. I have modified the same. now you can test. if you still get any errors regarding the fields such as "Invalid field CurrencyIsoCode" . just comment those llines which are defined in the method and try to save. these errors may get as your org doesnt enable the currency functionality / the object doesnt have the fields with the same api names.

for your reference the variable name  starts with  small letter "L" not with 1 . (lstNewCon).

Still if you face any errors give the details of the error.

Waiting for your reply,

Regards,
Sai Krishna Tavva.

 

All Answers

Nagendra ChinchinadaNagendra Chinchinada
Add Trigger.oldMap.get(opp.id).StageName <> opp.StageName && ​in if condition. Now new Contact will be cretaed if StageName is changed to Closed Won from any other starge. If Opportunity having stageName Closed Won is edited, then it will not get into if loop and contact won't be created. 
Is this the requirement you need ?. Plese let me know.
 
trigger CreateContract on Opportunity (after insert, after update) {

    List<Contract> conttoinsert = new List<Contract>();

    for (Opportunity opp : Trigger.new) {

        if (opp.StageName == 'Closed Won' && Trigger.oldMap.get(opp.id).StageName <> opp.StageName && opp.RecordTypeID == '012N00000004pSi' && opp.Record_Complete__c == TRUE) {
            Contract con = new Contract();
            con.Name                  = opp.name;
            con.Account               = opp.Account;
            con.CurrencyIsoCode       = opp.CurrencyIsoCode;
            con.AccountId             = opp.AccountId;
            con.StartDate             = opp.Service_Start_Date__c;
            con.Contract_Value__c     = opp.Total_Performance__c;
            con.Description           = opp.Opportunity_Description__c;
            con.Sales_Lead__c         = opp.OwnerID;
            conttoinsert.add(con);
        } 
    }

    if ( !conttoinsert.isEmpty()) {
        insert conttoinsert;

}
}

 
Snita LalSnita Lal

Thanks for the quick response Nagendra - We've attempted to use your additional piece of code but unfortunately upon saving the record we hit the following error:

Error: Invalid Data. 
Review all error messages below to correct your data.
Apex trigger CreateContract caused an unexpected exception, contact your administrator: CreateContract: execution of AfterInsert caused by: System.NullPointerException: Attempt to de-reference a null object: Trigger.CreateContract: line 7, column 1

Please can you check this for us?

Thanks,

Snita

Nagendra ChinchinadaNagendra Chinchinada
Here is the fix,
 
trigger CreateContract on Opportunity (after insert, after update) {

    List<Contract> conttoinsert = new List<Contract>();

    for (Opportunity opp : Trigger.new) {

        if ( (trigger.isUpdate  && opp.StageName == 'Closed Won' && Trigger.oldMap <> null && Trigger.oldMap.get(opp.id).StageName <> opp.StageName && opp.RecordTypeID == '012N00000004pSi' && opp.Record_Complete__c == TRUE) ) // For Update 
		|| (trigger.isInsert  && opp.StageName == 'Closed Won' && opp.RecordTypeID == '012N00000004pSi' && opp.Record_Complete__c == TRUE) ) // For Insert
		{
            Contract con = new Contract();
            con.Name                  = opp.name;
            con.Account               = opp.Account;
            con.CurrencyIsoCode       = opp.CurrencyIsoCode;
            con.AccountId             = opp.AccountId;
            con.StartDate             = opp.Service_Start_Date__c;
            con.Contract_Value__c     = opp.Total_Performance__c;
            con.Description           = opp.Opportunity_Description__c;
            con.Sales_Lead__c         = opp.OwnerID;
            conttoinsert.add(con);
        } 
    }

    if ( !conttoinsert.isEmpty()) {
        insert conttoinsert;

}
}

 
Nagendra ChinchinadaNagendra Chinchinada
Hardcoding Ids in Apex is not a good practice. Instead, use Schema.SObjectType method to query the Recordtype Id dynamically and use it as given in below code.


trigger CreateContract on Opportunity (after insert, after update) {

    List<Contract> conttoinsert = new List<Contract>();
    
    Id RCTypeId = Schema.SObjectType.Opportunity.getRecordTypeInfosByName().get('Replace your Recordtype name here ').getRecordTypeId();

    for (Opportunity opp : Trigger.new) {

        if ( (trigger.isUpdate  && opp.StageName == 'Closed Won' && Trigger.oldMap <> null && Trigger.oldMap.get(opp.id).StageName <> opp.StageName && opp.RecordTypeID == RCTypeId && opp.Record_Complete__c == TRUE) ) // For Update 
        || (trigger.isInsert  && opp.StageName == 'Closed Won' && opp.RecordTypeID == RCTypeId && opp.Record_Complete__c == TRUE) ) // For Insert
        {
            Contract con = new Contract();
            con.Name                  = opp.name;
            con.Account               = opp.Account;
            con.CurrencyIsoCode       = opp.CurrencyIsoCode;
            con.AccountId             = opp.AccountId;
            con.StartDate             = opp.Service_Start_Date__c;
            con.Contract_Value__c     = opp.Total_Performance__c;
            con.Description           = opp.Opportunity_Description__c;
            con.Sales_Lead__c         = opp.OwnerID;
            conttoinsert.add(con);
        } 
    }

    if ( !conttoinsert.isEmpty()) {
        insert conttoinsert;

}
}
Snita LalSnita Lal
Thanks Nagendra we've copied in the newest version of the code and had to remove the second bracket before the For Update annotation to get it to save. But I think we've broken the OR as the record is no longer inserting but we have no errors upon saving the opportunity.
Snita LalSnita Lal
Hi Nagendra,

Appreciate this and more than happy to do this once the issue has been resolved. The above message states that we had to slightly tweak your code to get the apex to save but believe that this has broken the OR in the IF and therefore the trigger no longer creates the contract record, however, we don't get the error upon saving the opportunity so assuming we're very close.

Any further help would be massively appreciated.

Thanks,

Snita
Nagendra ChinchinadaNagendra Chinchinada
 if ( (trigger.isUpdate  && opp.StageName == 'Closed Won' && Trigger.oldMap <> null && Trigger.oldMap.get(opp.id).StageName <> opp.StageName && opp.RecordTypeID == RCTypeId && opp.Record_Complete__c == TRUE)  
        || (trigger.isInsert  && opp.StageName == 'Closed Won' && opp.RecordTypeID == RCTypeId && opp.Record_Complete__c == TRUE) ) 
 
Tavva Sai KrishnaTavva Sai Krishna
Hi Snita,

let me summarise your requirement. One contract record need to be created when  the Active opportunity record is created / updated .
Active opportunity : stagename is closed won and record complete is true. Also need to prevent the duplicate creation of contract record.

if this is your requirement. Then i have written trigger which works as explained above.

Below i explained the  schema relationship of opportunity, contract and account. Based on this schema the code was written. Also let me know if you are using different schema other than i explained below.

OBJECTS SCHEMA:

 Contract is parent to opportunity (Look up relationship) and child to account (Look up relationship). while inserting /updating the active opportunity you need to create the contract record with the related account and again update the opportunity records with the newly created contract record. Below code logic as same as explained here.

Below image is the schema structure for the same as i explained.

Opportunity ,Account and contract schema structure
trigger CreateContract on Opportunity (after insert, after update) {

    List<Contract> conttoinsert = new List<Contract>();
	
	List<opportunity> LstUpdOpp = new List<opportunity>();
	
	Map<String,opportunity> mapOpp = new Map<String,opportunity> ();
	
	if(Trigger.IsInsert || Trigger.Isupdate)
	{
		//Intialising mapOpp
		for(opportunity opp: Trigger.new)
		{
			mapOpp.put(opp.name,opp);
		}
		
		if(Trigger.Isupdate)
		{
			List<opportunity> lstOppNoCont = new List <opportunity>();
			//Removing duplicate creation of contract records.
			for(opportunity opp: Trigger.new)
			{
				if(opp.contract == null)
					lstOppNoCont.add(opp);// list holds the opp records for which contract records are not created.
			}
			
			//if the opportunity doesnt have contract records then will create new contract
			if(!lstOppNoCont.isEmpty())
			{
				// Fetching the contract records to be insert for valid opportunities.
				conttoinsert.addall(newContract(lstOppNoCont));
			}
		}
			
		//It create the contract records for eligible opportunity
		if(Trigger.IsInsert)
		{
			// Fetching the contract records to be insert for valid opportunities.
			conttoinsert.addall(newContract(Trigger.new));
		}
		
		//Create contract records if the list is not empty. also update the opportunity records with the new contract records.
		if ( !conttoinsert.isEmpty()) {
			
			//create contracts.
			insert conttoinsert;
			
			//Update Opportunity with the contract record.
			for(contract Rec: conttoinsert)
			{
				Opportunity oppRec = new Opportunity();
				oppRec = mapOpp.get(Rec.name);
				oppRec.contract = rec.id;
				oppRec.id = oppRec.id;
				LstUpdOpp.add(oppRec);
				
			}
			
			if(!LstUpdOpp.isEmpty()){
				update LstUpdOpp;
			}
        }
	}
	
	//Create the contract records for eligible opportunity and returns list of contract.
	public list <contract> newContract (list<opportunity> Lstopp)
	{
		for (Opportunity opp : Lstopp) {

			List<Contract> lstNewCon = new List<Contract>();
			if (opp.StageName == 'Closed Won' && opp.RecordTypeID == '012N00000004pSi' && opp.Record_Complete__c == TRUE) {
				Contract con = new Contract();
				con.Name                  = opp.name;
				con.Account               = opp.Account;
				con.CurrencyIsoCode       = opp.CurrencyIsoCode;
				con.AccountId             = opp.AccountId;
				con.StartDate             = opp.Service_Start_Date__c;
				con.Contract_Value__c     = opp.Total_Performance__c;
				con.Description           = opp.Opportunity_Description__c;
				con.Sales_Lead__c         = opp.OwnerID;
				lstNewCon.add(con);
			} 
		}
		return lstNewCon;
	}
	
	
	
    
}

Also, note that i havent tested above code . so please let me know the errors, if you are facing any. 

Waiting for your reply,

Regards,
Sai Krishna Tavva
Snita LalSnita Lal
Hi Sai,

Thanks for taking the time to fully explain the logic and process - Genuinely really helpful.

Unfortunately we do hit an error when inserting your code, it actually returns an error for the variable 1stNewCon, although we can see you have set the variable above.

Compile Error: Variable does not exist: NewCon at line 83 column 16

Any further help you could offer would be gratefully apprecaited.

Thanks,

Snita
Tavva Sai KrishnaTavva Sai Krishna
Hi Snita,

Yes, please check with the modified code below.
 
trigger CreateContract on Opportunity (after insert, after update) {

    List<Contract> conttoinsert = new List<Contract>();
	
	List<opportunity> LstUpdOpp = new List<opportunity>();
	
	Map<String,opportunity> mapOpp = new Map<String,opportunity> ();
	
	if(Trigger.IsInsert || Trigger.Isupdate)
	{
		//Intialising mapOpp
		for(opportunity opp: Trigger.new)
		{
			mapOpp.put(opp.name,opp);
		}
		
		if(Trigger.Isupdate)
		{
			List<opportunity> lstOppNoCont = new List <opportunity>();
			//Removing duplicate creation of contract records.
			for(opportunity opp: Trigger.new)
			{
				if(opp.contract == null)
					lstOppNoCont.add(opp);// list holds the opp records for which contract records are not created.
			}
			
			//if the opportunity doesnt have contract records then will create new contract
			if(!lstOppNoCont.isEmpty())
			{
				// Fetching the contract records to be insert for valid opportunities.
				conttoinsert.addall(newContract(lstOppNoCont));
			}
		}
			
		//It create the contract records for eligible opportunity
		if(Trigger.IsInsert)
		{
			// Fetching the contract records to be insert for valid opportunities.
			conttoinsert.addall(newContract(Trigger.new));
		}
		
		//Create contract records if the list is not empty. also update the opportunity records with the new contract records.
		if ( !conttoinsert.isEmpty()) {
			
			//create contracts.
			insert conttoinsert;
			
			//Update Opportunity with the contract record.
			for(contract Rec: conttoinsert)
			{
				Opportunity oppRec = new Opportunity();
				oppRec = mapOpp.get(Rec.name);
				oppRec.contractid = rec.id;
				oppRec.id = oppRec.id;
				LstUpdOpp.add(oppRec);
				
			}
			
			if(!LstUpdOpp.isEmpty()){
				update LstUpdOpp;
			}
        }
	}
	
	//Create the contract records for eligible opportunity and returns list of contract.
	public list <contract> newContract (list<opportunity> Lstopp)
	{
		List<Contract> lstNewCon = new List<Contract>();
		
		for (Opportunity opp : Lstopp) {
		
			if (opp.StageName == 'Closed Won' && opp.RecordTypeID == '012N00000004pSi' && opp.Record_Complete__c == TRUE) {
				Contract con = new Contract();
				con.Name                  = opp.name;
				con.Account               = opp.Account;
				con.CurrencyIsoCode       = opp.CurrencyIsoCode;
				con.AccountId             = opp.AccountId;
				con.StartDate             = opp.Service_Start_Date__c;
				con.Contract_Value__c     = opp.Total_Performance__c;
				con.Description           = opp.Opportunity_Description__c;
				con.Sales_Lead__c         = opp.OwnerID;
				lstNewCon.add(con);
			} 
		}
		return lstNewCon;
	}
	
	
	
    
}

we need to define the variable outside the for loop. I have modified the same. now you can test. if you still get any errors regarding the fields such as "Invalid field CurrencyIsoCode" . just comment those llines which are defined in the method and try to save. these errors may get as your org doesnt enable the currency functionality / the object doesnt have the fields with the same api names.

for your reference the variable name  starts with  small letter "L" not with 1 . (lstNewCon).

Still if you face any errors give the details of the error.

Waiting for your reply,

Regards,
Sai Krishna Tavva.

 
This was selected as the best answer
Tavva Sai KrishnaTavva Sai Krishna
Hi Snita,

I hope you arent facing any issues with my answer. if am i right please select the best answer and mark this question as solved as it may useful to others a lot.

Regards,
Sai Krishna Tavva.