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
travis.truetttravis.truett 

Copy Contact Roles from Opportunity to Clone Opportunity?

I've written a trigger that duplicates an opportunity when we change its status to "Closed-Won." The idea is that every time we close an opportunity, we want to create a follow-up opportunity for pursuing that client again. My trigger makes a clone of the opportunity, adds some new products and schedule items, sets the status to discovery, and a couple of other minor things. As of right now, the trigger doesn't actually copy any objects associated with opportunity. Ideally, if there's a way to do this, I want to copy all of the existing Contact Roles from the closed Opp to the new one. Here's my code...let me know if you have any questions about it or need to know anything about our setup. 

​Thanks! 

trigger Create_followup on Opportunity (before update, after insert) {

Pricebook2 standardBook = [SELECT Id FROM Pricebook2 WHERE Name = :'Ambition'];//Create an instance of the standard pricebook


if(Trigger.isUpdate){


List<Opportunity> listOppor = new List<Opportunity>();
for (Opportunity o: Trigger.new){

    if (o.StageName == 'Closed Won' && o.Stage_Change__c == false && o.Is_Clone__c == false){

        Opportunity oppNew = o.clone();
        oppNew.Name = oppNew.Name  + ' - Annual ' + o.CloseDate.year();
        
        if(o.Renewal_Date__c != null){
        oppNew.Renewal_Date__c = o.Renewal_Date__c.addYears(1);
        oppNew.CloseDate = o.Renewal_Date__c.addYears(1);}
        
        oppNew.StageName = 'Discovery';
        oppNew.Probability = 25;
       
        
        
        oppNew.Pricebook2Id = standardBook.Id;//associate the standard pricebook with this opportunity
        
        oppNew.Is_Clone__c = true;
        listOppor.add(oppNew);
        o.Stage_Change__c = true;
        
}

 
}//end of for loop

   if(listOppor.size() > 0){
 insert listOppor;       
        
}

}

if(trigger.isInsert){

try{
OpportunityLineItem[] lines = new OpportunityLineItem[0];
PricebookEntry entry = [SELECT Id, UnitPrice FROM PricebookEntry WHERE Pricebook2Id = :standardBook.Id AND Product2.ProductCode = 'ENTERPRISE_ANNUAL_UPFRONT'];
List<Event> eventList = new List<Event>();
//List<Note> noteList = new List<Note>();


for(Opportunity o: Trigger.new){
if(o.Is_Clone__c == true){

//noteList.add(new Note(ParentId=o.id,Title='Matt is the Apex_God',Body='Matt is the Apex_God',isPrivate=false));

lines.add(new OpportunityLineItem(PricebookEntryId=entry.Id, OpportunityId=o.Id, UnitPrice=entry.UnitPrice, Quantity=1));

if(o.Renewal_Date__c != null){

DateTime myDateTime = o.Renewal_Date__c.addMonths(-10);
 

eventList.add(new Event(whatid=o.id,startdatetime=myDateTime,subject='Account Management Follow-Up', EndDateTime=myDateTime, IsAllDayEvent=true));
eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(2),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(2), IsAllDayEvent=true));
eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(4),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(4), IsAllDayEvent=true));
eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(6),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(6), IsAllDayEvent=true));
eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(8),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(8), IsAllDayEvent=true));
eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(10),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(10), IsAllDayEvent=true));
eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(10),subject='Renewal',EndDateTime=myDateTime.addMonths(10), IsAllDayEvent=true));
eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(9),subject='Sales Contract Follow-Up',EndDateTime=myDateTime.addMonths(9), IsAllDayEvent=true));

}//end of if

}
}
insert lines;
insert eventList;
//insert noteList;

}
catch(Exception e){

}
}


}
Best Answer chosen by travis.truett
Prosenjit Sarkar 7Prosenjit Sarkar 7
Sorry Travis, I too was unawared that . Yes, you have got the right point. In that case we can create contact role just by copying from previous ones like the opportunity.

