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
Piotr@ProntoPiotr@Pronto 

Cross object field update - Account-Contact

Hello everyone,

 

Im a total newbie to the Apex World but really keen on learning!

 

We're trying to build a code that would let us connect two fields under standard objects namely Account and Contact.

 

e.g. if "A" field (checkbox) under "Account" object is TRUE - "A" field (checkbox) under "Contact" object is also TRUE.

 

We're also trying to get the same work with the "date" field e.g. if I insert a date 5/5/2012 in "Test Date" field under "Account" object, the exact same date will appear under "Test Date" field under "Contact" object.

 

Why we want to do that? We'd like this to trigger certain workflows built on "Contact" object but want to do updated under "Account" object only.

 

Would someone be able to help me out?

 

Thanks you all

Piotr

Best Answer chosen by Admin (Salesforce Developers) 
bob_buzzardbob_buzzard

Its a sunny Monday morning, so why not.  Revised code below:

 

trigger Crossobjectfieldupdate on Account (after update) 
{
  if (Trigger.isUpdate)
  {
      Map<Id, Account> updAccs=new Map<Id, Account>();
      for (Account acc : trigger.new)
      {
         Account oldAcc=trigger.oldMap.get(acc.id);
         if (acc.Test_Date__c != oldAcc.Test_Date__c)
         {
            updAccs.put(acc.id, acc);
         }
       }
 
       List<Contact> updCon=[select id, accountId, Test_Date__c from contact where accountId in :updAccs.keySet()];
       for (Contact con: updCon)
       {
           Account acc=updAccs.get(con.accountId);
           con.Test_Date__c=acc.Test_Date__c;
       } 
   }
 
   update updCon;
}

 

So essentially the code locates all those accounts where the Test_Date__c has changed and stores them in a map keyed by the account id.  Then all contacts associated with those accounts are retrieved en-masse.  These are iterated, and the associated account pulled from the map based on the accountid field and the Test_Date__c is set for the contact.  Finally, the retrieved list of contacts is updated.  This should handle bulk changes (e.g. from the data loader etc) as well.

 

 

All Answers

Navatar_DbSupNavatar_DbSup

Hi,

 

For this you can write a trigger after update on Account so that it updates the required field of all related contacts.

 

Try below code snippet as reference:

 

trigger Crossobjectfieldupdate on Account (after update) {

List<Contact> con=new List<Contact>();

Account acc;

Contact cc;

List<Contact> Updatecon=new List<Contact>();

if (Trigger.isUpdate)

{

    acc=Trigger.New[0];

    con=[select id,A__c from contact where accountid=:acc.id];

    if(con.size()>0)

        for(contact c:con)

        {

            cc=new Contact(id=c.id,A__c=acc.A__c);

            Updatecon.add(cc);

        }

}

 

update Updatecon;

 

}

 

Did this answer your question? If not, let me know what didn't work, or if so, please mark it solved. 

bob_buzzardbob_buzzard

Please be aware that this trigger will only process the first account affected.  As triggers receive a list of records (accounts in this case) you would need to make sure that you updated all contacts of all accounts appropriately.

Piotr@ProntoPiotr@Pronto

Thank you very much for your help guys!

 

I'm not sure what you mean by "first account affected". We'd like this to be global, like a workflow rule. Whenever we update a certain field under Account object we'd like the same field to update under Contact object.

 

Can you please elaborate on your last post? Sorry, I'm a total newbie to this...

 

Piotr

DharshniDharshni

Hi

 

You can make acheive this using cross-object formula field.

 

For ex. you have a field named 'Acc_Date__c' in account, and you want this to be available in all the related contacts, you can create a formula field in contact - 'Date on Account' with the formula - Account.Acc_Date__c. Whenever your account is updated, the formula in the contact is run and the values are syncronized.

Piotr@ProntoPiotr@Pronto

That's true, however, we need this "date" field under Contact level to trigger a workflow. Formula field isn't able to do that if I'm not wrong.

 

The code provided worked great and triggered the workflow but I'm just waiting on some more info from the moderator about his concerns.

 

 

bob_buzzardbob_buzzard

Sure.  A trigger receives a list of affected records, if changes are made to more than one record at a time, via the web services api for example, or a visualforce page.  In this case, the example code will process the first account and update the contacts, but the rest of the accounts and their associated contacts won't be touched.  Processing the first record in the list works for regular UI interaction, but isn't good practice if your code may be called as part of a bulk change, either now or at any time in the future.

 

There's more information at:

 

http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_triggers_bestpract.htm

Piotr@ProntoPiotr@Pronto

I think I understand now but even though I looked at the documentation still am not 100% sure.

 

We basically want the "Test Date" field to trigger a workflow (email alert to a Contact) immediate and time-dependent too. This obviously happens after "Test Date" is inserted under Account object.

 

With new contacts that's pretty easy since you'd always update 1 at a time, however, we wanted to set this up for current/existing contacts as well.

 

So if I imported a list of contacts with say "Test Date" fields using LexiLoader the trigger wouldn't work? Is that what you're trying to say?

 

Is there anything that can be done about it?

 

Or maybe trigger is not a solution to what we're trying to get?

bob_buzzardbob_buzzard

Hmm.  If you are using workflow from the Test Date field on a contact record, then triggers wouldn't be involved as that's standard configuration.

 

This is more around updating a number of accounts in one go - in that case, only the contacts associated with the first account would have their Test Date set with the code above.

Piotr@ProntoPiotr@Pronto

Right, however, we want this to be very much automatic for new Accounts/Contacts e.g. we create a new Account and 3 users get created. After the "Test Date" is filled under the Account object, all Contacts receive the same "Test Date" and workflow fires. Otherwise we'd need to go to each one separately and this is what we really want to avoid.

 

In a way we could then cover our existing contacts by simply updating "Test Date" under Contact object without worrying about the trigger and have some one update each Account to be consistent. Would this make sense?

 

Anyway, is there any way around it so that the trigger covers bulk updates as well? Anything you could recommend knowing what we're trying to achieve here?

 

Thank you so much!

 

 

bob_buzzardbob_buzzard

You can cover your existing contacts in that way.

 

The trigger can cover bulk operations without too much effort - you'd need to pull back all affected contacts (probably based on the keyset of the trigger.new value), then iterate those and update them with the test date from the related account.

 

 

 

Piotr@ProntoPiotr@Pronto

I'm not sure how to do that, keep on reading your reply all over again but I guess my lack of knowledge here doesn't help. I'll try to figure it out.

 

How do I deploy the trigger to my production SFDC?

 

I want to make sure that only this 1 trigger is carried over and no other data such as Account or Reports.

 

Thanks!

 

 

Piotr@ProntoPiotr@Pronto

In the meantime I started digging about how to do that myself.

 

Apparently I need to test triggers and achieve 75% code coverage, currently I have 0. I heard about some code I need to add but I have no clue how to do that. Would you be able to help or should I post a new topic? I have no idea about this.

 

I managed to transfer the trigger to my production SFDC but during validation the following errors came up:

 

1. CSForce_SearchController.testSearch()    Class    24 Failure Message: "System.NullPointerException: Attempt to de-reference a null object", Failure Stack Trace: "Class.CSForce_SearchHelper.<init>: line 24, column 1 Class.CSForce_SearchController.doSearch: line 88, column 1 Class.CSForce_SearchController.testSearch: line 170, column 1"

 

2. CrossobjectfieldupdateNPSStartDate    Test coverage of selected Apex Trigger is 0%, at least 1% test coverage is required

 

3.Deploy Error    Average test coverage across all Apex Classes and Triggers is 68%, at least 75% test coverage is required.

 

Can you help? Am really stuck here....

 

Thanks!

bob_buzzardbob_buzzard
Piotr@ProntoPiotr@Pronto

Thanks!

 

I went through this documentation earlier on in a day but since I'm a pure beginner it really means nothing to me and I don't even know where to start.

 

When I compare the trigger with the test code they seem to be completely different and looks like it's a totally separate code.

 

Is there a formula or some sort of pattern anywhere so I can implement the above code and run the test?

 

I tried creating new Apex Class but there would always be bunch of errors.

 

Any chance someone could help here and let me know what code I need to use to test the trigger please?

bob_buzzardbob_buzzard

