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
Nicu TanaseNicu Tanase 

The opportunity SyncedQuote field is read only within a trigger

Hello,

I have a trigger that clones an opportunity and tries to insert the cloned one.
Something like that : 
Opportunity renewalOpportunity = o.clone(); 
.... [apex logic]
insert  renewalOpportunity;

The problem is that I receive this error:  The opportunity SyncedQuote field is read only within a trigger .
Well, the most awkard thing is that SyncedQuote is null.
I've tried to extend my right in order to be able to modify SyncesQuote but i am not able to do this.
I've tried to delete the field from my trigger but i reach another err 
Error:Apex trigger createRenewalOpportunity caused an unexpected exception, contact your administrator: : System.NullPointerException: Attempt to de-reference a null object

How can i solve this problem?

Thanks in advance!!
Nayana KNayana K
Please post whole trigger code here. If there are ready fields then you might skip that field-value pair while cloning.
Nicu TanaseNicu Tanase
Here is the piece of code i am talking about
trigger createRenewalOpportunity on Opportunity (before update) {
    if(!MyValidator_cls.hasAlreadyDone()) {    
        for (Opportunity o : Trigger.new) {
            if(o.StageName == 'Won' && oldOpportunity.StageName != 'Won'){ 
                Opportunity renewalOpportunity = o.clone(); 
                system.debug('===o.clone()===renewalOpportunity.SyncedQuoteId='+renewalOpportunity.SyncedQuoteId);
                system.debug('===o.clone()===renewalOpportunity.SyncedQuote='+renewalOpportunity.SyncedQuote);
                renewalOpportunity.SyncedQuoteId = null;
                system.debug('===o.clone()===renewalOpportunity.SyncedQuoteId='+renewalOpportunity.SyncedQuoteId);
                system.debug('===o.clone()===renewalOpportunity.SyncedQuote='+renewalOpportunity.SyncedQuote);
                renewalOpportunity.Name = 'Renewal - ' + o.Name;    
                renewalOpportunity.Type = 'Renewal Business';   
                renewalOpportunity.StageName = 'Closing & Final Negotiation';    
                renewalOpportunity.ForecastCategoryName = 'Closed Order placed';
                renewalOpportunity.LeadSource = 'Renewal';
                renewalOpportunity.Offer_Type__c = 'Renewal';
                renewalOpportunity.Offer_Type_Discount__c = '35';

                system.debug('===before insert===renewalOpportunity.SyncedQuoteId='+renewalOpportunity.SyncedQuoteId);
                system.debug('===before insert===renewalOpportunity.SyncedQuote='+renewalOpportunity.SyncedQuote);

                insert  renewalOpportunity;

                MyValidator_cls.setAlreadyDone();
            }
        }
    }
}

How can i skip the SyncedQuote and SyncedQuoteId when cloning?
As a matter of fact when cloning from the interface, these fields are not cloned.

Thanks you for your answer.
Nayana KNayana K
trigger createRenewalOpportunity on Opportunity (before update) {
    if(!MyValidator_cls.hasAlreadyDone()) {  
		String strFldApiName;
		Map<String, Schema.SObjectField> fields = Opportunity.sObjectType.getDescribe().fields.getMap();

        for (Opportunity o : Trigger.new) {
            if(o.StageName == 'Won' && oldOpportunity.StageName != 'Won'){ 
                Opportunity renewalOpportunity = o.clone(); 
				for(Schema.SObjectField field : fields.values()){
					if(!field.getDescribe().isUpdateable())
					{
						strFldApiName = field.getDescribe().getName();
						if(renewalOpportunity.containsKey(strFldApiName))
							renewalOpportunity.remove(strFldApiName);
					}
						
				}
                 renewalOpportunity.Name = o.Name;
                
                
                renewalOpportunity.Name = 'Renewal - ' + o.Name;    
                renewalOpportunity.Type = 'Renewal Business';   
                renewalOpportunity.StageName = 'Closing & Final Negotiation';    
                renewalOpportunity.ForecastCategoryName = 'Closed Order placed';
                renewalOpportunity.LeadSource = 'Renewal';
                renewalOpportunity.Offer_Type__c = 'Renewal';

                renewalOpportunity.Offer_Type_Discount__c = '35';
                insert  renewalOpportunity;

                MyValidator_cls.setAlreadyDone();
            }
        }
    }
}