Please check with this code and let me know if it has worked or not.
 
trigger Create_followup on Opportunity (before update, after insert) {
	Pricebook2 standardBook = [SELECT Id FROM Pricebook2 WHERE Name = :'Ambition'];//Create an instance of the standard pricebook
	
	if(Trigger.isUpdate){
		List<Opportunity> listOppor = new List<Opportunity>();
		for (Opportunity o: Trigger.new){
			if (o.StageName == 'Closed Won' && o.Stage_Change__c == false && o.Is_Clone__c == false){

				Opportunity oppNew = o.clone();
				oppNew.Name = oppNew.Name  + ' - Annual ' + o.CloseDate.year();
				
				if(o.Renewal_Date__c != null){
				oppNew.Renewal_Date__c = o.Renewal_Date__c.addYears(1);
				oppNew.CloseDate = o.Renewal_Date__c.addYears(1);}
				
				oppNew.StageName = 'Discovery';
				oppNew.Probability = 25;
			    oppNew.Parent_Opportunity__c = o.Id;
				
				
				oppNew.Pricebook2Id = standardBook.Id;//associate the standard pricebook with this opportunity
				
				oppNew.Is_Clone__c = true;
				listOppor.add(oppNew);
				o.Stage_Change__c = true;
				
			}

		}//end of for loop

		if(listOppor.size() > 0){
			insert listOppor;
			
			List<OpportunityContactRole> ocrList = [SELECT OpportunityId, ContactId, Role FROM OpportunityContactRole WHERE OpportunityId IN :Trigger.New];
			List<OpportunityContactRole> newOcrList = new List<OpportunityContactRole>();

			if(!ocrList.isEmpty()) {
				Map<Id, Id> oldOpNewOpIdMap = new Map<Id, Id>();
				for(Opportunity opNew : listOppor) {
					oldOpNewOpIdMap.put(opNew.Parent_Opportunity__c, opNew.Id);
				}	
				for(OpportunityContactRole ocr : ocrList) {
					OpportunityContactRole newOcr = new OpportunityContactRole();
					newOcr.ContactId = ocr.ContactId;
					newOcr.Role = ocr.Role;
					newOcr.OpportunityId = oldOpNewOpIdMap.get(ocr.OpportunityId);
					newOcrList.add(newOcr);
				}
				insert newOcrList;
			}
			
			
		}
		
		

	}

	if(trigger.isInsert){
		try{
			OpportunityLineItem[] lines = new OpportunityLineItem[0];
			PricebookEntry entry = [SELECT Id, UnitPrice FROM PricebookEntry WHERE Pricebook2Id = :standardBook.Id AND Product2.ProductCode = 'ENTERPRISE_ANNUAL_UPFRONT'];
			List<Event> eventList = new List<Event>();
			//List<Note> noteList = new List<Note>();


			for(Opportunity o: Trigger.new){
				if(o.Is_Clone__c == true){

					//noteList.add(new Note(ParentId=o.id,Title='Matt is the Apex_God',Body='Matt is the Apex_God',isPrivate=false));

					lines.add(new OpportunityLineItem(PricebookEntryId=entry.Id, OpportunityId=o.Id, UnitPrice=entry.UnitPrice, Quantity=1));

					if(o.Renewal_Date__c != null){

						DateTime myDateTime = o.Renewal_Date__c.addMonths(-10);
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime,subject='Account Management Follow-Up', EndDateTime=myDateTime, IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(2),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(2), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(4),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(4), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(6),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(6), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(8),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(8), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(10),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(10), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(10),subject='Renewal',EndDateTime=myDateTime.addMonths(10), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(9),subject='Sales Contract Follow-Up',EndDateTime=myDateTime.addMonths(9), IsAllDayEvent=true));

					}//end of if

				}
			}
			insert lines;
			insert eventList;
			//insert noteList;

		}
		catch(Exception e){

		}
	}
}

Thanks 
Prosenjit

All Answers

Prosenjit Sarkar 7Prosenjit Sarkar 7
Hi Travis,

