[SalesForce] Comparing trigger.old and trigger.new – both hold same value for a Field

Scenario:
I have a trigger that will fire whenever a Primary Campaign Changes on an Opportunity by comparing trigger.old and trigger.new values of CampaingID,

Issue:
When I changed the campaign on the opportunity to point to a different campaign, the trigger did not fire when I debugged it I found that trigger.old value and trigger.new value of Campaign are the SAME
(I checked the logs there is no recursion problem all classes are called only once).

Any reason why this might be happening?

SideNote : I have WFRs firing on Opportunity as well

trigger MasterOpportunityTrigger on Opportunity(after update) 
{

    if (Trigger.isUpdate) 
    {
        if(triggerHelper.isAfterUpdateFirstRun == true)
            triggerHelper.isAfterUpdateFirstRun = false; 

        //Campaign changed for Opportunity update the old campaign
        CampChangeRollup Camp = new CampChangeRollup(trigger.oldmap,trigger.newmap);
        Camp.FCampRollUp();
        system.debug('*************CampChangeRollup Class Execute After Update');
    }        
}

public class CampChangeRollup 
{
    private Map<Id, Opportunity> oldOpps;
    private Map<Id, Opportunity> newOpps;
    private Map<Id,Opportunity> CampIdOpp = new Map<Id,Opportunity>();
    private list<Opportunity> Opplst = new list<Opportunity>();
    private list<Opportunity> delOpplst = new list<Opportunity>();

    public CampChangeRollup(Map<Id, Opportunity> oldTriggerOpps, Map<Id, Opportunity> newTriggerOpps) 
    {
        if(oldTriggerOpps != Null)
            oldOpps = oldTriggerOpps;
        else
            oldOpps = new Map<Id,Opportunity>();

        if(newTriggerOpps != Null)
            newOpps = newTriggerOpps;    
        else
            newOpps = new Map<Id,Opportunity>();


        for(Opportunity opp : newOpps.values())
        {    
            // Access the "old" record by its ID in Trigger.oldMap
            // Opportunity oldOpp;           
            Id newOppCampId;
            Id oldOppCampId;

            if(!oldOpps.isEmpty())
                oldOppCampId = oldOpps.get(opp.Id).CampaignId;

            if(opp.CampaignId != null)
                newOppCampId = opp.CampaignId;

            system.debug('oldOppCampId'+' '+oldOppCampId+' '+'newOppCampId'+' '+newOppCampId);

            //Only when Campaign is changed 
            if(newOppCampId != oldOppCampId) 
                CampIdOpp.put(oldOppCampId,opp);     
        }
    }

    public void FCampRollUp()
    {
        try
        {
            if(CampIdOpp!= null)
            {
                for(Campaign Camp : [select id from Campaign where id =:CampIdOpp.keyset()])
                {
                    Id OppId = CampIdOpp.get(Camp.id).id;
                    String soql = CloningUtils.getCreatableFieldsSOQL('Opportunity','id = :OppId ');
                    system.debug('returned SOQL'+ soql);            
                    Opportunity O = (Opportunity)Database.query(soql);
                    Opportunity Od = O.clone(false, true);
                    Od.CampaignId = Camp.Id; 
                    Od.Amount = 1;               
                    system.debug('Opportunity tobe deleted'+ Od.id+Od.name+Od.CampaignId);
                    Opplst.add(Od);
                } 

                if(Opplst.size()>0)
                { 
                    insert Opplst;   
                    system.debug('Opplst'+Opplst);
                    system.debug('Dummy Opportunity Inserted for Campaign Changed Opportunity'+ Opplst+Opplst[0].id+Opplst+Opplst[0].CampaignId);                          
                }
            }
        } catch(Exception error){ }
    }
}

Best Answer

One of the possible scenario here is usage of multiple triggers with DML. Let's look at the example below.

Trigger B:

trigger OpportunityTriggerB on Opportunity (after update) {
    if(Trigger.isUpdate && Trigger.isAfter){
        OpportunityTriggerHandlerB.onAfterUpdate( Trigger.oldMap, Trigger.newMap);
    }
}

With its handler:

public with sharing class OpportunityTriggerHandlerB {
    public static Boolean firstRunAfterUpdate = true;
    private static Integer counter = 0;

    public static void onAfterUpdate(map<Id,Opportunity> oldMap, map<Id,Opportunity> newMap) {
        system.debug('counter: ' + counter++);

        if (firstRunAfterUpdate) {
            firstRunAfterUpdate = false;

            for (Opportunity opp : newMap.values()){
                if (oldMap.get(opp.Id).StageName != opp.StageName) {
                    system.debug('OpportunityTriggerHandler magic');
                } else {
                    system.debug('OpportunityTriggerHandler no magic');
}   }   }   }   }

Due to Stage update, will get following logs:

|DEBUG|counter: 0
|DEBUG|OpportunityTriggerHandler magic

But if we add new Trigger for Opportunity:

trigger OpportunityTriggerC on Opportunity (after update) {
    if(Trigger.isUpdate && Trigger.isAfter){
        OpportunityTriggerHandlerC.onAfterUpdate( Trigger.oldMap, Trigger.newMap);
    }
}

Handler :

public with sharing class OpportunityTriggerHandlerC {
    public static Boolean firstRunAfterUpdate = true;
    private static Integer counter = 0;

    public static void onAfterUpdate(map<Id,Opportunity> oldMap, map<Id,Opportunity> newMap) {

        system.debug('C counter: ' + counter++);
        for (Opportunity opp : newMap.values()){
            system.debug('C old StageName: ' + oldMap.get(opp.Id).StageName + ' new StageName: ' + opp.StageName );
        }

        if (firstRunAfterUpdate) {
            firstRunAfterUpdate = false;

            updateProcedure(newMap.values());
        }
    }

    private static void updateProcedure(list<Opportunity> opps){
        list <Opportunity> oppToUpdate = new list<Opportunity>();
        for(Opportunity opp :opps) {
            oppToUpdate.add(new Opportunity(id = opp.id) );
        }
        system.debug('C OpportunityTriggerHandler update');
        update oppToUpdate;
    }
}

Console log:

|DEBUG|C counter: 0
|DEBUG|C old StageName: Qualification new StageName: Negotiation/Review
|DEBUG|C OpportunityTriggerHandler update
|DEBUG|C counter: 1
|DEBUG|C old StageName: Negotiation/Review new StageName: Negotiation/Review
|DEBUG|counter: 0
|DEBUG|OpportunityTriggerHandler no magic
|DEBUG|counter: 1

As we can see there is no Magic for B Handler. DML we've made in trigger C has created new trigger event, and this event has higher priority that DML from UI. (I suppose Salesforce is using Stack for this.) That's why we have same field values while they're arriving to Trigger B If you disable recursion functionality, you'll see that values from UI arrive to trigger B with counter = 1. Just make sure you have one trigger and as many handlers as you want.

I thought that order is dependent on trigger time creation or its name, but it's not