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
Maria22Maria22 

OrderTrigger: execution of BeforeUpdate caused by: System.NullPointerException: Attempt to de-reference a null object

I got stuck at one place on one of my requirement on which I am working on.

My use case is as follows:
 
SCENARIO 1: Close Opportunity when Upgrade Order is cancelled 
GIVEN a a person account record in Salesforce AND the has an open upgrade opportunity record in Salesforce AND the opportunity record type equals "Upgrade Opportunity" AND the opportunity stage is not "Closed - Won" WHEN  places an upgrade order AND the status of the upgrade order has been changed to Cancelled THEN the open upgrade opportunity Stage field is set to "Closed - Lost" AND all other related opportunities that also meet this scenario criteria (Upgrade Opportunity, not Closed-Won) are set to "Closed - Lost"

I have done the scenario and code is also getting saved but getting System.NullPointerException:Attempt to de-reference a null Object when try to change the status of an upgrade order from a value say"Order-Placed" to "Cancelled"

Below is my Apex Class which runs in before update from Apex Trigger:
 
public with sharing class OrderTriggerService extends TriggerService {
public void updateOpportunityStage(){
        Set<Id> setIds = new Set<Id>();
        Map<Id,Order> orderOldMap =(Map<Id,Order>)oldMap;
        Map<Id,Order> orderNewMap=(Map<Id,Order>)newMap;
        Set<Opportunity>  updateOppties=new Set<Opportunity>();
        DateTime statusChange= System.now()-90;
        
        for (Order childObj : (List<Order>) newList) {
            if(childObj.Account_Record_Type_Name__c == 'Recipient' && childObj.Record_Type_Dev_Name__c == 'Upgrade_Order'){ 
            setIds.add(childObj.AccountId);
               
            }
        }
        if (setIds.isEmpty()) { return; }
        
        Map<ID, Account> parentAccts = new Map<Id, Account>(
                                          [select id, Upgrade_Order_Booked_Date__c, Upgrade_Order_Status__c, Order_Cancellation_Reason__c,Upgrade_Order_Number__c,Order_Cancellation_Date__c,
                                          (select id, Name, Booked_Date__c, Status ,Order_Cancellation_Reason__c,Oracle_Order_Number__c,Order_Cancellation_Date__c
                                            from Orders ) 
                                           from Account where id in :setIds]);
        System.debug('updateOrderUpgrade parentAccts: ' + parentAccts);
        
        Map<Account, Order> accountToLatestOrder = new Map<Account, Order>();
        for (Account acc : parentAccts.values()) {
            if (acc.orders.isEmpty() == false) {
                accountToLatestOrder.put(acc, acc.orders[0]);
            }
        }
         
    
      for(Account acc : [select id, Upgrade_Order_Booked_Date__c, Upgrade_Order_Status__c, Order_Cancellation_Reason__c,Upgrade_Order_Number__c,Order_Cancellation_Date__c,
                         			(select Id,Name,closedate,amount,StageName,Oracle_Order_Number__c,Status_Last_Changed_On__c
                                     from opportunities where Record_Type_Dev_Name__c='Upgrade_Opportunity') 
                         			from Account where id in : setIds]){
                             
                             for(Order orders : accountToLatestOrder.values()){            
                             		for(Opportunity opps : acc.opportunities){
                                 
                                 		if(opps.StageName<>'Upgrade-Closed Won' && orders.Status!=orderOldMap.get(orders.Id).Status && orders.Status=='Cancelled'){
                                 		
                                                 opps.StageName='Upgrade - Closed Lost'; 
                                                 
                                                 
                                 		}
                               
                            	
                                 System.debug('updateOpportunityStage Opportunity: ' + opps);  
                    			updateOppties.add(opps);
                             }
                             }
                         }
        
         				List<Opportunity> updateopps=new List<Opportunity>(updateOppties);
        				update updateopps;
        			}
}

Also,attached is the screenshots

Errors snapshot taken from UI

Kindly help me.

Any help will be greatly appreciated.

Many thanks​
Maria22Maria22
Hi Everyone,

I would like to include few points to my question above

1. I mentioned in my question above that code is running in before update call
2. I am getting error in below line
if(opps.StageName<>'Upgrade-Closed Won' && orders.Status!=orderOldMap.get(orders.Id).Status && orders.Status=='Cancelled')