It is totally separate code.  It is unit test code that is executed to confirm the trigger is working as expected.  Effectively you have to write code to test your code if you want to deploy apex.

 

In order to create a test class you should just need something like the following:

 

@istest
private class TriggerTest
{
   private static testMethod void TriggerTest1()
   {
   }
}

 In the body of the TriggerTest you'll need to create data and insert/update as appropriate in order to exercise your trigger.

 

I'd suggest its worth spending some time getting to grips with Apex first though - bear in mind that your trigger will be executed whenever a record is saved or updated, so you need to make sure that it works under as many scenarios as possible.

Piotr@ProntoPiotr@Pronto

Thanks for all your help!

 

I just wish I knew what to do with all this.

 

I totally understand what you're saying and agree that I need to spend some quality time on this. Here, I'm jsut pressed by deadlines. We were hoping to launch this automatic every 90 days email to all contacts through workflow but it's been 2 weeks of going back and forth with SFDC Support and it didn't work. Then I started looking at Apex and ended up in this community.

 

I pasted your test code into "Apex Class" but unfortunately have no clue what to insert in the body of the TriggerTest.

 

Would you by chance be able to help with this instance only? Ill surely do my reading but it looks like it's gonna take me weeks before we move forward with this and at the same time miss the deadline. Either way thanks A LOT for all your help! I know I may be asking for too much here...

 

Here is the code I'd like to test :

 

trigger CrossobjectfieldupdateNPSStartDate on Account (after update) {

List<Contact> con=new List<Contact>();

Account acc;

Contact cc;

List<Contact> Updatecon=new List<Contact>();

if (Trigger.isUpdate)

{

    acc=Trigger.New[0];

    con=[select id,NPS_Start_Date__c from contact where accountid=:acc.id];

    if(con.size()>0)

        for(contact c:con)

        {

            cc=new Contact(id=c.id,NPS_Start_Date__c=acc.NPS_Start_Date__c);

            Updatecon.add(cc);

        }

}
 
update Updatecon;


}
bob_buzzardbob_buzzard

It should just be as simple as creating an account, adding some contacts and then setting that date field.  You'll probably want some verification in there.

 

Something like the the following in the body of the method should do it:

 

Account acc=new Account(Name='Unit Test');
insert acc;

Contact cont1=new Contact(FirstName='Unit', 
                          LastName='Test 1');

insert cont1;

Contact cont2=new Contact(FirstName='Unit', 
                          LastName='Test 2');

insert cont2;


acc.NPS_Start_Date__c=System.today();
update acc;

// now check the contacts were updated
for (Contact cont : [select id, NPS_Start_Date__c from Contact where accountId=:acc.id])
{
   System.assertEquals(cont.NPS_Start_Date__c, System.today());
}

 

Piotr@ProntoPiotr@Pronto

You're a STAR! Thank you!

 

I managed to save it and run the test.

 

It gave me 11% code coverage total and 75% code coverage on the date trigger.

 

However, no new account or contact has been created in my Sandbox. I understand that they should have? Or they have, were tested and were automatically removed?

bob_buzzardbob_buzzard

The latter.  Test data is never actually committed, although for the purposes of your test it looks like it has.  The transaction is rolled back in its entirety at the end of the test.

Piotr@ProntoPiotr@Pronto

I see and it makes sense. Can't wait to learn all about it but this will surely take some time.

 

I uploaded the trigger to my production Salesforce and while validating I've come across the following errors:

 

1. CSForce_SearchController.testSearch()    Class    24    Failure Message: "System.NullPointerException: Attempt to de-reference a null object", Failure Stack Trace: "Class.CSForce_SearchHelper.<init>: line 24, column 1 Class.CSForce_SearchController.doSearch: line 88, column 1 Class.CSForce_SearchController.testSearch: line 170, column 1" - how can I fix this? I'm not even getting what the problem is...

 

2. CrossobjectfieldupdateNPSStartDate    Test coverage of selected Apex Trigger is 0%, at least 1% test coverage is required - I thought the test was successful and am not sure why it says 0%? What can I do to change it?

 

3. Deploy Error    Average test coverage across all Apex Classes and Triggers is 68%, at least 75% test coverage is required. - same here... how can I make sure it gets at least 75%?

 

 

bob_buzzardbob_buzzard

You'll need to deploy your test class to production along with the trigger to satisfy that.

 

To fix the CSForce stuff you'll need to find whoever developed that and get them to fix their tests I'm afraid.  The other option is to try to figure out what is broken and fix it yourself.

Piotr@ProntoPiotr@Pronto

I see... let me do some research on how to deploy both of them. So far I don't know but I'll dig into it.

 

Regarding the code I used one from the second post from Navatar_DbSup and it worked in my Sandbox. I need to learn how to write the code so I just relied on this suggestion. This is what I included:

 

trigger Crossobjectfieldupdate on Account (after update) {

List<Contact> con=new List<Contact>();

Account acc;

Contact cc;

List<Contact> Updatecon=new List<Contact>();

if (Trigger.isUpdate)

{

    acc=Trigger.New[0];

    con=[select id,A__c from contact where accountid=:acc.id];

    if(con.size()>0)

        for(contact c:con)

        {

            cc=new Contact(id=c.id,A__c=acc.A__c);

            Updatecon.add(cc);

        }

}

 

update Updatecon;

 

}

 

I changed custom fields of course but the pattern is above. Other than that I made no changes. Would you be able to take a quick look at it?

 

Anyway, I'm based in Thailand so its Friday evening for me now. I'll check back tomorrow. Thank you so much for your help again again and again and have a great weekend!



bob_buzzardbob_buzzard

Hmm.  Not sure if this the actual code, but it isn't actually changing anything.  It retrieves all the contacts associated with the account, creates new versions of them and saves them.  There's no reason why you can't change the contacts that you have received.  I thought you were looking to set a date in there?

 

You have a good weekend too.

Piotr@ProntoPiotr@Pronto

Ouch! I only confirmed that I have 0 understanding of this all. :(

 

I however uploaded it to my Sandbox and it did just that. I obviously changed the custom field "A__c" to "Test_Date__c" for testing purposes only ("Test_Date__c" is a custom field "date" type). Once the date was inserted under Account object, the same date automatically showed up under Contact object too so I thought it was working great.

 

Would you be able to look into this code and improve it?

 

We're nearly there... well at least I thought we were... :)

 

Thanks Bob!

 

 

bob_buzzardbob_buzzard

Its a sunny Monday morning, so why not.  Revised code below:

 

trigger Crossobjectfieldupdate on Account (after update) 
{
  if (Trigger.isUpdate)
  {
      Map<Id, Account> updAccs=new Map<Id, Account>();
      for (Account acc : trigger.new)
      {
         Account oldAcc=trigger.oldMap.get(acc.id);
         if (acc.Test_Date__c != oldAcc.Test_Date__c)
         {
            updAccs.put(acc.id, acc);
         }
       }
 
       List<Contact> updCon=[select id, accountId, Test_Date__c from contact where accountId in :updAccs.keySet()];
       for (Contact con: updCon)
       {
           Account acc=updAccs.get(con.accountId);
           con.Test_Date__c=acc.Test_Date__c;
       } 
   }
 
   update updCon;
}

 

So essentially the code locates all those accounts where the Test_Date__c has changed and stores them in a map keyed by the account id.  Then all contacts associated with those accounts are retrieved en-masse.  These are iterated, and the associated account pulled from the map based on the accountid field and the Test_Date__c is set for the contact.  Finally, the retrieved list of contacts is updated.  This should handle bulk changes (e.g. from the data loader etc) as well.

 

 

This was selected as the best answer
Piotr@ProntoPiotr@Pronto

lol :) thank you!

 

It looks like there's a problem with : "update updCon;" when I try to save the code...

 

It says: Error: Compile Error: Variable does not exist: updCon at line 23 column 11

 

Do you know what might be wrong with this? What variable are they talking about?

bob_buzzardbob_buzzard

Yeah - its the fact that updCon is declared in an inner scope.  If you change it to:

 

trigger Crossobjectfieldupdate on Account (after update) 
{
  if (Trigger.isUpdate)
  {
      Map<Id, Account> updAccs=new Map<Id, Account>();
      for (Account acc : trigger.new)
      {
         Account oldAcc=trigger.oldMap.get(acc.id);
         if (acc.Test_Date__c != oldAcc.Test_Date__c)
         {
            updAccs.put(acc.id, acc);
         }
       }
 
       List<Contact> updCon=[select id, accountId, Test_Date__c from contact where accountId in :updAccs.keySet()];
       for (Contact con: updCon)
       {
           Account acc=updAccs.get(con.accountId);
           con.Test_Date__c=acc.Test_Date__c;
       } 
 
       update updCon;
   }
}

 i.e. just move the updCon up one brace level, it should be in scope and work.

Piotr@ProntoPiotr@Pronto


Like you said, worked great and saved!

So I went ahead to test it using the same trigger as provided before

Account acc=new Account(Name='Unit Test');
insert acc;

Contact cont1=new Contact(FirstName='Unit',
                          LastName='Test 1');

insert cont1;

Contact cont2=new Contact(FirstName='Unit',
                          LastName='Test 2');

insert cont2;


acc.Test_Date__c=System.today();
update acc;

// now check the contacts were updated
for (Contact cont : [select id, Test_Date__c from Contact where accountId=:acc.id])
{
   System.assertEquals(cont.Test_Date__c, System.today());
}

and got 81% code coverage on that trigger. What would it take to get 100%? What could be the problem there?

I looked at the code coverage details and the following is highlighted in red (everything else blue/purple):

Account acc=updAccs.get(con.accountId);
           con.Test_Date__c=acc.Test_Date__c;

 

I went ahead with that and uploaded both the trigger and the test class to Production SFDC (successful). While validating 2 errors showed up:

 

1. CSForce_SearchController.testSearch()    Class    24    Failure Message: "System.NullPointerException: Attempt to de-reference a null object", Failure Stack Trace: "Class.CSForce_SearchHelper.<init>: line 24, column 1 Class.CSForce_SearchController.doSearch: line 88, column 1 Class.CSForce_SearchController.testSearch: line 170, column 1"

 

2. Deploy Error    Average test coverage across all Apex Classes and Triggers is 74%, at least 75% test coverage is required.

 

:( what can be wrong with it this time round? Wrong Test Class?

bob_buzzardbob_buzzard

None of your contacts are associated with the account - try setting the AccountId on the test contacts to that of the account that you have inserted.

 

The other problem is that you have unrelated test failures.  Until those are fixed I doubt you'll be able to deploy anything to production.

Piotr@ProntoPiotr@Pronto

oh no :( I thought we were so close...

 

I'm not sure what you mean by "None of your contacts are associated with the account" In both Sandbox (3 test accounts) and Production SFDC (4,000 +) we have accounts with related contacts. Maybe I don't understand properly what you're advising me to do here?

 

How do I fix unrelated test failures?

 

You helped so much Bob already. I feel like I'm asking way too much here but hope there's not much more to do and will be all set.

 

Please advise...

bob_buzzardbob_buzzard

In your test method, you create an account and two contacts.  Those contacts don't have their account id populated and thus aren't associated with an account.  Thus when you change the test date on the accoun there are no contacts to be updated.

 

With regard to the unrelated test failures, you either need to find the developer that wrote those and get them to fix them, or go into the source code yourself to fix it.

Piotr@ProntoPiotr@Pronto

So we're talking about the test code here:

 

*** *** ***

 

Account acc=new Account(Name='Unit Test');
insert acc;

Contact cont1=new Contact(FirstName='Unit',
                          LastName='Test 1');

insert cont1;

Contact cont2=new Contact(FirstName='Unit',
                          LastName='Test 2');

insert cont2;


acc.Test_Date__c=System.today();
update acc;

// now check the contacts were updated
for (Contact cont : [select id, Test_Date__c from Contact where accountId=:acc.id])
{
   System.assertEquals(cont.Test_Date__c, System.today());
}

 

*** *** ***

 

Where do I insert this account ID in the code above? And also I'm not sure what the ID is since it doesn't exist in my SFDC. I thought it's only created for testing purposes and then disappears. Where can I get it from?

 

None of the codes were written by me or a developer :( Everything I have I got through your help here and this is why I really don't know how to move forward. Would you be able to review both, the test code above, and the trigger code below so those errors disaapear? Or there's some other code I'll need to edit?

 

*** *** ***

 

trigger Crossobjectfieldupdate on Account (after update)
{
  if (Trigger.isUpdate)
  {
      Map<Id, Account> updAccs=new Map<Id, Account>();
      for (Account acc : trigger.new)
      {
         Account oldAcc=trigger.oldMap.get(acc.id);
         if (acc.Test_Date__c != oldAcc.Test_Date__c)
         {
            updAccs.put(acc.id, acc);
         }
       }
 
       List<Contact> updCon=[select id, accountId, Test_Date__c from contact where accountId in :updAccs.keySet()];
       for (Contact con: updCon)
       {
           Account acc=updAccs.get(con.accountId);
           con.Test_Date__c=acc.Test_Date__c;
       }
   
     update updCon;
   }
}

 

*** *** ***

 

Thanks!!!

 

bob_buzzardbob_buzzard

You'd add the accountId to the contact when creating it.  The id will be populated once the insert has taken place. e..g

 

Contact cont1=new Contact(FirstName='Unit',
                          LastName='Test 1'
                          AccountId=acc.id);

 

 

The error that you are seeing is from code that is present in your production org - if you haven't carried out any development, that sounds like a managed package is throwing an error. If that is the case you need to contact the author of the package and get them to fix it.

Piotr@ProntoPiotr@Pronto

oh bummer :( but it's good to know!!!

 

I added AccountId to both test contacts and it worked great. Got 100% in Sandbox and upon uploading to Production the code coverage error disappeared! Awesome.

 

But as you said, there's a problem with one of our managed packages. We use quite a few of them so it could be anything...

 

Looking at the code below, would you be able to say what causes the problem or how I would be able to find out? I'd get in touch with them right away. Looks like this is the last final step before we can deploy this trigger.

 

Failure Message: "System.NullPointerException: Attempt to de-reference a null object", Failure Stack Trace: "Class.CSForce_SearchHelper.<init>: line 24, column 1 Class.CSForce_SearchController.doSearch: line 88, column 1 Class.CSForce_SearchController.testSearch: line 170, column 1"

Piotr@ProntoPiotr@Pronto

I think I got it:

 

CSForce_SearchController under "Apex Classes".... now my last mission. Find out what it is and what package it's part of. It has code coverage 0%. Seems like it shouldn't even be in Production SFDC.

Piotr@ProntoPiotr@Pronto

Bob 1 more please.

 

If this is Apex Class is causing problem and I just run a test where I got only 74% on code coverage it looks like this Apex Class isn't event working right?

 

Would it be okay for me to disable it in Sandbox and upload changes to Production?

 

Or it could cause some more issues along the way?

 

I sent a request to SFDC support to fix it or at least let me know what managed apckage it's part of. In case you're able to tell please let me know.

 

Thanks for your fantastic help here!

bob_buzzardbob_buzzard

I had a quick google when you first posted but couldn't find anything.

 

I can't really say what effect disabling it might have.  It looks like a controller for a visualforce page, so its likely that would stop working.  Much depends on what use your organization is making of it.

Piotr@ProntoPiotr@Pronto

I see. Well I'll wait for SFDC to get back to me on this.

 

As much as I want this trigger to start working I don't want to disactivate and serious features. Not making use of Visualforce today much but we were planning on in near future. Unless, I'll disactivate it now, fix later and activate it then.

 

Thanks Bob!

Piotr@ProntoPiotr@Pronto

Hi Bob!

 

We got that working! So happy :) and thank you for everything. We fixed the error and everything is working fine, apart from...

 

While trying to do the bulk update of the date we get the following error:

 

"field integrity exception: test date: invalid date: Wed Mar 30 00:00:00 GMT 12 Error fields: Test_Date__c"

 

Do you know what's wrong with it by chance?

Piotr@ProntoPiotr@Pronto

Nevermind that's what I expected. The formatting was a problem. ;)

bob_buzzardbob_buzzard

That looks like a date/time rather than a date.  Which data type is your field?

bob_buzzardbob_buzzard

Cross post!