Tell me one thing, have  you made a refference of old Opportunity into the new Opportunity ? If yes, you code is not binding them. please confirm that.

Thanks 
Prosenjit
travis.truetttravis.truett
In the trigger, I have:

Opportunity oppNew = o.clone();

I then change field values and add objects to oppNew, and I can still access o. Is that what you're asking?
Prosenjit Sarkar 7Prosenjit Sarkar 7
No Let me exmplain . Suppose yoy have 3 closed won opportunity Op1, Op2, Op3. No wyou have created 3 new Opportinuties with respect to them and they are Nop1, Nop2, Nop3. Now how can you understand which one is the follow up opportinity of op1 ? there should e self reference lookup right ?
Other wise the trigger cannot be bulkified properly.
Thanks
Prosenjit.
travis.truetttravis.truett
The new opportunities inherit the name of the opportunity they were cloned from with " - Annual 20xx" at the end, depending on the year of the close date. I'm not sure how to have the clones inherit a reference to the opportunity they were cloned from, but if that is something I need to do, I'd like to know how.
Prosenjit Sarkar 7Prosenjit Sarkar 7
Hi Travis,
I undertand you are referencing two opportunitie by their names. But its not much secure. Suppose, we have two closed won opportunities with same name. Then how can we distiguish their follow ups ?
Better way can be create a look up field of object opportunity it self, i.e. self look up . Name the field as  Parent opportunity.
So that there will be a maintained hierarchy. For example, We have Op1 . Now it is closed. so Fop1 is created and Parent Opportinuty field of Fop1 will be Op1. Now Fop1 is also closed and again Fop2 is created as a follow up . So the hierarchy will be like Op1 -> Fop1 -> Fop2. We can redirect to all the previous opportunities from the current open opportunity by that field.
So,, when we create a New Opportunity from a closed we can easily track their Contact Roles and copy to that particular new Opportunity.

This is just a good practice suggestion. It might vary from your client specification.

By the way, Their is an standard object OpportunityContactRole which is responcible for storing contact role information.
Fields are ,
  • ContactId : which is the look up to the Contact.
  • OpportunityId : which the look up Opportunity (this field should be update in your trigger from closed to new).
If you are stick with you previous data model I can help with some updated code but that will violate some best practice rule , I think. Remaining is depending on you cilent specification.

Thanks
Prosenjit.
travis.truetttravis.truett
Ok so I added the parent opportunity field to the opportunity layout. I can populate that from my trigger pretty easily. How do I move from there to copying the contact roles over? Can I now access the parent opportunity directly in my trigger? 

I've only been coding in Apex for a few weeks, so this stuff is still all fairly new to me. Thanks for your help.
Prosenjit Sarkar 7Prosenjit Sarkar 7
If you had the field in Object you can always use it in trigger. Adding in page layout is not required. 
Prosenjit Sarkar 7Prosenjit Sarkar 7
Please tell me your parent opportunity fields api name .. I can help you the code.
travis.truetttravis.truett
It's Parent_Opportunity__c
Prosenjit Sarkar 7Prosenjit Sarkar 7

Hi Travis,

here is your code which will copy the Contact Roles. I haven't change the other codes. Just only added codes which will add contact roles.
 
trigger Create_followup on Opportunity (before update, after insert) {
	Pricebook2 standardBook = [SELECT Id FROM Pricebook2 WHERE Name = :'Ambition'];//Create an instance of the standard pricebook
	
	if(Trigger.isUpdate){
		List<Opportunity> listOppor = new List<Opportunity>();
		for (Opportunity o: Trigger.new){
			if (o.StageName == 'Closed Won' && o.Stage_Change__c == false && o.Is_Clone__c == false){

				Opportunity oppNew = o.clone();
				oppNew.Name = oppNew.Name  + ' - Annual ' + o.CloseDate.year();
				
				if(o.Renewal_Date__c != null){
				oppNew.Renewal_Date__c = o.Renewal_Date__c.addYears(1);
				oppNew.CloseDate = o.Renewal_Date__c.addYears(1);}
				
				oppNew.StageName = 'Discovery';
				oppNew.Probability = 25;
			    oppNew.Parent_Opportunity__c = o.Id;
				
				
				oppNew.Pricebook2Id = standardBook.Id;//associate the standard pricebook with this opportunity
				
				oppNew.Is_Clone__c = true;
				listOppor.add(oppNew);
				o.Stage_Change__c = true;
				
			}

		}//end of for loop

		if(listOppor.size() > 0){
			insert listOppor;
			
			List<OpportunityContactRole> ocrList = [SELECT OpportunityId FROM OpportunityContactRole WHERE OpportunityId IN :Trigger.New];
			if(!ocrList.isEmpty()) {
				Map<Id, Id> oldOpNewOpIdMap = new Map<Id, Id>();
				for(Opportunity opNew : listOppor) {
					oldOpNewOpIdMap.put(opNew.Parent_Opportunity__c, opNew.Id);
				}	
				for(OpportunityContactRole ocr : ocrList) {
					ocr.OpportunityId = oldOpNewOpIdMap.get(ocr.OpportunityId);
				}
				update ocrList;
			}
			
			
		}
		
		

	}

	if(trigger.isInsert){
		try{
			OpportunityLineItem[] lines = new OpportunityLineItem[0];
			PricebookEntry entry = [SELECT Id, UnitPrice FROM PricebookEntry WHERE Pricebook2Id = :standardBook.Id AND Product2.ProductCode = 'ENTERPRISE_ANNUAL_UPFRONT'];
			List<Event> eventList = new List<Event>();
			//List<Note> noteList = new List<Note>();


			for(Opportunity o: Trigger.new){
				if(o.Is_Clone__c == true){

					//noteList.add(new Note(ParentId=o.id,Title='Matt is the Apex_God',Body='Matt is the Apex_God',isPrivate=false));

					lines.add(new OpportunityLineItem(PricebookEntryId=entry.Id, OpportunityId=o.Id, UnitPrice=entry.UnitPrice, Quantity=1));

					if(o.Renewal_Date__c != null){

						DateTime myDateTime = o.Renewal_Date__c.addMonths(-10);
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime,subject='Account Management Follow-Up', EndDateTime=myDateTime, IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(2),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(2), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(4),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(4), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(6),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(6), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(8),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(8), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(10),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(10), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(10),subject='Renewal',EndDateTime=myDateTime.addMonths(10), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(9),subject='Sales Contract Follow-Up',EndDateTime=myDateTime.addMonths(9), IsAllDayEvent=true));

					}//end of if

				}
			}
			insert lines;
			insert eventList;
			//insert noteList;

		}
		catch(Exception e){

		}
	}
}

Thanks :)
travis.truetttravis.truett
I got an error when duplicating an opp. It looks like that ocr.OpportunityId field is read only? Do I need to edit a field?

Error:Apex trigger Create_followup caused an unexpected exception, contact your administrator: Create_followup: execution of BeforeUpdate caused by: System.SObjectException: Field is not writeable: OpportunityContactRole.OpportunityId: Trigger.Create_followup: line 41, column 1
travis.truetttravis.truett
I took a look at the OpportunityContactRole object, and it looks like you can't update the OpportunityId field once the OpportunityContactRole has been created. Is that the issue?
Prosenjit Sarkar 7Prosenjit Sarkar 7
Sorry Travis, I too was unawared that . Yes, you have got the right point. In that case we can create contact role just by copying from previous ones like the opportunity.

Please check with this code and let me know if it has worked or not.
 
