2

I'm really new to this platform (one week barely, although the programming logic is the same for everything) and I'm not able to solve this.

I have added two new choices to the Type picklist of the Account object "First" and "Last", When a pick "First" I need to set the Account Address from the first contact related to that account, and when "Last" the last address from the last contact related to that account.

This is what the senior Developer ask me to do:

Create two Types in account = FIRST and LAST.

I need something to pass the contact information to the account (mailing = billing).

  • If the type of account is FIRST -> it will take the information of the first contact related to the account. The first contact is always the first one created, the account will always take the information from it.

  • If the type of account is LAST -> it will take the information of the last contact related to the account. The last contact is always the last one created, the account will always take the information from it.

This was my previous code

trigger UpdateAddress on Account (before insert, before update) {

Set<String> firstAccountIds = new Set<String>();
Set<String> lastAccountIds = new Set<String>();

List<Account> accList = new List<Account>();
List<Contact> contactList = new List<Contact>();

Account acc;
Map<String, String> mapBillingStreet = new Map<String, String>();
Map<String, String> mapBillingCity = new Map<String, String>();
Map<String, String> mapBillingState = new Map<String, String>();
Map<String, String> mapBillingCountry = new Map<String, String>();
Map<String, String> mapBillingPostalCode = new Map<String, String>();

    for(Account acc : trigger.new) {
        if(acc.Type == 'First') {
            firstAccountIds.add(acc.Id);

        } else if (acc.Type == 'Last') {
            lastAccountIds.add(acc.Id);
        }
    }

for(Contact firstContact: [SELECT Id, AccountId, MailingStreet, MailingCity, MailingState, MailingCountry, MailingPostalCode FROM Contact WHERE AccountId IN :accList ORDER BY CreatedDate ASC LIMIT 1]) {            
    mapBillingStreet.put(firstContact.AccountId, firstContact.MailingStreet);
    mapBillingCity.put(firstContact.AccountId, firstContact.MailingCity);
    mapBillingState.put(firstContact.AccountId, firstContact.MailingState);
    mapBillingCountry.put(firstContact.AccountId, firstContact.MailingCountry);
    mapBillingPostalCode.put(firstContact.AccountId, firstContact.MailingPostalCode);
}

for(Contact lastContact: [SELECT Id, AccountId, MailingStreet, MailingCity, MailingState, MailingCountry, MailingPostalCode FROM Contact WHERE AccountId IN :accList ORDER BY CreatedDate DESC LIMIT 1]) {            
    mapBillingStreet.put(lastContact.AccountId, lastContact.MailingStreet);
    mapBillingCity.put(lastContact.AccountId, lastContact.MailingCity);
    mapBillingState.put(lastContact.AccountId, lastContact.MailingState);
    mapBillingCountry.put(lastContact.AccountId, lastContact.MailingCountry);
    mapBillingPostalCode.put(lastContact.AccountId, lastContact.MailingPostalCode);
}

if(!accList.IsEmpty()){
    update accList;
}
}

I came to this cuz a recommendation of a coworker, but it doesn't work and I think is too robust.

Please help, the company is testing me and my job depends on this.


Edit

This is the actual code:

trigger TestTrigger on Account (after insert, after update) {

List<Account> accList = new List<account>();

Set<Id> firstAcctIds = new Set<Id>();
Set<Id> lastAcctIds = new Set<Id>();

// Separate your "first" type accounts from your "last" type accounts
for(Account acct : Trigger.new){
    if(acct.Type == 'First'){
        firstAcctIds.add(acct.Id);
    } else if (acct.Type == 'Last'){
        lastAcctIds.add(acct.Id);
    }
}

// Populate the address fields of all the "before" accounts with the values from the contact
for(Contact firstContact: [SELECT Id, AccountId, MailingStreet, MailingCity, MailingState, MailingCountry, MailingPostalCode FROM Contact WHERE AccountId IN :firstAcctIds ORDER BY CreatedDate ASC LIMIT 1]){
    Account a = Trigger.newMap.get(firstContact.AccountId);
    a.BillingStreet = firstContact.MailingStreet;
    a.BillingCity = firstContact.MailingCity;
    a.BillingState = firstContact.MailingState;
    a.BillingCountry = firstContact.MailingCountry;
    a.BillingPostalCode = firstContact.MailingPostalCode;
    accList.add(a);
}

// Populate the address fields of all the "last" accounts with the values from the contact
for(Contact lastContact: [SELECT Id, AccountId, MailingStreet, MailingCity, MailingState, MailingCountry, MailingPostalCode FROM Contact WHERE AccountId IN :lastAcctIds ORDER BY CreatedDate DESC LIMIT 1]){
    Account a = Trigger.newMap.get(lastContact.AccountId);
    a.BillingStreet = lastContact.MailingStreet;
    a.BillingCity = lastContact.MailingCity;
    a.BillingState = lastContact.MailingState;
    a.BillingCountry = lastContact.MailingCountry;
    a.BillingPostalCode = lastContact.MailingPostalCode;
    accList.add(a);
}

// And you're done!
if(!accList.isEmpty()) {
    update accList;
}
}

