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
Snita LalSnita Lal 

Firing trigger only when certain action is executed

Hi,

We have a fully functional trigger that fires when a new Opportunity is created from our custom objects and the trigger will prepopulate the Account lookup field and avoid the user assigning manually to potential the wrong account. 

Problem arises that we have to disable the required population of the account field on the layout to stop it hitting the validation before saving at which point the trigger will fire.

We have tried to write some validation rules that work tandem with this but we seem to be in a bit of a loop.

Please can you guys maybe suggest the best course of action as without taking a step back I don't see us making much progress.

We do believe that maybe firing the trigger when only a certain action is excuted but we wouldn't know how to go about writing this into our trigger below, your help would be greatfully appreciated and any advice you can offer we would take on board.

Thanks,

Snita

Trigger UpdateAccountLookup on Opportunity (before insert,before update) {

Set<id> setSiteids=new set<id>();
map<Id, String> mapsiteIdToopptId = new map<Id, String>();
Map<Id, String> mapOpptIdToAccId = new Map<Id, String>();


list<account> acclst = new list<account>();

    For(Opportunity o : trigger.new){
        
        setSiteids.add(o.Site_Location__c);
        mapsiteIdToopptId.put(o.Site_Location__c, o.Id);
    }
    
    for(Site__c objSite : [Select Id , Parent_Account__c from Site__c where Id IN :setSiteids ])
    {
        mapOpptIdToAccId.put(mapsiteIdToopptId.get(objSite.Id), objSite.Parent_Account__c);
    }
    
    for(Opportunity o : trigger.new)
    {
        o.AccountId = mapOpptIdToAccId.get(o.Id);
    }
}


 
Best Answer chosen by Snita Lal
Agustina GarciaAgustina Garcia
You had 2 options. Once you have all Account Ids, you can make a query and retrie it in a map in order to look for that later.

Or you can do it directly in your Site query:
 
trigger UpdateAccountLookup on Opportunity (after insert,after update)
{
    Set<id> setSiteids=new set<id>();
    Map<Id, String> mapsiteIdToopptId = new map<Id, String>();
    Map<Id, Id> mapOpptIdToAccId = new Map<Id, Id>();
    Map<Id, String> mapAccountIdToAccountName = new Map<Id,String>();


    if(trigger.isAfter)
    {
        for(Opportunity o : trigger.new)
        {
            setSiteids.add(o.Site_Location__c);
            mapsiteIdToopptId.put(o.Site_Location__c, o.Id);
        }

        for(Site__c objSite : [Select Id , Parent_Account__c, Parent_Account__r.Name from Site__c where Id IN :setSiteids ])
        {
            mapOpptIdToAccId.put(mapsiteIdToopptId.get(objSite.Id), objSite.Parent_Account__c);
            mapAccountIdToAccountName.put(objSite.Parent_Account__c, objSite.Parent_Account__r.Name);
        }

        for(Opportunity o : trigger.new)
        {
            if(o.AccountId != mapOpptIdToAccId.get(o.Id))
            {
                String accName = mapAccountIdToAccountName.get(mapOpptIdToAccId.get(o.Id));
                o.AccountId.addError('Wrong account. It should be Site one: ' + accName);
                //o.AccountId.addError('Wrong account. It should be Site one: ' + mapOpptIdToAccId.get(o.Id));
            }
        }
    }
}

 

All Answers

Agustina GarciaAgustina Garcia
Hi,

Why don't you try to change the place where the magic take the place?

Instead of tryting to populate the Account in the Opportunity trigger, create a trigger related to your custom object and in the after insert / update create the new Opportunity with its proper account. So that, when the insert or update DML is executed, the UI validation will take place and no error will be thrown.
 
trigger myTrigger on myObject__c (after insert)
{

    if(trigger.isAfter)
    {
        List<Opporunity> newOpportunities = new List<Opportunity>();

        for(myObject__c myObj :trigger.new)
        {
           Opportunity newOpp = new Opportunity();
           
           newOpp.Account = myAccountId;
           //populate other fields

           newOpportunities.add(newOpp);
	   
        }
	
	insert newOpportunities;
    }

}

I hoope this helps
Snita LalSnita Lal
Hi Augustina,

Appreciate your reply and support here. Believe we've put the correct fields in the correct place but we seem to an illegal assignment from the changes. Trigger has been put on the Custom Object (site__c) and the field already popoulate with the Account on this object is (Parent_Account__c) which we've entered in the mapping. Like your logic here and never thought to do it this way.

I've pasted what we done below any further support would be gratefully appreciated.
 
