I'm using MetatadaService class from https://raw.githubusercontent.com/financialforcedev/apex-mdapi/master/apex-mdapi/src/classes/MetadataService.cls, which I have updated to 53.0 version.
I'm trying to generate a field and add view/edit access to it for Standard User Profile.
The problem is: when I'm inserting FieldPermissions
, I'm getting this error:
(Database.SaveResult[getErrors=(Database.Error[getFields=(Field);getMessage=Field Name: bad value for restricted picklist field: Opportunity.CUSTOM_FIELD_NAME__c;getStatusCode=INVALID_OR_NULL_FOR_RESTRICTED_PICKLIST;]);getId=null;isSuccess=false;])
However, the field IS being created and if I run CustomFieldGenerator.addFieldToStandardProfile();
in a separate transaction, everything goes smoothly.
So my question is: do I really need to separate callout to Metadata API and DML for FieldPermissions
, because SF won't see newly created field until the very end of transaction? Or am I missing something?
Very simplified code to test:
public class CustomFieldGenerator {
public static final String LOCAL_NAMESPACE = [SELECT NamespacePrefix FROM Organization LIMIT 1].NamespacePrefix;
public static final String CUSTOM_FIELD_NAME = (String.isNotBlank(LOCAL_NAMESPACE) ? (LOCAL_NAMESPACE + '__') : '') + 'CUSTOM_FIELD_NAME__c';
public static final String STANDARD_OBJECT_NAME = 'Opportunity';
private static MetadataService.CustomField generateField() {
MetadataService.CustomField field = new MetadataService.CustomField();
field.fullName = STANDARD_OBJECT_NAME + '.' + CUSTOM_FIELD_NAME;
field.label = 'CUSTOM_FIELD_NAME';
field.type_x = 'Text';
field.length = 255;
return field;
}
public static void createField() {
MetadataService.MetadataPort service = new MetadataService.MetadataPort();
service.SessionHeader = new MetadataService.SessionHeader_element();
service.SessionHeader.sessionId = UserInfo.getSessionId();
service.CallOptions = new MetadataService.CallOptions_element();
service.timeout_x = 120000;
List<MetadataService.SaveResult> saveResults = service.createMetadata(new List<MetadataService.CustomField>{
generateField()
});
System.debug(saveResults);
}
public static void addFieldToStandardProfile() {
Id profileBasedPermissionSetId = [SELECT Id FROM PermissionSet WHERE Profile.Name = 'Standard User'].Id;
List<FieldPermissions> toInsert = new List<FieldPermissions>{new FieldPermissions(
ParentId = profileBasedPermissionSetId,
SobjectType = STANDARD_OBJECT_NAME,
Field = STANDARD_OBJECT_NAME + '.' + CUSTOM_FIELD_NAME,
PermissionsRead = true,
PermissionsEdit = true)
};
List<Database.SaveResult> res = Database.insert(toInsert, false);
System.debug(res);
}
}
Run through Developer Console:
CustomFieldGenerator.createField();
CustomFieldGenerator.addFieldToStandardProfile();
Just to make it clear. I have moved to this approach from inserting field permissions through Metadata API, too, which included reading Profiles and updating them. But callout to Metadata API had already been seeing updated schema in the same transaction.
SELECT...FOR UPDATE
twice - once on some randomly selected, pre-existing record and second time on the same record. The second call will block. An easier way might be to do another callout to some service under your control.