Kindly help
monsterloomismonsterloomis
How are you populating orderOldMap? I see it being initialized, but my guess would be that orderOldMap.get(orders.id) is null, and then you’re asking it for status. Status of null? Does not compute. :-)

I would put a debug statement to confirm, then put a check in before using an orderOldMap (containsKey) to ensure you get a record back before you try to access its values. Hope that helps!
Maria22Maria22
Hi Masterloomis, 
Thanks again for your response.
 I really  appreciate you are taking time for my questions. Can you do me a favour. If possible can you help in tweek/modify the code which I have written. That will be a great help for me. 

Kindly help.

​Many thanks in advance
monsterloomismonsterloomis
System.debug('orderOldMap.get(orders.Id): ' + orderOldMap.get(orders.Id));
if(opps.StageName <> 'Upgrade-Closed Won' 
    && orderOldMap.get(orders.Id) != NULL // make sure the map entry exists
    && orders.Status!=orderOldMap.get(orders.Id).Status // THEN use it
    && orders.Status=='Cancelled')

I can't speak to the effects of not finding orders.Id in the orderOldMap (i.e. whether that logic is "acceptable") - but by checking it this way, you short-circuit it to confirm that it exists before trying to get the status, as those conditional statements will evaluate in order, and it will exit as soon as a condition is NOT met. Hope that helps, and good luck.
Maria22Maria22
Hi Monsterloomis,

Thanks for your response again.

I tried your code and put inside my if condition.No wthe order is getting saved when I am trying to change the status from any thing to Cancelled and I am not getting null pointer exception.But the problem still lies the same.
OPportunity with record type=Upgrade Opportunity and if that oppty is linked with the account to which upgrade order is linked and status of Opportunity is not Closed Won then ideally those opportunities should be set to Clsed Lost.But nothing is happening on opportunities.I can see the same old status on Opportunity.

I don't know whats going wrong with code,I am afraid now.I am running out of time also.

Kindly help.

Many thanks in advance
Maria22Maria22
Hi Monsterloomis,

I checked the debug logs. In debug logs I can see orders.Id is null
 
orderOldMap.get(orders.Id): null

Where my logic is going wrong.What I am doing wrong or what should I do in order to rectify the same.

KIndly help.

Many thanks in advance
monsterloomismonsterloomis
Can you share the code that is populating the orderOldMap? All the code I provided did was dodge around the problem, which seems to be that the map orderOldMap doesn't contain the entry you think it should in order to get to that point in the logic. I would focus on why orderOldMap is missing that, but it's hard to say for sure without seeing how it's being populated and why it's missing "orders.id". What happens in the debug logs? Are you monitoring them in the developer console or debug tool?
Maria22Maria22
Hi Monsterloomis,

Actually we are using Event-Dispatcher model for our coding where we have one trigger and one dispatcher and one service class. Trigger.OldMap is our trigger old map list. I am monitoring debug logs in developer console
Maria22Maria22
Bleow is my trigger:
/**
 * Description:   Added TriggerFactory to make sure qwe call each function only once

 */

trigger OrderTrigger on Order (after delete, after insert, after undelete, after update, before delete, before insert, before update) {
              
    TriggerFactory.getOrderService().execute();
    
}

Below is OrderTriggerDispatcher class
public with sharing class OrderTriggerDispatcher extends TriggerDispatcher {
    
    public OrderTriggerService getService() {
            return (OrderTriggerService)this.svr;
        }
       
    public override void doAfterInsert(){
        this.getService().updateOrderUpgrade();
       
        
    }
      public override void doAfterUpdate(){
         this.getService().updateOrderUpgrade();
       
    }  
    public override void doBeforeUpdate(){
        this.getService().updateOrderUpgradeCancellationDate();
        this.getService().updateOpportunityStage();
       
    }
          
}

Below is my OrderTriggerService class
 



public with sharing class OrderTriggerService extends TriggerService {
    
    public void updateOrderUpgradeCancellationDate(){
        //Set<Id> setIds = new Set<Id>();
        Map<Id,Order> orderOldMap =(Map<Id,Order>)oldMap;
    DateTime dT = System.now();
    Date myDate = date.newinstance(dT.year(), dT.month(), dT.day());
       
        for (Order childObj : (List<Order>) newList) {
         if(childObj.Account_Record_Type_Name__c == 'Recipient' && childObj.Record_Type_Dev_Name__c == 'Upgrade_Order' && childObj.Status!=orderOldMap.get(childObj.Id).Status && childObj.Status=='Cancelled')  {
                    childObj.Order_Cancellation_Date__c=myDate;
             
         
           }
        }
          //System.debug('updateOrderUpgradeCancellationDate Order_Cancellation_Date__c: ' + Order_Cancellation_Date__c);
        System.debug(myDate);
        
    }
    
    /*
     * find the latest upgrade order under recipients and copy order value to account level
     */
    public void updateOrderUpgrade() {
        Set<Id> setIds = new Set<Id>();  
        for (Order childObj : (List<Order>) newList) {
            if(childObj.Account_Record_Type_Name__c == 'Recipient' && childObj.Record_Type_Dev_Name__c == 'Upgrade_Order'  ) {
                setIds.add(childObj.AccountId);
            }
        }
        System.debug('updateOrderUpgrade setIds: ' + setIds);
        
        if (setIds.isEmpty()) { return; }
        
        Map<ID, Account> parentAccts = new Map<Id, Account>(
                                          [select id, Upgrade_Order_Booked_Date__c, Upgrade_Order_Status__c, Order_Cancellation_Reason__c,Upgrade_Order_Number__c,Order_Cancellation_Date__c,
                                          (select id, Name, Booked_Date__c, Status ,Order_Cancellation_Reason__c,Oracle_Order_Number__c,Order_Cancellation_Date__c
                                            from Orders where Booked_Date__c != null order by Booked_Date__c desc limit 1) 
                                           from Account where id in :setIds]);
        System.debug('updateOrderUpgrade parentAccts: ' + parentAccts);
        
        // put account <-> latest order map value
        Map<Account, Order> accountToLatestOrder = new Map<Account, Order>();
        for (Account acc : parentAccts.values()) {
            if (acc.orders.isEmpty() == false) {
                accountToLatestOrder.put(acc, acc.orders[0]);
            }
        }
        System.debug('updateOrderUpgrade accountToLatestOrder: ' + accountToLatestOrder);
        
        Map<String, String> accFieldToOrderField = new Map<String, String>();
        
        accFieldToOrderField.put('Upgrade_Order_Booked_Date__c', 'Booked_Date__c');
        accFieldToOrderField.put('Upgrade_Order_Status__c', 'Status');
        accFieldToOrderField.put('Order_Cancellation_Reason__c','Order_Cancellation_Reason__c');
    accFieldToOrderField.put('Upgrade_Order_Number__c','Oracle_Order_Number__c');    
         accFieldToOrderField.put('Order_Cancellation_Date__c', 'Order_Cancellation_Date__c');
        
        Map<Id, Account> needUpdate = this.populateAccountFields(accountToLatestOrder, accFieldToOrderField);
        if (needUpdate.isEmpty() == false) { update needUpdate.values(); }
    }
 
    /*
     * if field is diff then copy field value from order to account based on field mapping
     */
    private Map<Id, Account> populateAccountFields(Map<Account, Order> accountToLatestOrder, Map<String, String> accFieldToOrderField) {
        Map<Id, Account> needUpdate = new Map<Id, Account>();
        
        for (Account acc : accountToLatestOrder.keySet()) {
            Order ord = accountToLatestOrder.get(acc);
            for (String accField : accFieldToOrderField.keySet()) {
                String orderField = accFieldToOrderField.get(accField);
                if (acc.get(accField) != ord.get(orderField)) {
                    acc.put(accField, ord.get(orderField));
                    needUpdate.put(acc.id, acc);
                }
            }
        }
        
        System.debug('populateAccountFields needUpdate: ' + needUpdate);
        return needUpdate;
    }
    
    
    // Update opps to Closed-Won/Closed-Lost based on Orders status changed Canceled/Closed and copy Oracle Order No from Orders to Opps
    public void updateOpportunityStage(){
        Set<Id> setIds = new Set<Id>();
        Map<Id,Order> orderOldMap =(Map<Id,Order>)oldMap;
        Map<Id,Order> orderNewMap=(Map<Id,Order>)newMap;
        Set<Opportunity>  updateOppties=new Set<Opportunity>();
        DateTime statusChange= System.now()-90;
        
        for (Order childObj : (List<Order>) newList) {
            if(childObj.Account_Record_Type_Name__c == 'Recipient' && childObj.Record_Type_Dev_Name__c == 'Upgrade_Order'){ 
            setIds.add(childObj.AccountId);
               
            }
        }
        if (setIds.isEmpty()) { return; }
        
        Map<ID, Account> parentAccts = new Map<Id, Account>(
                                          [select id, Upgrade_Order_Booked_Date__c, Upgrade_Order_Status__c, Order_Cancellation_Reason__c,Upgrade_Order_Number__c,Order_Cancellation_Date__c,
                                          (select id, Name, Booked_Date__c, Status ,Order_Cancellation_Reason__c,Oracle_Order_Number__c,Order_Cancellation_Date__c
                                            from Orders ) 
                                           from Account where id in :setIds]);
        System.debug('updateOrderUpgrade parentAccts: ' + parentAccts);
        
        Map<Account, Order> accountToLatestOrder = new Map<Account, Order>();
        for (Account acc : parentAccts.values()) {
            if (acc.orders.isEmpty() == false) {
                accountToLatestOrder.put(acc, acc.orders);
            }
        }
         
    
      for(Account acc : [select id, Upgrade_Order_Booked_Date__c, Upgrade_Order_Status__c, Order_Cancellation_Reason__c,Upgrade_Order_Number__c,Order_Cancellation_Date__c,
                         			(select Id,Name,closedate,amount,StageName,Oracle_Order_Number__c,Status_Last_Changed_On__c
                                     from opportunities where Record_Type_Dev_Name__c='Upgrade_Opportunity') 
                         			from Account where id in : setIds]){
                             
                             for(Order orders : accountToLatestOrder.values()){            
                             		for(Opportunity opps : acc.opportunities){
                                 		System.debug('orderOldMap.get(orders.Id): ' + orderOldMap.get(orders.Id));

                                 		//if(opps.StageName<>'Upgrade-Closed Won' && orders.Status!=orderOldMap.get(orders.Id).Status && orders.Status=='Cancelled'){
                                            if(opps.StageName <> 'Upgrade-Closed Won' && orderOldMap.get(orders.Id) != NULL // make sure the map entry exists
                                               && orders.Status!=orderOldMap.get(orders.Id).Status // THEN use it
                                               && orders.Status=='Cancelled'){
                                 		//if(opps.StageName<>'Upgrade-Closed Won' && orderNewMap.get(orders.Id).Status!=orderOldMap.get(orders.Id).Status && orderNewMap.get(orders.Id).Status=='Cancelled'){
                                                 opps.StageName='Upgrade - Closed Lost'; 
                                                 opps.Oracle_Order_Number__c=orders.Oracle_Order_Number__c;
                                                 
                                 		}
                               
                            	else if((opps.StageName<>'Upgrade-Closed Won'&&(opps.StageName=='Upgrade - Closed Lost' && opps.Status_Last_Changed_On__c>= statusChange))&& orders.Status!=orderOldMap.get(orders.Id).Status && orders.Status=='Closed'){                 		
                                    		opps.StageName='Upgrade-Closed Won';
                                    	 	opps.Oracle_Order_Number__c=orders.Oracle_Order_Number__c;
                                 }
                                 System.debug('updateOpportunityStage Opportunity: ' + opps);  
                    			updateOppties.add(opps);
                             }
                             }
                         }
        
         				List<Opportunity> updateopps=new List<Opportunity>(updateOppties);
        				update updateopps;
        			}
      }
For current requirement I have added updateOpportunityStage() method in OrderService Class.
Kindly let me know if you need any more inputs from my side.

I guess or assume that problem is The link between Accounts,Orders and Opportunities are not getting fir correctly.

Kindly help.

Many thanks in advance
 
Maria22Maria22
Hi Monsterloomis,

I guess you may need to look into Trigger Dispatcher and Trigger Service class as well.So I am putting those classes also.

Below is Trigger Service class:
public virtual with sharing class TriggerService {
    
    //Trigger new list and old map variables
    public List<sObject> oldList;
    public Map<Id, sObject> oldMap;
    public List<sObject> newList;
    public Map<Id, sObject> newMap;

    //Trigger context variables
    public Boolean isBefore;
    public Boolean isAfter;
    public Boolean isInsert;
    public Boolean isUpdate;
    public Boolean isDelete;
    public Boolean isUndelete;
    
    private TriggerDispatcher dispatcher;
    
    
    public void setDispatcher(TriggerDispatcher dispatcher) {
        this.dispatcher = dispatcher;
        dispatcher.registerService(this);
    }
    
    public void execute() {
        this.dispatcher.processTriggerEvents();
    }
}

Below is trigger Dispatcher
 
/*
 * this class is a trigger event dispatcher, also there are some by pass or recursion flag variables defined.
 * pass the trigger context variable to delegate service class, so subclass of service class can easily use them.
 * 
 * @author   Tushar Arora, Ming Lu
 */
public virtual with sharing class TriggerDispatcher {
    
    //By pass trigger logic variable
    public static Boolean byPassTrigger = false;
    
    //Recursion flag variables
    public static Boolean allowBeforeInsert = true;
    public static Boolean allowAfterInsert = true;
    public static Boolean allowBeforeUpdate = true;
    public static Boolean allowAfterUpdate = true;
    public static Boolean allowBeforeDelete = true;
    public static Boolean allowAfterDelete = true;
    public static Boolean allowAfterUndelete = true;
    
    // trigger service instance
    public TriggerService svr;
    
    /*
     * register object service instance to dispatcher instance
     */
    public void registerService(TriggerService svr) {
        // TODO do we need multi service instances inside dispacher?
        this.svr = svr;
    }
    
    /*
     * call from TriggerService instance
     */
    public void processTriggerEvents(){
        
        if (byPassTrigger) { return; }
        
        if(allowBeforeInsert || allowAfterInsert 
            || allowBeforeUpdate || allowAfterUpdate 
            || allowBeforeDelete || allowAfterDelete 
            || allowAfterUndelete)
            initializeVariables();

        if(Trigger.isBefore && Trigger.isInsert){
            
            if(allowBeforeInsert){

                doBeforeInsert();       
                allowBeforeInsert = false;
            }

        }
        else if(Trigger.isAfter && Trigger.isInsert){

            if(allowAfterInsert){

                doAfterInsert();
                allowAfterInsert = false;
            }   
                
        }
        else if(Trigger.isBefore && Trigger.isUpdate){

            if(allowBeforeUpdate){

                doBeforeUpdate();
                allowBeforeUpdate = false;
            }

        }
        else if(Trigger.isAfter && Trigger.isUpdate){

            if(allowAfterUpdate){

                doAfterUpdate();
                allowAfterUpdate = false;
            }

        }
        else if(Trigger.isBefore && Trigger.isDelete){

            if(allowBeforeDelete){

                doBeforeDelete();
                allowBeforeDelete = false;
            }

        }
        else if(Trigger.isAfter && Trigger.isDelete){

            if(allowAfterDelete){

                doAfterDelete();
                allowAfterDelete = false;
            }

        }
        else if(Trigger.isAfter && Trigger.isUndelete){

            if(allowAfterUndelete){

                doAfterUndelete();
                allowAfterUndelete = false;
            }
        }
    }
    
    /*
     * initialize trigger context variables to service instance
     */
    public void initializeVariables() {
        
        this.svr.oldList = Trigger.old;
        this.svr.oldMap = Trigger.oldMap;
        this.svr.newList = Trigger.new;
        this.svr.newMap = Trigger.newMap;

        this.svr.isBefore = Trigger.isBefore;
        this.svr.isAfter = Trigger.isAfter;
        this.svr.isInsert = Trigger.isInsert;
        this.svr.isUpdate = Trigger.isUpdate;
        this.svr.isDelete = Trigger.isDelete;
        this.svr.isUndelete = Trigger.isUndelete;
    }
    
    // Below methods should be overrided in ObjectDispatcher class
    public virtual void doBeforeInsert(){ }
    public virtual void doAfterInsert(){ }
    public virtual void doBeforeUpdate(){ }
    public virtual void doAfterUpdate(){ }
    public virtual void doBeforeDelete(){ }
    public virtual void doAfterDelete(){ }
    public virtual void doAfterUndelete(){ }
    // END 
    
}

Kindly help.

Many thanks in advance​
monsterloomismonsterloomis
Okay - so how about debugging the entire oldMap to see what it does contain? That "null" says the contents differ from what you're expecting. 

The query starting on line 18 that is retrieving orders appears to be such that it COULD retrieve order that aren't in the trigger's scope, so you would need to anticipate that oldMap may not contain the id for every order in acc.orders. That means the null check is still relevant, so you'll still want that. I'd also expect to see more than one entry in the debug log, and many of them would be null, because you're updating one order, so the trigger scope is one order - and that query is designed to return all orders under the account, correct?

Maybe also debug "updateOppties" to see what's in there? Does line 48 produce any output that isn't null?
Maria22Maria22
Hi MonsterLoomies,

Below is the debug logs for updateOppties
 
18:42:11:252 USER_DEBUG [151]|DEBUG|updateOpportunityStage Opportunity: Opportunity:{AccountId=0019E00000WqbGMQAZ, Id=0069E000009OGQZQA4, Name=Binnu Paaji, CloseDate=2018-10-03 00:00:00, StageName=Upgrade-Closed Won, Oracle_Order_Number__c=1, Status_Last_Changed_On__c=2018-10-03 14:01:57}



 
Maria22Maria22
Below is the screenshot taken from my debug logs

Oldmap returning null but updateoppties returns some values

Debug Logs
 Is there any way we can connect online and can discuss this issue. I am really bothering now and afraid about the same.

If possibel we can connect in some way and discuss.

Kindly help.

Mnay thanks in advance
 
Maria22Maria22
Hi MonsterLoomis,

Yes you are damn correct.I designed the query to return all the orders of record type=upgrade order for an accounnt and if any of those upgrade order status changed to Cancelled then all the opportunities linked to that account where opportunity record type=Upgarde Opportunity and status of opportunity is not equals to closed won will becomes Closed Lost.

Kindly help.

Many thanks in advance
monsterloomismonsterloomis
Is there a reason for adding every opportunity to updateOppties? It seems like you would only want to add the ones that have actually met your criteria for updating. Try moving "updateOppties.add(opps)" from the end of the for loop to INSIDE each block in the line after the Oracle Order Number update.

I think what will happen when you start adding only the opportunities that should be updated is that you'll see that at the end of the loop, System.debug('updateOppties: ' + updateOppties) will show an empty list, which is why nothing is being changed (and why you're probably almost ready to put your fist through your laptop screen).

I don't think there's anything spooky going on, but I can tell you that what I would do output the entire opportunity list that's being returned by that query and watch them walk through that bit of logic. I strongly recommend using checkpoints in the dev console to help in diagnosing this further: https://trailhead.salesforce.com/en/modules/apex_basics_dotnet/units/debugging_diagnostics. It will save you a LOT of time with that exercise.

I've been there with the frustration and the pressure. As for chatting, I love a good bug hunt, but I'm afraid I'm mostly hanging out with family today. A little dev boards action I can handle, but I don't think I'm up for a chat. :-) I will check in from time to time, though, to see how it's going and will help if I can.
monsterloomismonsterloomis
Better link to checkpoints docs: https://trailhead.salesforce.com/modules/developer_console/units/developer_console_checkpoints
Maria22Maria22
Many thanks Monsterloomis for your response.Yes you are correct you should hang out with your family.Sorry for all the inconveniences caused.
I will try what you said and update in same tread if I get any success or even not then also I will update.

Hope for the best and best of luck to me.

Mnay thanks
monsterloomismonsterloomis
No problem at all! Good luck, and let us know how it goes.
Maria22Maria22
Hi Monsterloomis,

I have fixed my code and is working fine as per requirement. I needed to refactor my code and now its working fine. Currently I am facing one issue.When I am changing upgrade order status to Cancelled then I am receiving error like
Error:Apex trigger OrderTrigger caused an unexpected exception, contact your administrator: OrderTrigger: execution of BeforeUpdate caused by: System.DmlException: Update failed. First exception on row 0 with id 0069E000009Oq5sQAC; first error: FIELD_CUSTOM_VALIDATION_EXCEPTION, Oracle Status is required.: []: Class.OrderTriggerService.updateOpportunityStage:

Below is my new service class whoich is working fine
 
public with sharing class OrderTriggerService extends TriggerService {
    
    
    
    
    // Update opps to Closed-Won/Closed-Lost based on Orders status changed Canceled/Closed and copy Oracle Order No from Orders to Opps
    public void updateOpportunityStage() {
         Map<Id,Order> orderOldMap =(Map<Id,Order>)oldMap;
        Map<Id, Order> accountIdToOrderId = new Map<Id, Order>();
        //Map<Id,Opportunity> opportunityOldMap = (Map<Id,Opportunity>)oldMap;
        DateTime statusChange= System.now()-90;
        
        // get related account id <-> order mapping
        for (Order newOrder : (List<Order>) newList) {
            if (newOrder.Account_Record_Type_Name__c == 'Recipient' 
                && newOrder.Record_Type_Dev_Name__c == 'Upgrade_Order'
                && newOrder.Status != orderOldMap.get(newOrder.Id).Status
                && (newOrder.Status == 'Cancelled' || newOrder.Status == 'Closed')) {
                accountIdToOrderId.put(newOrder.AccountId, newOrder);
            }
        }
        
        // query related accounts with opportunity
        List<Account> accounts = [select id, 
                                      (select Id,Name,closedate,amount,StageName,Oracle_Order_Number__c,Status_Last_Changed_On__c,Oracle_Status__c
                                       from opportunities where Record_Type_Dev_Name__c = 'Upgrade_Opportunity') 
                                  from Account where id in: accountIdToOrderId.keySet()];
        
        
        List<Opportunity> opportunityUpdate = new List<Opportunity>();
        for (Account acc : accounts) {
            Order order = accountIdToOrderId.get(acc.id);
            if (order.Status == 'Cancelled') {
                for (Opportunity opp : acc.opportunities) {
                    if(opp.StageName<>Constants.Opportunity_Upgrade_Closed_Won){
                         opp.StageName=Constants.Opportunity_Upgrade_Closed_Lost; 
                         opp.Oracle_Order_Number__c=order.Oracle_Order_Number__c;
                         opportunityUpdate.add(opp);
                    }
                    
                }
            }
            
             if (order.Status == 'Closed') {
                for (Opportunity opp : acc.opportunities) {
                    if(opp.StageName<>Constants.Opportunity_Upgrade_Closed_Won  || (opp.StageName==Constants.Opportunity_Upgrade_Closed_Lost && opp.Status_Last_Changed_On__c>= statusChange)){
                        opp.StageName=Constants.Opportunity_Upgrade_Closed_Won; 
                        opp.Oracle_Order_Number__c=order.Oracle_Order_Number__c;
                        opportunityUpdate.add(opp);
                    }
                }
            }
        } 
        
        if (opportunityUpdate.isEmpty() == false) { update opportunityUpdate; }			
    }
}

I checked in sandbox and found there is a Validation rule on Opportunity object for  "Oracle_Status__c" field.
Validation rule:
AND( ISPICKVAL( Oracle_Status__c , ''), 
ISPICKVAL(StageName , 'Upgrade - Closed Won') )

which means when changing a status or stagename of opportunity becomes Upgrade-Closed Won then oracle status field shpould not be blank.

But in my requirement​ there may be chance an oppty have some value with oracle status field as null.and if my user change status of order to cancel then trigger will try to update oppty to Upgrade Closed won and hits error as field exception because oracle status field may be blank for that oppty record.

Can you help me to track this in code.How can we track the same in my code. Is there any way I can respect VR in my apex class shown above.

I hope you will respond and help me as you were doing till now.

Kindly help.

Many thanks in advance
monsterloomismonsterloomis
So, you can work around this validation rule in code easily enough: just provide a status around line 47/48. If you provide a value, it won't choke on the validation rule. You could put in the default status, for example.

BUT, consider what you're doing there. When a user confronts that rule, they can make a decision about what value to put in there, if that's an appropriate decision for their user and that record. When you hit it programmatically, you can either code around it, or you can allow the user to encounter the error.

If the error were on orders, I'd recommend using addError(), but in this trigger, Opportunities aren't in the context, so that won't work. Ultimately, this is a data quality issue, which leaves your choices fairly straightforward:
  • Allow the error to occur, and provide the users documentation for what to do when they encounter it.
  • Figure out an appropriate "oracle status" for a default behavior (or replicate the business logic for assigning it a value), and then assign it in lines 47/48-ish so they never encounter the error
  • Use a try/catch statement and just swallow/debug the error - which I definitely do NOT recommend. Including in the interest of completeness. :-)
Hope that helps. Seems like it's getting pretty close now. 
monsterloomismonsterloomis
Ever get this resolved? Curious to know how it came out. If it helped, you may want to mark it as resolved so others can find it later.