But I'm getting this error:

TestTrigger: execution of AfterUpdate caused by: System.FinalException: Record is read-only Trigger.TestTrigger: line 31, column 1

Well, like I said I'm new into this apex and SOQL thing and to be honest I don't understand pretty much about it, even so, I need to keep this job by the moment so I would really appreciate all your help.

6
  • Welcome Adrian! I have little time now, so I can't answer completely the way I'd like (I am sure somebody else will) so I will just comment. There are several places where you can improve the code (5 maps?! why?). However, to get you out of a fix quickly.... you're capturing the maps but never updating the accounts... that's your immediate problem. Commented Sep 26, 2018 at 1:03
  • @SebastianKessel Hi, how should I solve that? Thanks in advance (could you please consider in helping me a little further if it not much to ask?)
    – AjFmO
    Commented Sep 26, 2018 at 17:38
  • Your code is almost ready. As you are on a "before trigger' context, your update is not needed. Simply remove it and you're be up and running. Commented Sep 26, 2018 at 18:37
  • 1
    Also, it is good etiquette to upvote and accept the answer that helped you solve your problem. If your code works, you should do that to Shane's answer. Commented Sep 26, 2018 at 18:38
  • @SebastianKessel Hola Sebastian, did you see the updated code? I did as Shane suggested and also as you suggested, but I can't make it work tho, I feel lame just for not be able to complete this. I'm gonna elaborate a little more my question just to ensure I'm getting the rights answers.
    – AjFmO
    Commented Sep 26, 2018 at 19:27

1 Answer 1

3

I see a lot of places to improve efficiency -- when I see lots of maps, it's usually a sign that it's time to use an inner class instead, for instance -- but there are three immediate problems:

First, in your two SOQL queries, you should be looking for Contacts with AccountIds in the firstAccountIds or lastAccountIds sets. You're looking for Ids in the (empty) accList.

You're mapping all your address info to the related Account Id, but you aren't actually changing any account records in the trigger.

You don't need to do a DML update on the Accounts. They're already in the trigger context. Simply changing the records while they're in your before insert/update trigger means the new values will be saved at the end of the transaction.

Fix those three problems, and you'll have working logic.

...though something like this would be more efficient:

trigger TestTrigger on Account (Before insert, Before update) {

    Set<Id> firstAcctIds = new Set<Id>();
    Set<Id> lastAcctIds = new Set<Id>();

    // Separate your "first" type accounts from your "last" type accounts
    for(Account acct : Trigger.new){
        if(acct.Type == 'First'){
            firstAcctIds.add(acct.Id);

        } else if (acct.Type == 'Last'){
            lastAcctIds.add(acct.Id);
        }
    }

    // Populate the address fields of all the "before" accounts with the values from the contact
    for(Contact firstContact: [SELECT Id, AccountId, BillingStreet, BillingCity, ... FROM Contact WHERE AccountId IN :firstAcctIds ORDER BY CreatedDate ASC LIMIT 1]){
        Account a = Trigger.newMap.get(firstContact.AccountId);

        a.BillingStreet = firstContact.BillingStreet;
        a.BillingCity = firstContact.BillingCity;
        ...
        ...

    }

    // Populate the address fields of all the "last" accounts with the values from the contact
    ...
    ...
    ...

    // And you're done!

}
3
  • I have tried as you said, including some lines more. I have updated the question to show the actual code.
    – AjFmO
    Commented Sep 26, 2018 at 17:28
  • @Adrian, It looks like you've updated your code to reflect my suggestions, but I see in your thread with Sebastian that it still isn't working for you. What errors are you getting now? Commented Sep 30, 2018 at 20:15
  • 2
    Ah -- I see. There are two problems: First, the trigger needs to fire in Before context, not After. You can't update the records that caused the trigger to run once you're in After context...or, at least, not like this. || Second, you don't need the update accList DML statement after "And you're done!" The records that you've modified are already being processed by the trigger. They'll be inserted/updated with the modified values after the transaction is complete. Give that a try. Commented Sep 30, 2018 at 20:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .