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
Alaric WimerAlaric Wimer 

Trigger to set Opportunity Name is not working, need help please!

I trying to create an Opportunity trigger that sets the name of a new Opportunity to "AccountName - AccountCity, AccountState" for example ("Test Corp. - San Franciso, CA").

What's difficult is that I have a custom checkbox field on the Opportunity named "Same as Mailing Address?". When this checkbox is checked, it sets the custom Opportunity address fields "Project Address", "Project City", "Project State" and "Project Zip Code" to the Account's custom fields "Mailing Address", "Mailing City", "Mailing State", "Mailing Zip Code" via process builder. But the Opportunity name doesn't update right away.

The goal is to have the user create an Opportunity, check the box, hit Save, and then The opportunity name is automatically displayed correctly.

However, what happens is that only the Account name shows up as the Opportunity name while the Project Address fields are correctly filled out. Then after one edit of any kind, the name changes to the correct format. 

I want it to show the correct naming convention after the user hits Save for the first time.

Screenshots of Opportunity create screen:

User-added image
After Save, this is what displays in the Opportunity record:

User-added image

Then after any update, it changes to the correct name:

User-added image
 

Here's my trigger I'm working on that doesn't work.

trigger NameOpportunity on Opportunity (before insert) {
     Set<Id> AccountIds = new Set<Id>(); // Initialize a list of Ids
     String accName; // Initialize account name variable
     String newOppName; // Initialize final opportunity name variable

    // Add an account ID to the list for every opportunity caught by trigger
    for (Opportunity op : Trigger.new) {
      AccountIds.add(op.AccountId);
    }

    // Create a map of Ids and account, querying the name from account records whose Id is in the AccountIds list generated earlier
    Map<Id, Account> accountMap = new Map<Id, Account>([SELECT Name FROM Account WHERE Id IN :AccountIds]);
    System.debug(accountMap);

    // Update the Opportunity name for every opportunity entering the trigger that has and Account, and "Same as Mailing Address" checked
    for (Opportunity myOpp : Trigger.new) {
            if (myOpp.AccountId != null && myOpp.Same_as_Mailing_Address__c == true) {
                accName = accountMap.get(myOpp.AccountId).Name;
                // set naming conventions
                newOppName = accountMap.get(myOpp.AccountId).Name + ' - ' + myOpp.Project_City__c + ', ' + myOpp.Project_State__c;
                myOpp.Name = newOppName;
            }
        }
}

Any help is greatly appreciated thank you!
Best Answer chosen by Alaric Wimer
Alaric WimerAlaric Wimer

I decided to remove the process from process builder and add all of the logic to a trigger. Everything works now. Thee issue with the trigger and test class was the following section, I just removed "op.Same_as_Mailing_Address__c = true" from the criteria because the process in process builder is deleted:

// Add an account ID to the list for every opportunity caught by trigger
    for (Opportunity op : Trigger.new) {
        if(op.AccountId != null && op.Same_as_Mailing_Address__c == true) {
            AccountIds.add(op.AccountId);
        }
    }


This is what it is now:

// Add an account ID to the list for every opportunity caught by trigger
    for (Opportunity op : Trigger.new) {
        if(op.AccountId != null) {
            AccountIds.add(op.AccountId);
        }
    }

 

All Answers

Ajay K DubediAjay K Dubedi
Hi Alaric,
Please read Order of execution in salesforce that trigger executes first and then process builder.
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers_order_of_execution.htm
So for update the name of Opportunity follow this code:
trigger NameOpportunity on Opportunity (before insert) {
    Set<Id> AccountIds = new Set<Id>(); // Initialize a list of Ids
    Map<Id, Account> accountMap = new Map<Id, Account>();
    
    // Add an account ID to the list for every opportunity caught by trigger
    for (Opportunity op : Trigger.new) {
        if(op.AccountId != null && op.Same_as_Mailing_Address__c == true) {
            AccountIds.add(op.AccountId);
        }
    }
    
    // Create a map of Ids and account, querying the name from account records whose Id is in the AccountIds list generated earlier
    if(AccountIds.size() > 0) {
        accountMap = new Map<Id, Account>([SELECT Name,Project_City__c, Project_State__c FROM Account WHERE Id IN : AccountIds]);
    }
    System.debug(accountMap);
    
    // Update the Opportunity name for every opportunity entering the trigger that has and Account, and "Same as Mailing Address" checked
    if(!accountMap.isEmpty()) {
        for (Opportunity myOpp : Trigger.new) {
            if (myOpp.AccountId != null && myOpp.Same_as_Mailing_Address__c == true && accountMap.get(myOpp.AccountId).Project_City__c !=null && accountMap.get(myOpp.AccountId).Project_State__c != null) {
                myOpp.Name = accountMap.get(myOpp.AccountId).Name + ' - ' + accountMap.get(myOpp.AccountId).Project_City__c + ', ' + accountMap.get(myOpp.AccountId).Project_State__c;
            }
        }
    }
}

