+ Start a Discussion
DaNae Peterson 1DaNae Peterson 1 

How to update an original record after cloning itself in a trigger

Hi all,

I am trying to write a trigger that is 2-fold in purpose.  The context is we have 2 different types of opportunity record types (for existing business): Renewal and Retention.  Renewal is informal when we are proactively trying to extend a contract and Retention is more formal when the client has issued a Request for Proposal from multiple vendors.  It is possible that we have a renewal that will change to retention.  In that case, what I am trying to do is:
1 - Clone the record (when stage = '6 Going to Bid') but change the record type to Retention and set stage = '3 Analysis'
2 - Update the original record (the one whose stage = '6 Going to Bid') with a link to the new record (I have created a custom lookup field labeled 'Related Retention Opportunity').

I have written a trigger and can get the first part to work but am getting stuck on the second requirement.  I think it is because of the criteria when to run the trigger (before update, after insert, etc.).  I have copied my trigger below but commented out where it is not working at the end.  Any help would be greatly appreciated.  THANK YOU!!



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

    for(Opportunity o : trigger.new){
        if(o.StageName == '6 Going to Bid'){
            
            Opportunity opp = [SELECT Id, Name, Related_Retention_Opportunity__c, AccountId, CloseDate, Type, No_of_Buses__c, Current_Contractor__c, NEC_Company__c, LeadSource, NEC_Contract__c, No_Bid_Decision_Made__c, OwnerId, CampaignId, Up_or_Out__c, Total_Routes_out_for_bid__c, Market_Cap__c, Description, Contract_Start_Date__c, Contract_End_Date__c, Contract_Extension_Date__c, Reason_Win_Loss_NoBid_Note__c, Account_Management__c, Priority__c, Business_Analysts__c, Bid_Coordinator__c, Comments_Update__c, Budgeted_Increase__c, Approved_Increase__c, Actual_Increase__c, Budgeted_Bus_Count__c, Approved_Bus_Count__c, Awarded_Bus_Count__c, Amount, X5_YR_OM__c, YR_1_Capex__c, New_Bus_CapEx_Yr1__c, District_Fleet_CapEx_Yr1__c, Cascade_Fleet_Value_Yr1__c, Other_Equip_CapEx_Yr1__c, ROCE__c, IRR__c, NPV__c, Final_Model_Detail__c, Op_Lease_Purchase__c, of_Op_Leases__c, Avg_Annual_Op_Lease_Cost__c, Winning_Contractor__c, No_Bid_Reason__c, Difference_from_low_bidder__c, Competitor_Pricing_Not_Available__c, Other_Bidding_Companies__c, Pricing__c, Reason_Win_Loss__c, STA_price_change_Yr_1__c, Difference_from_First_Student__c, Illinois_Central_s_price_change_Yr_1__c, Cook_IL_price_change_Yr_1__c, Regional_Co_price_change_Yr_1__c, Regional_Co_2_price_change_Yr_1__c
                               FROM Opportunity WHERE Id = :o.Id];
            
            Opportunity OClone = new Opportunity();
            
            OClone.Name = 'Going to Bid - ' + opp.Name;
            OClone.RecordTypeId = '012A0000000VkfM';
            OClone.AccountId = opp.AccountId;
            OClone.CloseDate = opp.CloseDate;
            OClone.StageName = '3 Analysis';
            OClone.Type = opp.Type;
            OClone.No_of_Buses__c = opp.No_of_Buses__c;
            OClone.Current_Contractor__c = opp.Current_Contractor__c;
            OClone.NEC_Company__c = opp.NEC_Company__c;
            OClone.LeadSource = opp.LeadSource;
            OClone.NEC_Contract__c = opp.NEC_Contract__c;
            OClone.No_Bid_Decision_Made__c = opp.No_Bid_Decision_Made__c;
            OClone.OwnerId = opp.OwnerId;
            OClone.CampaignId = opp.CampaignId;
            OClone.Up_or_Out__c = opp.Up_or_Out__c;
            OClone.Total_Routes_out_for_bid__c = opp.Total_Routes_out_for_bid__c;
            OClone.Market_Cap__c = opp.Market_Cap__c;
            OClone.Description = opp.Description;
            OClone.Contract_Start_Date__c = opp.Contract_Start_Date__c;
            OClone.Contract_End_Date__c = opp.Contract_End_Date__c;
            OClone.Contract_Extension_Date__c = opp.Contract_Extension_Date__c;
            OClone.Priority__c = opp.Priority__c;
            OClone.Business_Analysts__c = opp.Business_Analysts__c;
            OClone.Bid_Coordinator__c = opp.Bid_Coordinator__c;
            OClone.Comments_Update__c = opp.Comments_Update__c;
            OClone.Budgeted_Increase__c = opp.Budgeted_Increase__c;
            OClone.Approved_Increase__c = opp.Approved_Increase__c;
            OClone.Actual_Increase__c = opp.Actual_Increase__c;
            OClone.Budgeted_Bus_Count__c = opp.Budgeted_Bus_Count__c;
            OClone.Approved_Bus_Count__c = opp.Approved_Bus_Count__c;
            OClone.Awarded_Bus_Count__c = opp.Awarded_Bus_Count__c;
            OClone.Amount = opp.Amount;
            OClone.Reason_Win_Loss_NoBid_Note__c = opp.Reason_Win_Loss_NoBid_Note__c;
            OClone.X5_YR_OM__c = opp.X5_YR_OM__c;
            OClone.YR_1_Capex__c = opp.YR_1_Capex__c;
            OClone.New_Bus_CapEx_Yr1__c = opp.New_Bus_CapEx_Yr1__c;
            OClone.District_Fleet_CapEx_Yr1__c = opp.District_Fleet_CapEx_Yr1__c;
            OClone.Cascade_Fleet_Value_Yr1__c = opp.Cascade_Fleet_Value_Yr1__c;
            OClone.Other_Equip_CapEx_Yr1__c = opp.Other_Equip_CapEx_Yr1__c;
            OClone.ROCE__c = opp.ROCE__c;
            OClone.IRR__c = opp.IRR__c;
            OClone.NPV__c = opp.NPV__c;
            OClone.Final_Model_Detail__c = opp.Final_Model_Detail__c;
            OClone.Op_Lease_Purchase__c = opp.Op_Lease_Purchase__c;
            OClone.of_Op_Leases__c = opp.of_Op_Leases__c;
            OClone.Avg_Annual_Op_Lease_Cost__c = opp.Avg_Annual_Op_Lease_Cost__c;
            OClone.Winning_Contractor__c = opp.Winning_Contractor__c;
            OClone.Reason_Win_Loss__c = opp.Reason_Win_Loss__c;
            OClone.No_Bid_Reason__c = opp.No_Bid_Reason__c;
            OClone.Difference_from_low_bidder__c = opp.Difference_from_low_bidder__c;
            OClone.Competitor_Pricing_Not_Available__c = opp.Competitor_Pricing_Not_Available__c;
            OClone.Other_Bidding_Companies__c = opp.Other_Bidding_Companies__c;
            OClone.Pricing__c = opp.Pricing__c;
            OClone.Reason_Win_Loss__c = opp.Reason_Win_Loss__c;
            OClone.STA_price_change_Yr_1__c = opp.STA_price_change_Yr_1__c;
            OClone.Difference_from_First_Student__c = opp.Difference_from_First_Student__c;
            OClone.Illinois_Central_s_price_change_Yr_1__c = opp.Illinois_Central_s_price_change_Yr_1__c;
            OClone.Cook_IL_price_change_Yr_1__c = opp.Cook_IL_price_change_Yr_1__c;
            OClone.Regional_Co_price_change_Yr_1__c = opp.Regional_Co_price_change_Yr_1__c;
            OClone.Regional_Co_2_price_change_Yr_1__c = opp.Regional_Co_2_price_change_Yr_1__c;
            OClone.Account_Management__c = opp.Account_Management__c;
            
            insert OClone;
            
 /*         IF(trigger.isafter){
             opp.Related_Retention_Opportunity__c = OClone.Id;
             update opp;     
            }
  */        

            
        }
    }
      
}
Niket SFNiket SF

1. You can check trigger.old and trigger.new values to avoid recursion. 
2. I saw you are copping each and every field manually. You can use object.clone(false) method. 
3. It will be easy to understand if you can write Trigger Handler and push your code to methods.

 
Mahesh DMahesh D
Hi DaNae,

Please check the below code which will solve your first requirement.
 
trigger CloneOpportunity on Opportunity (after update, after insert) {
	
	List<Opportunity> newOppList = new List<Opportunity>();

	Map<String, Schema.RecordTypeInfo> recordTypeByName = Schema.SObjectType.Opportunity.getRecordTypeInfosByName();
	Schema.RecordTypeInfo rtByName =  recordTypeByName.get('Retention');
	Id recTypeId = (rtByName != null ? rtByName.getRecordTypeId():'');
	
	
    for(Opportunity opp : trigger.new) {
        if(opp.StageName == '6 Going to Bid' && (Trigger.isInsert || opp.StageName != Trigger.oldMap.get(opp.Id))) {
			Opportunity cloneOpp = opp.clone(false, false, false, false);
			cloneOpp.RecordTypeId = recTypeId;
			cloneOpp.StageName = '3 Analysis';
			newOppList.add(cloneOpp);
		}
	}
    
    if(!newOppList.isEmpty())
	     insert newOppList;
}

To implement the second requirement, we have to identify a way to match the created cloned record vs existing record because this trigger may execute for 1 record / 10 records or 100 records in the same way.

Regards,
Mahesh
Mahesh DMahesh D
Please use the below code which I corrected one statement.
 
trigger CloneOpportunity on Opportunity (after update, after insert) {
	
	List<Opportunity> newOppList = new List<Opportunity>();

	Map<String, Schema.RecordTypeInfo> recordTypeByName = Schema.SObjectType.Opportunity.getRecordTypeInfosByName();
	Schema.RecordTypeInfo rtByName =  recordTypeByName.get('Retention');
	Id recTypeId = (rtByName != null ? rtByName.getRecordTypeId():'');
	
	
    for(Opportunity opp : trigger.new) {
        if(opp.StageName == '6 Going to Bid' && (Trigger.isInsert || opp.StageName != Trigger.oldMap.get(opp.Id).StageName)) {
			Opportunity cloneOpp = opp.clone(false, false, false, false);
			cloneOpp.RecordTypeId = recTypeId;
			cloneOpp.StageName = '3 Analysis';
			newOppList.add(cloneOpp);
		}
	}
    
	if(!newOppList.isEmpty())
		insert newOppList;
}

Regards,
Mahesh
DaNae Peterson 1DaNae Peterson 1
@[Mahesh D], It does work, thank you!  I did insert the following as line 14:
 cloneOpp.Name = 'Going to Bid - ' + opp.Name;

Do you have any suggestions how to meet the second requirement?  A couple of ways to help match the record is both have the same account IDs, due dates, and the names will be very similar with the exception of the cloned record will contain 'Going to Bid - ' in the front of the name.

 -DaNae