Automatically setting the record type of a detail object based on the record type of its master object

Record types are defined on individual objects and when an object that has a record type is created using the default UI the user is first presented with a page where the record type is selected and then is presented with a normal edit page controlled by the selected record type. But if an object model has master and detail objects where the record types need to be consistent, this manual setting is inappropriate for the detail object because it allows an inconsistent value to be set and also because it forces the user to fill in an extra, redundant page.

This post describes a technique for skipping the manual setting of the record type for the detail object and instead automatically setting it. An important part of this is the “Skip Record Type Selection Page” option accessed via an object’s “Standard Buttons and Links” override for “New”:

The normal “New” page is replaced by the specified page that must be based on the object’s standard controller (to be offered in the override page) and add its logic via a controller extension:

<apex:page
        standardController="PaymentSpecification__c"
        extensions="PaymentSpecificationNewController"
        action="{! url }"
        />

The page is never rendered but instead invokes its controller’s url method that returns the URL that is rendered.

The controller is implemented in two classes to separate the core logic that applies to any master/detail pair from the specific master/detail information. Here BenefitClaimed is the master object and PaymentSpecification is the detail object (created using the “New” button above the related list of PaymentSpecification objects in the BenefitClaimed page):

public with sharing class PaymentSpecificationNewController extends NewChildControllerBase {
    public PaymentSpecificationNewController(ApexPages.StandardController ignored) {
        super(BenefitClaimed__c.SObjectType, PaymentSpecification__c.SObjectType);
    }
}

The base class contains the core logic:

public abstract class NewChildControllerBase {
    private SObjectType parentSobType;
    private SObjectType childSobType;
    public NewChildControllerBase(SObjectType parentSobType, SObjectType childSobType) {
        this.parentSobType = parentSobType;
        this.childSobType = childSobType;
    }
    public PageReference url() {
        PageReference p = new PageReference('/' + childSobType.getDescribe().getKeyPrefix() + '/e');
        Map<String, String> m = p.getParameters();
        m.putAll(ApexPages.currentPage().getParameters());
        m.put('RecordType', getChildRecordTypeId(getParentSObjectId(m)));
        m.put('nooverride', '1');
        return p;
    }
    private Id getChildRecordTypeId(Id parentId) {
        SObject parent = Database.query('select RecordType.DeveloperName from ' + String.valueOf(parentSobType)
                + ' where Id = :parentId');
        String parentDeveloperName = (String) parent.getSobject('RecordType').get('DeveloperName');
        return [select Id from RecordType where SObjectType = :String.valueOf(childSobType)
                and DeveloperName = :parentDeveloperName].Id;
    }
    private Id getParentSObjectId(Map<String, String> m) {
        for (String key : m.keySet()) {
            if (key.endsWith('_lkid')) {
                return m.get(key);
            }
        }
        return null;
    }
}

The approach used is to take all the parameters from the original request, then add the parameters RecordType and nooverride, and then use those parameters with the normal object edit URL. Keeping the original request parameters is important because they include the values needed to pre-populate the parent object name and id in the child object (see Lkid hack value is fragile for some backround information on this; the approach used here is not fragile) and the URL to return to on cancel. The RecordType parameter supplies the detail object’s record type id and the nooverride parameter stops an infinite loop by ensuring the request is handled by the default UI page rather than the override page.

The record type id for the detail object is obtained by using the id of the master object, obtained from the “_lkid” suffixed parameter, to query the master object’s record type. This is then used to obtain the record type id for the detail object. In this case both record types have the same DeveloperName so that is used in the matching logic but other mappings can obviously be implemented. The dynamic SOQL is used to keep this code generic and configured by just the two SObjectType constructor parameters.

Advertisements

9 thoughts on “Automatically setting the record type of a detail object based on the record type of its master object

  1. Great solution. Tnx. I used it to set a default recordType on the child record (so without a mapping). The good thing here is that you can query for the recordTypeId instead of hardcoding it in a URL..

  2. This is an excellent solution. Yo saved me a lot of time and provided a generic base as well. Worked first time – which never happens 🙂 Great job!!

  3. Thank you! This is excellent.

    Looks like the query in getChildRecordTypeId is truncated a bit.
    Database.query(‘selectordType.DeveloperName from ‘ + String.valueOf(parentSobType)
    + ‘ where Id = :parentId’);
    should be ‘select RecordType.DeveloperName from ‘.

  4. I tried with the code above…on Opportunity(master) and Contracts__c (detail) …and i have 3 recordtypes on opportunity(APJC/VSO/Latam)..for APJC and VSO its working fine..when i select a master record of type Latam and tried creating a new contract from related list am getting the following exception..can you please help on this…

    Exception : List has no rows for assignment to SObject
    Error is in expression ‘{!redirect}’ in component in page newcontractscustompage: Class.NewContractsChildController.getChildRecordTypeId: line 24, column 1
    Class.NewContractsChildController.redirect: line 16, column 1

    An unexpected error has occurred. Your development organization has been notified.

  5. This code works perfect. But it fails when you try to click on the “Save and New” button after finishing off with the first record and wanting to jump into creating another record.
    The error that comes out is :
    The page you submitted was invalid for your session. Please click Save again to confirm your change.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s