I hope you find the above solution helpful. If it does, please mark as Best Answer to help others too.
Thanks,
Ajay Dubedi
Alaric WimerAlaric Wimer

Thank you Ajay, I used your code revision and my test class assertion still doesn't pass. Do you think I should remove the process from process builder and instead add it to this trigger? 

Also, here's my test class:

@isTest
private class NameOpportunityTest {
    @isTest static void methodOne() {
        // 1. Create Account
        Account acc = new Account();
        acc.Name    = 'Test Account';
        acc.Mailing_Address__c = '123 Main St';
        acc.Mailing_City__c = 'Long Beach';
        acc.Mailing_State__c = 'CA';
        acc.Mailing_Zip_Code__c = '90802';
        insert acc;

        // 2. Create Opportunity, Assign Account to Opportunity, Set "Same as mailing address" to true on Opportunity
        Opportunity myOpp = new Opportunity();
        myOpp.Name = 'Test Opportunity';
        myOpp.StageName = 'RFP';
        myOpp.AccountId = acc.Id;
        myOpp.CloseDate = Date.Today();
        myOpp.Same_as_Mailing_Address__c = true;
        insert myOpp;

        // 3. Set correct name of opp
        String oppName = acc.Name + ' - ' + acc.Mailing_City__c + ', ' + acc.Mailing_State__c;

        // 4. Get latest opp info
        Opportunity myUpdatedOpp = [SELECT Name FROM Opportunity WHERE Id = :myOpp.Id LIMIT 1];

        // 5. Assert that the NameOpportunity trigger fired
        System.AssertEquals(oppName, myUpdatedOpp.Name);
    }
}
Alaric WimerAlaric Wimer

I also made one adjustment to your revision. The address fields from the account are actually called "Mailing City" and "Mailing State" instead of "Project City" and "Project State". So my trigger now looks like this:

trigger NameOpportunity on Opportunity (before insert) {
    Set<Id> AccountIds = new Set<Id>(); // Initialize a list of Ids
    Map<Id, Account> accountMap = new Map<Id, Account>();
    
    // Add an account ID to the list for every opportunity caught by trigger
    for (Opportunity op : Trigger.new) {
        if(op.AccountId != null && op.Same_as_Mailing_Address__c == true) {
            AccountIds.add(op.AccountId);
        }
    }
    
    // Create a map of Ids and account, querying the name from account records whose Id is in the AccountIds list generated earlier
    if(AccountIds.size() > 0) {
        accountMap = new Map<Id, Account>([SELECT Name,Mailing_City__c, Mailing_State__c FROM Account WHERE Id IN : AccountIds]);
    }
    System.debug(accountMap);
    
    // Update the Opportunity name for every opportunity entering the trigger that has and Account, and "Same as Mailing Address" checked
    if(!accountMap.isEmpty()) {
        for (Opportunity myOpp : Trigger.new) {
            if (myOpp.AccountId != null && myOpp.Same_as_Mailing_Address__c == true && accountMap.get(myOpp.AccountId).Mailing_City__c !=null && accountMap.get(myOpp.AccountId).Mailing_State__c != null) {
                myOpp.Name = accountMap.get(myOpp.AccountId).Name + ' - ' + accountMap.get(myOpp.AccountId).Mailing_City__c + ', ' + accountMap.get(myOpp.AccountId).Mailing_State__c;
            }
        }
    }
}
Alaric WimerAlaric Wimer
But the issue still remains, the test class assertion is failing.
Alaric WimerAlaric Wimer

So, I've decided to remove the naming process from Process Builder and instead have the trigger do all of the naming. My second test class method assertion is failing, but the first one is passing. Does anyone know why it's failing?

Here's my trigger:

trigger NameOpportunity on Opportunity (before insert, before update) {
    Set<Id> AccountIds = new Set<Id>(); // Initialize a list of Ids
    Map<Id, Account> accountMap = new Map<Id, Account>();
    
    // Add an account ID to the list for every opportunity caught by trigger
    for (Opportunity op : Trigger.new) {
        if(op.AccountId != null && op.Same_as_Mailing_Address__c == true) {
            AccountIds.add(op.AccountId);
        }
    }
    
    // Create a map of Ids and account, querying the name from account records whose Id is in the AccountIds list generated earlier
    if(AccountIds.size() > 0) {
        accountMap = new Map<Id, Account>([SELECT Name,Mailing_City__c, Mailing_State__c FROM Account WHERE Id IN : AccountIds]);
    }
    System.debug(accountMap);
    
    // Update the Opportunity name for every opportunity entering the trigger that has and Account, and "Same as Mailing Address" checked
    if(!accountMap.isEmpty()) {
        for (Opportunity myOpp : Trigger.new) {
            if (myOpp.AccountId != null && 
                myOpp.Same_as_Mailing_Address__c == true && 
                accountMap.get(myOpp.AccountId).Mailing_City__c != null &&
                accountMap.get(myOpp.AccountId).Mailing_State__c != null) {
                    myOpp.Name = accountMap.get(myOpp.AccountId).Name + ' - ' + accountMap.get(myOpp.AccountId).Mailing_City__c + ', ' + accountMap.get(myOpp.AccountId).Mailing_State__c;
            } else if (myOpp.AccountId != null && 
                       myOpp.Project_City__c != null &&
                       myOpp.Project_State__c != null && 
                       myOpp.Same_as_Mailing_Address__c == false) {
                            myOpp.Name = accountMap.get(myOpp.AccountId).Name + ' - ' + myOpp.Project_City__c + ', ' + myOpp.Project_State__c;
            }
        }
    }
}

And here's my test class:
 
@isTest
private class NameOpportunityTest {
    @isTest static void sameAsMailingAddressTrue() {
        // 1. Create Account
        Account acc = new Account();
        acc.Name    = 'Test Account';
        acc.Mailing_Address__c = '123 Main St';
        acc.Mailing_City__c = 'Long Beach';
        acc.Mailing_State__c = 'CA';
        acc.Mailing_Zip_Code__c = '90802';
        insert acc;

        // 2. Create Opportunity, Assign Account to Opportunity, Set "Same as mailing address" to true on Opportunity
        Opportunity myOpp = new Opportunity();
        myOpp.Name = 'Test Opportunity';
        myOpp.StageName = 'RFP';
        myOpp.AccountId = acc.Id;
        myOpp.CloseDate = Date.Today();
        myOpp.Same_as_Mailing_Address__c = true;
        insert myOpp;

        // 3. Set correct name of opp
        String oppName = acc.Name + ' - ' + acc.Mailing_City__c + ', ' + acc.Mailing_State__c;

        // 4. Get latest opp info
        Opportunity myUpdatedOpp = [SELECT Name FROM Opportunity WHERE Id = :myOpp.Id LIMIT 1];

        // 5. Assert that the NameOpportunity trigger fired
        System.AssertEquals(oppName, myUpdatedOpp.Name);
    }
    @isTest static void sameAsMailingAddressFalse() {
        // 1. Create Account
        Account acc             = new Account();
        acc.Name                = 'Test Account';
        acc.Mailing_Address__c  = '123 Main St';
        acc.Mailing_City__c     = 'Long Beach';
        acc.Mailing_State__c    = 'CA';
        acc.Mailing_Zip_Code__c = '90802';
        insert acc;

        // 2. Create Opportunity, Assign Account to Opportunity, Set "Same as mailing address" to false on Opportunity
        Opportunity myOpp      = new Opportunity();
        myOpp.Name             = 'Test Opportunity';
        myOpp.StageName        = 'RFP';
        myOpp.AccountId        = acc.Id;
        myOpp.CloseDate        = Date.Today();
        myOpp.Same_as_Mailing_Address__c = false;
        myOpp.Project_City__c            = 'Anywhere';
        myOpp.Project_State__c           = 'NY';
        insert myOpp;

        // 3. Set correct name of opp
        String oppName = acc.Name + ' - ' + myOpp.Project_City__c + ', ' + myOpp.Project_State__c;

        // 4. Get latest opp info
        Opportunity myUpdatedOpp = [SELECT Name FROM Opportunity WHERE Id = :myOpp.Id LIMIT 1];

        // 5. Assert that the NameOpportunity trigger fired
        System.AssertEquals(oppName, myUpdatedOpp.Name);
    }
}

 
Alaric WimerAlaric Wimer

I decided to remove the process from process builder and add all of the logic to a trigger. Everything works now. Thee issue with the trigger and test class was the following section, I just removed "op.Same_as_Mailing_Address__c = true" from the criteria because the process in process builder is deleted:

// Add an account ID to the list for every opportunity caught by trigger
    for (Opportunity op : Trigger.new) {
        if(op.AccountId != null && op.Same_as_Mailing_Address__c == true) {
            AccountIds.add(op.AccountId);
        }
    }


This is what it is now:

// Add an account ID to the list for every opportunity caught by trigger
    for (Opportunity op : Trigger.new) {
        if(op.AccountId != null) {
            AccountIds.add(op.AccountId);
        }
    }

 
This was selected as the best answer