trigger UpdateOpportunityAccount on Site__c (after insert)
{

    if(trigger.isAfter)
    {
        List<Opportunity> newOpportunities = new List<Opportunity>();

        for(site__c s :trigger.new)
        {
           Opportunity newOpp = new Opportunity();
           
           newOpp.Account = s.Parent_Account__c;

           newOpportunities.add(newOpp);
       
        }
    
    insert newOpportunities;
    }

}

Thanks,

Snita
Agustina GarciaAgustina Garcia
Snita, sorry but the field name was wrong. Find below proper code. I have also included other Opportunity required fields, so take them into accout and set your own values.
 
trigger UpdateOpportunityAccount on Site__c (after insert)
{
    if(trigger.isAfter)
    {
        List<Opportunity> newOpportunities = new List<Opportunity>();

        for(site__c s : trigger.new)
        {
           Opportunity newOpp = new Opportunity();
           
           newOpp.AccountId = s.Parent_Account__c;
           
           //Other required fields
           newOpp.Name = 'Opp From Trigger';
           newOpp.StageName = 'Closed Won';
           newOpp.CloseDate = System.today();
           
           newOpportunities.add(newOpp);
        }    
        insert newOpportunities;
    }
}

Hope this time helps.
 
Snita LalSnita Lal
Augustina - No problem, thanks for your patience, I'm new to this :)

Ultimately now we've added this and trigger saves perfectly without errors. However, when we insert the new opportunity against the site, nothing is updated by the trigger and the validations kick in not allowing the record to be saved. Like I said early probably something we've done wrong.

Thanks for your support 

Snita
 
Agustina GarciaAgustina Garcia
I'm afraid I'm not getting you. Above trigger related to your custom Site object what does is to create a new Opportunity after creating a Site record, and it uses Parent_Account field value to set Opportunity Account field. I have already checked in my org and it is working fine.

If you are not able to create a new Opportunity after creating a Site, look at Opportunity and check you don't have any unnecesary trigger, or if you have any other required field that you have not included in the trigger.

If that is not your case, could you share your error with me?
Snita LalSnita Lal
Apologies Agustina - We seem to have got cross wires, probably the fact that we didnt explain ourselves properly and yes the trigger does work the way you intended it to.

Ultimately what we need is when a new opportunity is created from a site (that in most cases will already exists) that the Account lookup is prepopulate with the Parent Account ID already linked to the site. We got our trigger functional and could by removing the validation on the Layout for the Account get the Account field to populate upon saving but considering these two things:
  • Validation for the Account needs to remain in place to ensure we don't have Opportunities created without an Account.
  • Secondly it really confuses the user that the account isn't populate when they're filling in the detail
With these two things in mind we were attempting to either:
  • Prepopulate the field on insert so its visible on edit
  • Or change the trigger behavour so that it only fires on this certain action or ignores the valdation upon saving. 
Hopefully this explain our requirement better and apologies for any confusion caused. 

Really appreciate your support 

Snita
Agustina GarciaAgustina Garcia
Snita,

I think I got it. 

When you say Opportunity is created from a site, it means that it can be created after saving the Site__c record automatically (solved with the above trigger) or it could be created somewhere else. On the other side, you are also worried about those Opportunities that are already cresatd and for any reason the user edit them. In any case, I would guess that there is a fild that relates the site with the Opportunity, so we will be able to see they are linked somehow.

Regarding your question, pre-populate Account field on Opportunity with the Site Parent one, that is not possible on Standard UI. If you have a visualforce page, yes, but my guess is that you don't want to change the UI. The only thing that you can do is create a trigger and in the after insert / update (why in after instead of before? look at this post http://andyinthecloud.com/2015/04/19/considerations-placing-validation-code-in-an-apex-triggers/ ), check if the Account field value is the expected one. If not, show an error message saying that the value should be the one you need. This will stopp you to save an Opportunity without Account and also if the Account is not the one you want. ( https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_methods_system_sobject.htm#apex_System_SObject_addError )

Please let me know if I have misunderstood something
Snita LalSnita Lal
Augustina,

Excellent thanks for taking the time out to look into our issue further. 

We did make the change to our original trigger (after insert/after update) but unfortunately we still hit an error on the record, this specifically...

Error: Invalid Data. 
Review all error messages below to correct your data.
Apex trigger UpdateAccountLookup caused an unexpected exception, contact your administrator: UpdateAccountLookup: execution of AfterInsert caused by: System.FinalException: Record is read-only: Trigger.UpdateAccountLookup: line 23, column 1