trigger Create_followup on Opportunity (before update, after insert) {
	Pricebook2 standardBook = [SELECT Id FROM Pricebook2 WHERE Name = :'Ambition'];//Create an instance of the standard pricebook
	
	if(Trigger.isUpdate){
		List<Opportunity> listOppor = new List<Opportunity>();
		for (Opportunity o: Trigger.new){
			if (o.StageName == 'Closed Won' && o.Stage_Change__c == false && o.Is_Clone__c == false){

				Opportunity oppNew = o.clone();
				oppNew.Name = oppNew.Name  + ' - Annual ' + o.CloseDate.year();
				
				if(o.Renewal_Date__c != null){
				oppNew.Renewal_Date__c = o.Renewal_Date__c.addYears(1);
				oppNew.CloseDate = o.Renewal_Date__c.addYears(1);}
				
				oppNew.StageName = 'Discovery';
				oppNew.Probability = 25;
			    oppNew.Parent_Opportunity__c = o.Id;
				
				
				oppNew.Pricebook2Id = standardBook.Id;//associate the standard pricebook with this opportunity
				
				oppNew.Is_Clone__c = true;
				listOppor.add(oppNew);
				o.Stage_Change__c = true;
				
			}

		}//end of for loop

		if(listOppor.size() > 0){
			insert listOppor;
			
			List<OpportunityContactRole> ocrList = [SELECT OpportunityId, ContactId, Role FROM OpportunityContactRole WHERE OpportunityId IN :Trigger.New];
			List<OpportunityContactRole> newOcrList = new List<OpportunityContactRole>();

			if(!ocrList.isEmpty()) {
				Map<Id, Id> oldOpNewOpIdMap = new Map<Id, Id>();
				for(Opportunity opNew : listOppor) {
					oldOpNewOpIdMap.put(opNew.Parent_Opportunity__c, opNew.Id);
				}	
				for(OpportunityContactRole ocr : ocrList) {
					OpportunityContactRole newOcr = new OpportunityContactRole();
					newOcr.ContactId = ocr.ContactId;
					newOcr.Role = ocr.Role;
					newOcr.OpportunityId = oldOpNewOpIdMap.get(ocr.OpportunityId);
					newOcrList.add(newOcr);
				}
				insert newOcrList;
			}
			
			
		}
		
		

	}

	if(trigger.isInsert){
		try{
			OpportunityLineItem[] lines = new OpportunityLineItem[0];
			PricebookEntry entry = [SELECT Id, UnitPrice FROM PricebookEntry WHERE Pricebook2Id = :standardBook.Id AND Product2.ProductCode = 'ENTERPRISE_ANNUAL_UPFRONT'];
			List<Event> eventList = new List<Event>();
			//List<Note> noteList = new List<Note>();


			for(Opportunity o: Trigger.new){
				if(o.Is_Clone__c == true){

					//noteList.add(new Note(ParentId=o.id,Title='Matt is the Apex_God',Body='Matt is the Apex_God',isPrivate=false));

					lines.add(new OpportunityLineItem(PricebookEntryId=entry.Id, OpportunityId=o.Id, UnitPrice=entry.UnitPrice, Quantity=1));

					if(o.Renewal_Date__c != null){

						DateTime myDateTime = o.Renewal_Date__c.addMonths(-10);
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime,subject='Account Management Follow-Up', EndDateTime=myDateTime, IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(2),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(2), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(4),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(4), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(6),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(6), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(8),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(8), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(10),subject='Account Management Follow-Up', EndDateTime=myDateTime.addMonths(10), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(10),subject='Renewal',EndDateTime=myDateTime.addMonths(10), IsAllDayEvent=true));
						eventList.add(new Event(whatid=o.id,startdatetime=myDateTime.addMonths(9),subject='Sales Contract Follow-Up',EndDateTime=myDateTime.addMonths(9), IsAllDayEvent=true));

					}//end of if

				}
			}
			insert lines;
			insert eventList;
			//insert noteList;

		}
		catch(Exception e){

		}
	}
}

Thanks 
Prosenjit
This was selected as the best answer
travis.truetttravis.truett
Works perfectly. Thanks!
Prosenjit Sarkar 7Prosenjit Sarkar 7
TMost welcome Travis, I too learned something from you problem . :)
Regards
Prosenjit