10

The backstory: The org I am working in allows Person (consumer) and Business records to be created by users and a host data push process in Account. As a result, today we have a large number of each type in Account.

As is expected, after a period of a couple years, many of both have opportunities, cases, and activities attached to the Account records.

Now the fun part: Folks have their heart set on using Parent Account to group customers together into larger hierarchical relationships. This feature is being sold as a solution for decommissioning another application that was built outside of Salesforce years ago that performs basically the same function.

However, as of the Winter 2013 release it is not technically feasible to choose a Parent Account for a Person (consumer) Account record.

The research I have done tells me it is easy enough to reload the customers who are today consumer as business.

However, is it technically feasible to:

  1. Maintain the same URLs and IDs for the consumer records in Account
  2. Keep all dependent items (opportunities, activities, etc.) linked
  3. Maintain the same URLs and IDs for all dependent items
  4. Do the above at scale (more than 300,000 Account records)
  5. Get it all done in one night

Essentially the question is can I easily update the record type in Account and change the Person Accounts to Business Accounts.

Today the main motivation for wanting to do this is as follows. It would allow the org to:

  • Designate a Parent Account for consumer records

  • Group consumer customers with business customers into a hierarchical relationship

Here is an example of a very simple hierarchical relationship with a mix of business and consumer:

ABC Company - Mr. ABC - Mrs. ABC

Thank you experts in advance for your insights.

  • I've seen Apex to convert Business Accounts to Person Accounts, but not vice versa. Do you have to maintain record id? – Mike Chale Dec 23 '13 at 23:31
  • Thank you for your comment. Basic requirement is zero impact to the user experience. If a URL that a user saved as a favorite no longer finds the same Account/Opportunity/Activity/Case record that would be perceived as a negative. While I do not think that the business folks care if the IDs actually stay the same, I know of no other way to preserve the URLs. So I have revised the question to include this as a requirement. – Jon Jaussi Dec 25 '13 at 2:12
5

As far as I know it's not possible to meet all your requirements. Specifically the one about keeping the same Id / URL. I'd love to be proved wrong :)

You'll have to insert a new Account with right record type and link/reparent all related data, then eventually delete the old PA. Maybe you could defer the delete and create a new field "click here to go to the new record"...

We can't use merge statement on a mix of business and person accounts.

The actual process is not too complex. I'll be describing it as for one record, it's up to you to maybe make a batch job out of it, maybe create a button to convert one at a time.

Things to consider beforehand:

  1. ask Salesforce.com support to make the audit fields like CreatedDate, CreatedById editable on Account and Contact. They'll most likely grant it for some time and you can write to these fields only during the insert, not during updates.
  2. identify reports and report types that have Person... or ...__pc in them (I think easiest would be to download them all to Eclipse IDE and run a full search - Ctrl+H). You might have to recreate them for business-only version, you might be up for some explanations if data suddenly "disappears" from certain dashboards.
  3. If you're using Leads & converting them you need to deal with the fact that ConvertedAccountId on the lead will point to old record (and I think converted lead becomes readonly afterwards).
  4. Similarly - you'll lose the field history tracked to date. And Chatter data - if you have it enabled. And approval process history if you were approving Accounts.
  5. If you want to convert only some of them and not all - create a checkbox field or something to identify them & fill it in. Optionally add also a lookup field that will point to new, replacement Acc.
  6. Try to identify validation rules & triggers that might prevent you from changing lookup to Account on objects. Add to them some kind of exception like && $Profile.Name != 'System Administrator' so they won't fire and cause problems.
  7. If you have Master-Detail links between Account and custom objects - make sure they have "reparentable" checkbox ticked.
  8. Prepare some reconciliation reports - how many Opportunities/Cases/... are owned by Business vs. PA accounts. You'll need to monitor the numbers and make sure the grand total stays.

Create a new Account

Prepare a query that fetches most of the standard and __c Account fields. We could try writing a dynamic way of fetching all the fields that match the condition but I think it's better to be done by hand (you'll want to skip the SystemModStamp, isDeleted, RecordTypeId, maybe you use Jigsaw / Data.com...). And clone it.

Account replacement = [SELECT Id, Name, ... 
    FROM Account 
    WHERE isPersonAccount = true
        AND To_be_converted__c = true AND Replacement__c = null /* depends if you'll want such fields, kind of extra protection */
        AND Id = :originalId
    FROM Account].clone(false,true,true,true); //http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_System_SObject_clone.htm
replacement.RecordTypeId = '...';
replacement.PersonContactID = null;
insert replacement;

Copy the Contact

This should be straightforward enough as there can be only one under the PA.

Contact repContact = [SELECT FirstName, LastName, ...
    FROM Contact
    WHERE isPersonAccount = true AND AccountId = :originalId].clone(false,true,true,true);
repContact.AccountId = replacement.Id;
insert repContact;

Fix all references

First we need to learn how many objects have a lookup to Account before we can fix them. You could have a look at the "child relationships" in Eclipse or Real Force Explorer:

enter image description here

Or you could use code to fetch them all:

for(Schema.ChildRelationship rel : Account.sObjectType.getDescribe().getChildRelationships()){
    System.debug('SELECT Id FROM ' + rel.getChildSObject() + ' WHERE ' + rel.getField() + ' = ...');
} // It's tempting to use Database.query() here

SELECT Id FROM Account WHERE ParentId = ...
SELECT Id FROM AccountContactRole WHERE AccountId = ...
SELECT Id FROM AccountFeed WHERE ParentId = ...
SELECT Id FROM AccountHistory WHERE AccountId = ...
SELECT Id FROM AccountPartner WHERE AccountFromId = ...
SELECT Id FROM AccountPartner WHERE AccountToId = ...
SELECT Id FROM AccountShare WHERE AccountId = ...
(...)
SELECT Id FROM Asset WHERE AccountId = ...
SELECT Id FROM Attachment WHERE ParentId = ...
SELECT Id FROM Case WHERE AccountId = ...
(...)

Some won't be applicable (Acc Contact Roles for example). Some stuff you'll have to write-off like history entries. Most will be easy to just fetch all and update the AccountId. Those that can't be reparented - could be queried, deep cloned and inserted back with new Account Id.

Cleanup

Check sharing rules? Check the manual sharing entries (if any). Remove the hacks from validation rules if you've made the exceptions for sysadmins. And once it looks good - delete the original Person Accounts.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.