Original trigger code we have used
trigger UpdateAccountLookup on Opportunity (after insert,after update) {

Set<id> setSiteids=new set<id>();
map<Id, String> mapsiteIdToopptId = new map<Id, String>();
Map<Id, String> mapOpptIdToAccId = new Map<Id, String>();


list<account> acclst = new list<account>();

    For(Opportunity o : trigger.new){
        
        setSiteids.add(o.Site_Location__c);
        mapsiteIdToopptId.put(o.Site_Location__c, o.Id);
    }
    
    for(Site__c objSite : [Select Id , Parent_Account__c from Site__c where Id IN :setSiteids ])
    {
        mapOpptIdToAccId.put(mapsiteIdToopptId.get(objSite.Id), objSite.Parent_Account__c);
    }
    
    for(Opportunity o : trigger.new)
    {
        o.AccountId = mapOpptIdToAccId.get(o.Id);
    }
}
Unfortunately our lack of knowledge and experience fails us here and I certainly don't think you have minunderstood our requirement, more that we can't tangibly get your explaination. 

Thanks again Augustina,

Snita
 
Agustina GarciaAgustina Garcia
Actually my advice was to not change the value automatically from Opportunity trigger. As you say, the end user should be aware of the change, so instead of just modify it, I would show an error message if the value is not the expected one. Find below some lines
 
trigger UpdateAccountLookup on Opportunity (after insert,after update)
{
	Set<id> setSiteids=new set<id>();
	Map<Id, String> mapsiteIdToopptId = new map<Id, String>();
	Map<Id, String> mapOpptIdToAccId = new Map<Id, String>();


	if(trigger.isAfter)
	{
		for(Opportunity o : trigger.new)
	    {
	       	setSiteids.add(o.Site_Location__c);
	       	mapsiteIdToopptId.put(o.Site_Location__c, o.Id);
	    }

	    for(Site__c objSite : [Select Id , Parent_Account__c from Site__c where Id IN :setSiteids ])
	    {
	    	mapOpptIdToAccId.put(mapsiteIdToopptId.get(objSite.Id), objSite.Parent_Account__c);
	    }

	    for(Opportunity o : trigger.new)
	    {
	    	if(o.AccountId != mapOpptIdToAccId.get(o.Id))
	    	{
	    		//Show error at top level
	    		//o.addError('Wrong account. It shoudl be Site one ' + mapOpptIdToAccId.get(o.Id));
	    		
	    		//Show error below the field
	    		o.AccountId.addError('Wrong account. It shoudl be Site one ' + mapOpptIdToAccId.get(o.Id));
	    	}
	    }
	}
}

The result should be something like this. Remeber you can enhance the code, and show the Account Name instea of its Id.

User-added image
Snita LalSnita Lal
Thanks again Augustina, this works brilliantly and we get the correct error showing too.

We've tried to amend the code to get the Account Name to pull into the error message instead of the Account Id, but run into errors when saving. Would we need to amend the whole trigger to reference the Account Name instead of Account Id?
 
Agustina GarciaAgustina Garcia
You had 2 options. Once you have all Account Ids, you can make a query and retrie it in a map in order to look for that later.

Or you can do it directly in your Site query:
 
trigger UpdateAccountLookup on Opportunity (after insert,after update)
{
    Set<id> setSiteids=new set<id>();
    Map<Id, String> mapsiteIdToopptId = new map<Id, String>();
    Map<Id, Id> mapOpptIdToAccId = new Map<Id, Id>();
    Map<Id, String> mapAccountIdToAccountName = new Map<Id,String>();


    if(trigger.isAfter)
    {
        for(Opportunity o : trigger.new)
        {
            setSiteids.add(o.Site_Location__c);
            mapsiteIdToopptId.put(o.Site_Location__c, o.Id);
        }

        for(Site__c objSite : [Select Id , Parent_Account__c, Parent_Account__r.Name from Site__c where Id IN :setSiteids ])
        {
            mapOpptIdToAccId.put(mapsiteIdToopptId.get(objSite.Id), objSite.Parent_Account__c);
            mapAccountIdToAccountName.put(objSite.Parent_Account__c, objSite.Parent_Account__r.Name);
        }

        for(Opportunity o : trigger.new)
        {
            if(o.AccountId != mapOpptIdToAccId.get(o.Id))
            {
                String accName = mapAccountIdToAccountName.get(mapOpptIdToAccId.get(o.Id));
                o.AccountId.addError('Wrong account. It should be Site one: ' + accName);
                //o.AccountId.addError('Wrong account. It should be Site one: ' + mapOpptIdToAccId.get(o.Id));
            }
        }
    }
}

 
This was selected as the best answer
Snita LalSnita Lal
Thanks agian for all your help Augustina this is all working perfectly.