1

I have a object that has more than 40 fields. There are 3 record types and each layout shows different fields. Now, I want to write a validation rule which allows editing of limited fields only when the stage of record is progress.

For e.g. If my School object have following fields:

School__c
Location__c
Name
Color__c
Class_c
Students__c
Teacher__c
Faculties__c
Department__c
stage__c

Now, If i have 3 record types called Type A, Type B, Type C.

In Type A, I want user be able to edit Color__c and Class__c fields.

In Type B, I want user to be able to edit Students__c and Teacher__c fields

In Type C, I want user to Edit Faculties__c, Department__c fields.

In all record type Name should be editable.

User should not be able to edit School__c, Location__c fields. The whole validation should run when stage__c = progress.

Can i write a validation rule that satisfies above condition.

2
  • This one we can achieve through validation rule. Is this condition for all the users correct ? Commented Aug 9, 2018 at 16:20
  • 1
    While this can be done using validation rules, but considering you have 40 fields, your scenario is the opposite, i.e., you need to write a validation rule to block say 30 fields to be editable and only allow handful of fields to be editable when the stage reaches a particular value for a particular record type. This could get cumbersome to maintain. Recommend approach will be to implement this using a trigger.
    – Jayant Das
    Commented Aug 9, 2018 at 16:34

2 Answers 2

6

You likely could write a validation rule to satisfy your conditions, but you might not be able to save that validation rule (because it'd likely go beyond the compiled character limit).

Validation rules aren't really meant to do what you're looking to do. Preventing certain fields from being edited is easy. The inverse of that, allowing certain fields to be edited, is hard.

A better declarative solution is to create new record types and page layouts, and mark the fields that you want to prevent from being edited as "read only" on those new layouts. You could use a workflow rule or process builder to automatically move your records into/out-of these special recordTypes.

Other than that, this would be a relatively simple trigger.

  • Gather a list of all the field api names on your object
  • Create a set of field api names you want to allow editing on (probably a Map<String, Set<String>> since you want different fields on different recordTypes)
  • Iterate over trigger.new, and check the record against its companion in trigger.old
  • If a field is changed and not in your "allowed" set, add an error

some example code to get you going

Trigger myTrigger on MyObject(before update){
    // We can gather the field API names through SObject describe information
    List<String> fieldAPINames = new List<String>();

    // This is one way to initialize a map
    Map<String, Set<String>> editableFieldsByRecordType = new Map<String, Set<String>>{
        'RecType1' => new Set<String>{'field1__c', 'field2__c', 'field3__c'}
    };

    for(Schema.FieldDiscribeResult fdr :Schema.SObject.MyObject.fields.getMap().values()){
        // The getName() method of the FieldDescribeResult class gives you the API name of the field
        fieldAPINames.add(fdr.getName());
    }

    for(MyObject newRec :Trigger.new){
        MyObject oldRec = Trigger.oldMap.get(newRec.Id);

        for(String currentField :fieldAPINames){
            if(oldRec.get(currentField) != newRec.get(currentField) && !editableFieldsByRecordType.get(newRec.recordTypeName__c).contains(currentField)){
                newRec.addError('Field is not editable for this stage');

                // After you find the first error, you may be able to move on to the next
                //   record.
                // Comment the next line out if you want to catch _all_ attempted edits
                break;
            }
        }
    }
}

The big caveat here (besides me not testing for a particular stage) is that I assume you have a recordTypeName__c field (a formula field) to pull the name of your recordType into the object you're working on.

Trigger context variables don't contain any "related data" (data on objects other than the one you're working on). If you need to use more than one period/dot/full-stop to access a field, like myRec.RecordType.Name, that information is not available in trigger context variables.

To get that information, you either need to explicitly query for it, or have a formula field. There are other ways around that, but those are the easiest and most common approaches.

1
  • 1
    A fieldset (or more likely a couple fieldsets) can be leveraged so that the list of fields is controlled by something that can be edited in Setup rather than hardcoded.
    – Mark Pond
    Commented Aug 9, 2018 at 17:09
0

I would like to integrate the answer of @Derek F. The for to retrieve the information from the object, i.e. the fields name now has to be written as:

for(Schema.SObjectField fdr : Schema.SObjectType.Case.fields.getMap().values()){
  fieldAPINames.add(String.valueOf(fdr));
}

You must log in to answer this question.

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