try this once
Nicu TanaseNicu Tanase
I understand what you tried to do. To remove all the fields that are not updateble.

I've tried this earlier only for my SyncedQuote field, but it doesn't work, the rror being : Method does not exist or incorrect signature: [Opportunity].remove(String)
Nicu TanaseNicu Tanase
Maybe there is another function beside remove ... but i am not able to find it. Can u help? 

Also i missed a line in my trigger, here is the complete trigger with all your additional code:
trigger createRenewalOpportunity on Opportunity (before update) {
    if(!MyValidator_cls.hasAlreadyDone()) {  
        String strFldApiName;
        Map<String, Schema.SObjectField> fields = Opportunity.sObjectType.getDescribe().fields.getMap();

        for (Opportunity o : Trigger.new) {
         Opportunity oldOpportunity = Trigger.oldMap.get(o.Id);     
            if(o.StageName == 'Won' && oldOpportunity.StageName != 'Won'){ 
                Opportunity renewalOpportunity = o.clone(); 
                for(Schema.SObjectField field : fields.values()){
                    if(!field.getDescribe().isUpdateable())
                    {
                        strFldApiName = field.getDescribe().getName();
                        if(renewalOpportunity.containsKey(strFldApiName))
                            renewalOpportunity.remove(strFldApiName);
                    }
                        
                }
                 renewalOpportunity.Name = o.Name;
                
                
                renewalOpportunity.Name = 'Renewal - ' + o.Name;    
                renewalOpportunity.Type = 'Renewal Business';   
                renewalOpportunity.StageName = 'Closing & Final Negotiation';    
                renewalOpportunity.ForecastCategoryName = 'Closed Order placed';
                renewalOpportunity.LeadSource = 'Renewal';
                renewalOpportunity.Offer_Type__c = 'Renewal';

                renewalOpportunity.Offer_Type_Discount__c = '35';
                insert  renewalOpportunity;

                MyValidator_cls.setAlreadyDone();
            }
        }
    }
}
Nayana KNayana K
Ideally it should work. When containsKey() can work, then why can't remove() !! I am surprised. I don't find any alternative approach now...Let me try this in my dev org once.
Nicu TanaseNicu Tanase
I thins because containsKey and remove are Map methods https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_map.htm%23apex_System_Map_methods and we are trying to apply a Map method on a Sobject 
Nicu TanaseNicu Tanase
I couldn't find any method to unset a key-value pair on an sObject https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_sobject.htm%23apex_methods_system_sobject 
Nicu TanaseNicu Tanase
the most disturbing fact is that SyncedQUoteId is null and i am still receiving the error.
Nayana KNayana K
trigger createRenewalOpportunity on Opportunity (before update) {
    if(!MyValidator_cls.hasAlreadyDone()) {  
		String strFldApiName;
		Map<String, Schema.SObjectField> fields = Opportunity.sObjectType.getDescribe().fields.getMap();

        for (Opportunity o : Trigger.new) {
            if(o.StageName == 'Won' && oldOpportunity.StageName != 'Won'){ 
                Opportunity renewalOpportunity = new Opportunity();
				for(Schema.SObjectField field : fields.values()){
					if(field.getDescribe().isUpdateable())
					{
						strFldApiName = field.getDescribe().getName();
						renewalOpportunity.put(strFldApiName, o.get(strFldApiName));
					}
						
				}
                 renewalOpportunity.Name = o.Name;
                
                
                renewalOpportunity.Name = 'Renewal - ' + o.Name;    
                renewalOpportunity.Type = 'Renewal Business';   
                renewalOpportunity.StageName = 'Closing & Final Negotiation';    
                renewalOpportunity.ForecastCategoryName = 'Closed Order placed';
                renewalOpportunity.LeadSource = 'Renewal';
                renewalOpportunity.Offer_Type__c = 'Renewal';

                renewalOpportunity.Offer_Type_Discount__c = '35';
                insert  renewalOpportunity;

                MyValidator_cls.setAlreadyDone();
            }
        }
    }
}

Then we can try this to get desired result.
Nayana KNayana K
Yes, though sobject instance is treated as map of field-value pair, containsKey(), remove() is not supported where as get(), put() are supported.
Nicu TanaseNicu Tanase
this is exactly how my code looks right now and it doesn't work ... I really don't know why. Also after for you should have this line for (Opportunity o : Trigger.new) {
            Opportunity oldOpportunity = Trigger.oldMap.get(o.Id);  

Otherwise you will receive this err : Variable does not exist: oldOpportunity.StageName


Do you have any ther idea beside manually unset the SyncedQuoteId? 
I am really out of options here :(
Nicu TanaseNicu Tanase

OK, so this is going to work : 

I've created another opportunity and copied to that oppty all the cloned fields :
                Opportunity renewalOpportunity2 = new Opportunity();
                 for(Schema.SObjectField field : fields.values()){
                    strFldApiName = field.getDescribe().getName();
                    strFldApiValue = renewalOpportunity.get(strFldApiName);
                    if(field.getDescribe().isUpdateable() && strFldApiValue != null)
                    {
                        renewalOpportunity2.put(strFldApiName, strFldApiValue);
                    }
                        
                }

Apparently even if Synced QUote is null and is created by the clone() process itself it's a problem... thumbs down for sales force :(
Nayana KNayana K
I just noticed that you were doing DML inside for loop. This is not recommened. Also after update is the proper way to insert renewal opportunity in this case
trigger createRenewalOpportunity on Opportunity (after update) {
    if(!MyValidator_cls.hasAlreadyDone()) {  
		String strFldApiName;
		Map<String, Schema.SObjectField> fields = Opportunity.sObjectType.getDescribe().fields.getMap();
		List<Opportunity> lstOppToInsert = new List<Opportunity>();
        for (Opportunity o : Trigger.new) {
            if(o.StageName == 'Won' && Trigger.oldMap.get(o.Id).StageName != 'Won'){ 
                Opportunity renewalOpportunity = new Opportunity();
				for(Schema.SObjectField field : fields.values()){
					if(field.getDescribe().isUpdateable())
					{
						strFldApiName = field.getDescribe().getName();
						renewalOpportunity.put(strFldApiName, o.get(strFldApiName));
					}
						
				}
                 renewalOpportunity.Name = o.Name;
                renewalOpportunity.Name = 'Renewal - ' + o.Name;    
                renewalOpportunity.Type = 'Renewal Business';   
                renewalOpportunity.StageName = 'Closing & Final Negotiation';    
                renewalOpportunity.ForecastCategoryName = 'Closed Order placed';
                renewalOpportunity.LeadSource = 'Renewal';
                renewalOpportunity.Offer_Type__c = 'Renewal';

                renewalOpportunity.Offer_Type_Discount__c = '35';
                lstOppToInsert.add(renewalOpportunity);

                MyValidator_cls.setAlreadyDone();
            }
             insert lstOppToInsert;
        }
    }
}

.



 
Nayana KNayana K
https://developer.salesforce.com/blogs/developer-relations/2015/01/apex-best-practices-15-apex-commandments.html
Nicu TanaseNicu Tanase
Thank you very much, you are right, in case of a mass update it is more efficient the way you describe it
Nayana KNayana K
Please mark this post as solved if it helped you.
Nicu TanaseNicu Tanase
Unfortunatelly the first problem remains ... Why i cannot insert a cloned opportunity? (only if that oppty has a syncing quote)
Nayana KNayana K
Standard 'Clone' functionality also doesn't copy synced quote. So this code gives desired output.
Nayana KNayana K
As we know, Opportunity.SyncedQuoteId will be populated if one of the children quotes have turned on sync. 

Assume, we have following records :
Opp1 (Opportunity)
Quote1, Quote2 (children of Opp1)
Say Quote1 is in sync so Opp1.SyncedQuote = Quote1.

Now we are tring to clone Opp1 with Name 'Opp1-Clone' and copy all other field-value pair into it. Let's imagine we have successfully inserted Opp1-Clone with SyncedQuote = Quote1 ( means Opp1-Clone.SyncedQuote = Quote1). What it indicateds is Opp1-Clone is having Quote1 as a child and it's sync is ON; Then what about Opp1??!! Quote1 was supposed to child of Opp1 right? Also we cannot have Child to have 2 parents (Quote1 to have both Opp1, Opp1) !! 
If Opp1-Clone.SyncedQuote = Quote1, it gives a feel like Quote1's parent is changed to Opp1-Clone.

So, ignoring copying of SyncedQuote irrespective of whether it is null or filled is the proper outcome I believe.