diff --git a/.prettierrc b/.prettierrc index e68bf09..b017f19 100755 --- a/.prettierrc +++ b/.prettierrc @@ -15,6 +15,14 @@ "files": "*.{cmp,page,component}", "options": { "parser": "html" } }, + { + "files": "*.{cls,trigger}", + "options": { "parser": "apex", "tabWidth": 2, "useTabs": true } + }, + { + "files": "*.{apex,soql}", + "options": { "parser": "anonymous-apex" } + }, { "files": "*.{yaml,yml}", "options": { "useTabs": false, "tabWidth": 2, "singleQuote": true } diff --git a/force-app/main/default/classes/RecordTypes.cls b/force-app/main/default/classes/RecordTypes.cls index 61dc293..2824490 100644 --- a/force-app/main/default/classes/RecordTypes.cls +++ b/force-app/main/default/classes/RecordTypes.cls @@ -3,7 +3,7 @@ * @description This class is allows for easy, readable access to Record Type information from other classes. *
*
Written by Evan Callahan, copyright 2010 Groundwire - *
Updated by David Schach, copyright 2021 X-Squared on Demand + *
Updated by David Schach, copyright 2021-2024 X-Squared on Demand *
*
This program is released under the GNU General Public License. http://www.gnu.org/licenses/ *
@@ -41,22 +41,22 @@ */ global inherited sharing class RecordTypes { /** - * @description Global schema describe + * @description Hold Sobject describe results as we need them */ - private static Map gd; + private static Map sObjectDescribes = new Map(); /** - * @description Map of Record Types by sObject name, then by Id + * @description Map of Record Types by sObject name (lowercase), then by Id */ private static Map> recordTypesById = new Map>(); /** - * @description Map of Record Types by sObject name, then by Name + * @description Map of Record Types by sObject name (lowercase), then by Name */ private static Map> recordTypesByName = new Map>(); /** - * @description Map of Record Types by sObject name, then by DeveloperName + * @description Map of Record Types by sObject name (lowercase), then by DeveloperName */ private static Map> recordTypesByDevName = new Map>(); @@ -66,7 +66,7 @@ global inherited sharing class RecordTypes { private static Map> availableRecordTypesMap = new Map>(); /** - * @description map of all Record Types by sObject name, including inactive and unavailable + * @description map of all Record Types by sObject name (lowercase), including inactive and unavailable */ private static Map> allRecordTypes = new Map>(); @@ -101,18 +101,29 @@ global inherited sharing class RecordTypes { * @param objectName SObject name (with __c if custom, etc) */ private static void fillMapsForObject(String objectName) { - // get the object map the first time - if (gd == null) { - gd = Schema.getGlobalDescribe(); + String sObjectName = objectName.toLowerCase(); + if (!sObjectDescribes.containsKey(sObjectName)) { + try { + List describes = Schema.describeSObjects(new List{ sObjectName }, SObjectDescribeOptions.DEFERRED); + sObjectDescribes.put(sObjectName, describes[0]); + } catch (InvalidParameterValueException e) { + return; + } } - // get the sObject description - if (gd.containsKey(objectName)) { + if (sObjectDescribes.containsKey(sObjectName)) { @SuppressWarnings('PMD.EagerlyLoadedDescribeSObjectResult') - Schema.DescribeSObjectResult d = gd.get(objectName).getDescribe(); - recordTypesByName.put(objectName, d.getRecordTypeInfosByName()); - recordTypesByDevName.put(objectName, d.getRecordTypeInfosByDeveloperName()); - recordTypesById.put(objectName, d.getRecordTypeInfosById()); + Schema.DescribeSObjectResult d = sObjectDescribes.get(sObjectName); + + Map objectRTsByName = new Map(); + Map objectRTsByDevName = new Map(); + for (Schema.RecordTypeInfo rti : d.getRecordTypeInfos()) { + objectRTsByName.put(rti.getName().toLowerCase(), rti); + objectRTsByDevName.put(rti.getDeveloperName().toLowerCase(), rti); + } + recordTypesByName.put(sObjectName, objectRTsByName); + recordTypesByDevName.put(sObjectName, objectRTsByDevName); + recordTypesById.put(sObjectName, d.getRecordTypeInfosById()); List recordTypesForSObject = new List(); List availableRecordTypesList = new List(); for (Schema.Recordtypeinfo rt : d.getRecordTypeInfos()) { @@ -123,12 +134,13 @@ global inherited sharing class RecordTypes { availableRecordTypesList.add(rt); } if (rt.isDefaultRecordTypeMapping()) { - defaultRecordTypeIds.put(objectName, rt.getRecordTypeId()); - defaultRecordTypes.put(objectName, rt); + System.debug('Default RT is ' + rt.getName()); + defaultRecordTypeIds.put(sObjectName, rt.getRecordTypeId()); + defaultRecordTypes.put(sObjectName, rt); } } - allRecordTypes.put(objectName, recordTypesForSObject); - availableRecordTypesMap.put(objectName, availableRecordTypesList); + allRecordTypes.put(sObjectName, recordTypesForSObject); + availableRecordTypesMap.put(sObjectName, availableRecordTypesList); } } @@ -140,19 +152,20 @@ global inherited sharing class RecordTypes { */ @SuppressWarnings('PMD.AvoidDeeplyNestedIfStmts') global static Schema.RecordTypeInfo getDefaultRecordType(String objectName) { + String sObjectName = objectName.toLowerCase(); // make sure we have this sObject's record types mapped - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); + if (!recordTypesByName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } - if (recordTypesByName.containsKey(objectName)) { - Schema.RecordTypeInfo defaultRT = defaultRecordTypes.get(objectName); + if (recordTypesByName.containsKey(sObjectName)) { + Schema.RecordTypeInfo defaultRT = defaultRecordTypes.get(sObjectName); /* if (defaultRT.isMaster()) { // fill the name to id map - Map rtMap = recordTypesByName?.get(objectName); + Map rtMap = recordTypesByName?.get(sObjectName); for (Schema.RecordTypeInfo rti : rtMap?.values()) { if (!rti.isMaster() && rti.isAvailable() && rti.isActive()) { - defaultRecordTypes.put(objectName, rti); - defaultRecordTypeIds.put(objectName, rti.getRecordTypeId()); + defaultRecordTypes.put(sObjectName, rti); + defaultRecordTypeIds.put(sObjectName, rti.getRecordTypeId()); return rti; } } @@ -172,14 +185,14 @@ global inherited sharing class RecordTypes { * @return `List` List of all record types for the object (active and inactive) */ global static List getRecordTypesForObject(String objectName) { + String sObjectName = objectName.toLowerCase(); // make sure we have this sObject's record types mapped - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); + if (!recordTypesByName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } - if (recordTypesByName.containsKey(objectName)) { + if (recordTypesByName.containsKey(sObjectName)) { // get the record types for the object - List recordTypeList = recordTypesByName?.get(objectName)?.values(); - return recordTypeList; + return recordTypesByName?.get(sObjectName)?.values(); } return null; } @@ -195,12 +208,13 @@ global inherited sharing class RecordTypes { * @return `Id` Default Record Type Id for the running user for this object */ global static Id getDefaultRecordTypeId(String objectName) { + String sObjectName = objectName.toLowerCase(); // make sure we have this sObject's record types mapped - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); + if (!recordTypesByName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } - if (recordTypesByName.containsKey(objectName)) { - Schema.RecordTypeInfo defaultRT = getDefaultRecordType(objectName); + if (recordTypesByName.containsKey(sObjectName)) { + Schema.RecordTypeInfo defaultRT = getDefaultRecordType(sObjectName); if (defaultRT.isMaster()) { return null; } @@ -216,11 +230,8 @@ global inherited sharing class RecordTypes { * @return `String` Default Record Type DeveloperName for the running user for this object */ global static String getDefaultRecordTypeDevName(String objectName) { - // make sure we have this sObject's record types mapped - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); - } - return getDefaultRecordType(objectName)?.getDeveloperName(); + String sObjectName = objectName.toLowerCase(); + return getDefaultRecordType(sObjectName)?.getDeveloperName(); } /** @@ -230,10 +241,6 @@ global inherited sharing class RecordTypes { * @return `String` Default Record Type Name for the running user for this object */ global static String getDefaultRecordTypeName(String objectName) { - // make sure we have this sObject's record types mapped - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); - } return getDefaultRecordType(objectName)?.getName(); } @@ -254,18 +261,22 @@ global inherited sharing class RecordTypes { * RecordTypes.getRecordTypeFromName('Account','Person Account') */ global static Schema.RecordTypeInfo getRecordTypeFromName(String objectName, String recordTypeName) { + String sObjectName = objectName.toLowerCase(); + String rtName = recordTypeName.toLowerCase(); // make sure we have this sObject's record types mapped - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); + if (!recordTypesByName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } // now grab and return the requested id - Map rtMap = recordTypesByName.get(objectName); - if (rtMap != null && rtMap.containsKey(recordTypeName)) { - return rtMap.get(recordTypeName); + //Map rtMap = recordTypesByName.get(sObjectName); + return recordTypesByName.get(sObjectName)?.get(rtName); + /* if (rtMap != null && rtMap.containsKey(rtName)) { + return rtMap.get(rtName); } else { return null; } + */ } /** @@ -280,7 +291,9 @@ global inherited sharing class RecordTypes { * RecordTypes.getRecordTypeIdFromName('Account','Person Account') */ global static Id getRecordTypeIdFromName(String objectName, String recordTypeName) { - return getRecordTypeFromName(objectName, recordTypeName)?.getRecordTypeId(); + //String sObjectName = objectName.toLowerCase(); + + return getRecordTypeFromName(objectName.toLowerCase(), recordTypeName.toLowerCase())?.getRecordTypeId(); } /** @@ -295,7 +308,8 @@ global inherited sharing class RecordTypes { * RecordTypes.getRecordTypeDevNameFromName('Account','Person Account') */ global static String getRecordTypeDevNameFromName(String objectName, String recordTypeName) { - return getRecordTypeFromName(objectName, recordTypeName)?.getDeveloperName(); + String sObjectName = objectName.toLowerCase(); + return getRecordTypeFromName(objectName.toLowerCase(), recordTypeName)?.getDeveloperName(); } //RecordType DeveloperName SECTION @@ -313,18 +327,13 @@ global inherited sharing class RecordTypes { * RecordTypes.getRecordTypeFromDevName('Account','Person_Account') */ global static Schema.RecordTypeInfo getRecordTypeFromDevName(String objectName, String recordTypeDevName) { + String sObjectName = objectName.toLowerCase(); // make sure we have this sObject's record types mapped - if (!recordTypesByDevName.containsKey(objectName)) { - fillMapsForObject(objectName); + if (!recordTypesByDevName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } - // now grab and return the requested object - Map rtMap = recordTypesByDevName.get(objectName); - if (rtMap != null && rtMap.containsKey(recordTypeDevName)) { - return rtMap.get(recordTypeDevName); - } else { - return null; - } + return recordTypesByDevName.get(sObjectName)?.get(recordTypeDevName.toLowerCase()); } /** @@ -337,7 +346,7 @@ global inherited sharing class RecordTypes { * RecordTypes.getRecordTypeNameFromDevName('Account','Person_Account') */ global static String getRecordTypeNameFromDevName(String objectName, String recordTypeDevName) { - return getRecordTypeFromDevName(objectName, recordTypeDevName)?.getName(); + return getRecordTypeFromDevName(objectName?.toLowerCase(), recordTypeDevName?.toLowerCase())?.getName(); } /** @@ -351,7 +360,7 @@ global inherited sharing class RecordTypes { * RecordTypes.getRecordTypeIdFromDevName('Account','Person_Account') */ global static Id getRecordTypeIdFromDevName(String objectName, String recordTypeDevName) { - return getRecordTypeFromDevName(objectName, recordTypeDevName)?.getRecordTypeId(); + return getRecordTypeFromDevName(objectName?.toLowerCase(), recordTypeDevName?.toLowerCase())?.getRecordTypeId(); } //RecordType Id SECTION @@ -366,8 +375,8 @@ global inherited sharing class RecordTypes { * @return `Schema.RecordTypeInfo` RecordTypeInfo object */ global static Schema.RecordTypeInfo getRecordTypeFromId(Id recordTypeId) { - String objectName = recordTypesIdMap.get(recordTypeId).SobjectType; - return getRecordTypeFromId(objectName, recordTypeId); + String sObjectName = recordTypesIdMap.get(recordTypeId)?.SobjectType.toLowerCase(); + return getRecordTypeFromId(sObjectName, recordTypeId); } /** @@ -378,18 +387,13 @@ global inherited sharing class RecordTypes { * @return `Schema.RecordTypeInfo` RecordTypeInfo object */ global static Schema.RecordTypeInfo getRecordTypeFromId(String objectName, Id recordTypeId) { + String sObjectName = objectName.toLowerCase(); // make sure we have this sObject's record types mapped - if (!recordTypesById.containsKey(objectName)) { - fillMapsForObject(objectName); + if (!recordTypesById.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } - // now grab and return the requested object - Map rtMap = recordTypesById.get(objectName); - if (rtMap != null && rtMap.containsKey(recordTypeId)) { - return rtMap.get(recordTypeId); - } else { - return null; - } + return recordTypesById.get(sObjectName)?.get(recordTypeId); } /** @@ -400,7 +404,7 @@ global inherited sharing class RecordTypes { * @return RecordType Name */ global static String getRecordTypeNameFromId(String objectName, Id recordTypeId) { - return getRecordTypeFromId(objectName, recordTypeId)?.getName(); + return getRecordTypeFromId(objectName.toLowerCase(), recordTypeId)?.getName(); } /** @@ -413,7 +417,7 @@ global inherited sharing class RecordTypes { * @see getRecordTypeDevNameFromId[1] */ global static String getRecordTypeDevNameFromId(String objectName, Id recordTypeId) { - return getRecordTypeFromId(objectName, recordTypeId)?.getDeveloperName(); + return getRecordTypeFromId(objectName.toLowerCase(), recordTypeId)?.getDeveloperName(); } /** @@ -427,19 +431,21 @@ global inherited sharing class RecordTypes { * getRecordTypeIdSetFromDevNames('Account', rtNames); */ global static Set getRecordTypeIdSetFromDevNames(String objectName, Set recordTypeDevNameSet) { + String sObjectName = objectName.toLowerCase(); Set recordTypeIds = new Set(); + Set devNamesLowerCase = new Set(); // make sure we have this sObject's record types mapped - if (!recordTypesByDevName.containsKey(objectName)) { - fillMapsForObject(objectName); + if (!recordTypesByDevName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } // fill the id set from the name set - if (recordTypesByName.containsKey(objectName)) { - Map rtMap = recordTypesByDevName.get(objectName); + if (recordTypesByName.containsKey(sObjectName)) { + Map rtMap = recordTypesByDevName.get(sObjectName); for (String recTypeDevName : recordTypeDevNameSet) { - if (rtMap.containsKey(recTypeDevName)) { - recordTypeIds.add(rtMap.get(recTypeDevName).getRecordTypeId()); + if (rtMap.containsKey(recTypeDevName.toLowerCase())) { + recordTypeIds.add(rtMap.get(recTypeDevName.toLowerCase()).getRecordTypeId()); } } } @@ -448,20 +454,22 @@ global inherited sharing class RecordTypes { /** * @description Gives a map of all record type IDs by DeveloperName for an sObject + *
Returns Master record type if no other RTs available * @author {@link [David Schach](https://github.com/dschach)} * @param objectName SObject name (with __c if custom, etc) * @return `Map` Map */ global static Map getRecordTypeDevNameIdMap(String objectName) { + String sObjectName = objectName.toLowerCase(); Map recordTypeMap = new Map(); // make sure we have this sObject's record types mapped - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); + if (!recordTypesByName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } // fill the name to id map - if (recordTypesByDevName.containsKey(objectName)) { - Map rtMap = recordTypesByDevName.get(objectName); + if (recordTypesByDevName.containsKey(sObjectName)) { + Map rtMap = recordTypesByDevName.get(sObjectName); for (recordTypeInfo rti : rtMap.values()) { if (!rti.isMaster()) { recordTypeMap.put(rti.getDeveloperName(), rti.getRecordTypeId()); @@ -478,15 +486,16 @@ global inherited sharing class RecordTypes { * @return `Map` Map */ global static Map getRecordTypeNameIdMap(String objectName) { + String sObjectName = objectName.toLowerCase(); Map recordTypeMap = new Map(); // make sure we have this sObject's record types mapped - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); + if (!recordTypesByName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } // fill the name to id map - if (recordTypesByName.containsKey(objectName)) { - Map rtMap = recordTypesByName.get(objectName); + if (recordTypesByName.containsKey(sObjectName)) { + Map rtMap = recordTypesByName.get(sObjectName); for (Schema.RecordTypeInfo rti : rtMap.values()) { if (!rti.isMaster()) { recordTypeMap.put(rti.getName(), rti.getRecordTypeId()); @@ -505,15 +514,16 @@ global inherited sharing class RecordTypes { * @return `Map` Map */ global static Map getAvailableRecordTypeDevNameIdMap(String objectName) { + String sObjectName = objectName.toLowerCase(); Map recordTypeMap = new Map(); // make sure we have this sObject's record types mapped - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); + if (!recordTypesByName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } // fill the devname to id map - if (availableRecordTypesMap.containsKey(objectName)) { - for (Schema.RecordTypeInfo rti : availableRecordTypesMap.get(objectName)) { + if (availableRecordTypesMap.containsKey(sObjectName)) { + for (Schema.RecordTypeInfo rti : availableRecordTypesMap.get(sObjectName)) { // requires permission set to cover in package tests because cannot add record type // to a profile when creating a scratch org recordTypeMap.put(rti.getDeveloperName(), rti.getRecordTypeId()); @@ -531,7 +541,8 @@ global inherited sharing class RecordTypes { * @return `Set` Map */ global static Set getAvailableRecordTypesIdSet(String objectName) { - return new Set(getAvailableRecordTypeNameIdMap(objectName).values()); + String sObjectName = objectName.toLowerCase(); + return new Set(getAvailableRecordTypeNameIdMap(sObjectName).values()); } /** @@ -543,15 +554,16 @@ global inherited sharing class RecordTypes { * @return `Map` Map */ global static Map getAvailableRecordTypeNameIdMap(String objectName) { + String sObjectName = objectName.toLowerCase(); Map recordTypeMap = new Map(); // make sure we have this sObject's record types mapped - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); + if (!recordTypesByName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } // fill the name to id map - if (availableRecordTypesMap.containsKey(objectName)) { - for (Schema.RecordTypeInfo rti : availableRecordTypesMap.get(objectName)) { + if (availableRecordTypesMap.containsKey(sObjectName)) { + for (Schema.RecordTypeInfo rti : availableRecordTypesMap.get(sObjectName)) { // requires permission set to cover in package tests because cannot add record type // to a profile when creating a scratch org recordTypeMap.put(rti.getName(), rti.getRecordTypeId()); @@ -598,17 +610,17 @@ global inherited sharing class RecordTypes { * System.SelectOption[value="", label="Inactive", disabled="true"] */ global static List getAvailableRecordTypesForSelectList(String objectName) { + String sObjectName = objectName.toLowerCase(); List recordTypesOptions = new List(); - Map availRecordTypes = getAvailableRecordTypeNameIdMap(objectName); // never null. Can be empty. - /* if (availRecordTypes == null) { - return recordTypesOptions; - } */ + Map availRecordTypes = getAvailableRecordTypeNameIdMap(sObjectName); // never null. Can be empty. + for (String thisRecordType : availRecordTypes.keyset()) { recordTypesOptions.add(new SelectOption(availRecordTypes.get(thisRecordType), thisRecordType)); } - if (recordTypesOptions.isEmpty() && gd.containsKey(objectName)) { - String defaultRTName = defaultRecordTypes.get(objectName)?.getName(); - defaultRTName = defaultRTName == null ? '' : '--' + defaultRecordTypes.get(objectName)?.getName() + '--'; + if (recordTypesOptions.isEmpty() && sObjectDescribes.containsKey(sObjectName)) { + // check the object exists + String defaultRTName = defaultRecordTypes.get(sObjectName)?.getName(); + defaultRTName = defaultRTName == null ? '' : '--' + defaultRecordTypes.get(sObjectName)?.getName() + '--'; recordTypesOptions.add(new SelectOption('', defaultRTName, false)); } recordTypesOptions.sort(); @@ -623,7 +635,8 @@ global inherited sharing class RecordTypes { * @return `List` Visualforce SelectOption list */ global static List getAllRecordTypesForSelectList(String objectName) { - return getAllRecordTypesForSelectList(objectName, true); + String sObjectName = objectName.toLowerCase(); + return getAllRecordTypesForSelectList(sObjectName, true); } /** * @description Make a Visualforce picklist with ALL `RecordType` Names displayed and the `RecordType` Id as the value @@ -634,27 +647,30 @@ global inherited sharing class RecordTypes { * @return `List` Visualforce SelectOption list */ global static List getAllRecordTypesForSelectList(String objectName, Boolean disableUnavailable) { + String sObjectName = objectName.toLowerCase(); List recordTypesOptions = new List(); - if (!allRecordTypes.containsKey(objectName)) { - fillMapsForObject(objectName); - } - List recordTypes = allRecordTypes.get(objectName); - Boolean hasAvailableRT = false; - if (recordTypes == null) { - return recordTypesOptions; - } - for (Schema.RecordTypeInfo rti : recordTypes) { - recordTypesOptions.add(new SelectOption(rti.getRecordTypeId(), rti.getName(), disableUnavailable ? !rti.isAvailable() : false)); - if (rti.isAvailable()) { - hasAvailableRT = true; + if (!allRecordTypes.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); + } + if (allRecordTypes.containsKey(sObjectName)) { + List recordTypes = allRecordTypes.get(sObjectName); + Boolean hasAvailableRT = false; + if (recordTypes == null) { + return recordTypesOptions; } + for (Schema.RecordTypeInfo rti : recordTypes) { + recordTypesOptions.add(new SelectOption(rti.getRecordTypeId(), rti.getName(), disableUnavailable ? !rti.isAvailable() : false)); + if (rti.isAvailable()) { + hasAvailableRT = true; + } + } + if (recordTypesOptions.isEmpty() || (disableUnavailable && !hasAvailableRT)) { + String defaultRTName = defaultRecordTypes?.get(sObjectName)?.getName(); + defaultRTName = defaultRTName == null ? '' : '--' + defaultRecordTypes?.get(sObjectName)?.getName() + '--'; + recordTypesOptions.add(new SelectOption('', defaultRTName, false)); + } + recordTypesOptions.sort(); } - if (recordTypesOptions.isEmpty() || (disableUnavailable && !hasAvailableRT && gd.containsKey(objectName))) { - String defaultRTName = defaultRecordTypes?.get(objectName)?.getName(); - defaultRTName = defaultRTName == null ? '' : '--' + defaultRecordTypes?.get(objectName)?.getName() + '--'; - recordTypesOptions.add(new SelectOption('', defaultRTName, false)); - } - recordTypesOptions.sort(); return recordTypesOptions; } @@ -665,8 +681,9 @@ global inherited sharing class RecordTypes { * @return `List` Visualforce SelectOption list */ global static List getStringRecordTypesForSelectList(String objectName) { + String sObjectName = objectName.toLowerCase(); List recordTypesOptions = new List(); - Map availRecordTypes = getAvailableRecordTypeNameIdMap(objectName); + Map availRecordTypes = getAvailableRecordTypeNameIdMap(sObjectName); /* if (availRecordTypes == null) { return recordTypesOptions; } */ @@ -685,11 +702,12 @@ global inherited sharing class RecordTypes { * @return `Boolean` Is this Record Type available to the running user? */ public static Boolean isRecordTypeAvailable(String objectName, String recordTypeDevName) { - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); + String sObjectName = objectName.toLowerCase(); + if (!recordTypesByName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); } - Boolean isAvailableRT = getRecordTypeFromDevName(objectName, recordTypeDevName)?.isAvailable(); - return isAvailableRT == null ? false : isAvailableRT; + Boolean isAvailableRT = getRecordTypeFromDevName(sObjectName, recordTypeDevName)?.isAvailable(); + return isAvailableRT ?? false; } /** @@ -700,12 +718,33 @@ global inherited sharing class RecordTypes { * @return `Boolean` Is this Record Type the default for the running user? */ public static Boolean isRecordTypeDefault(String objectName, String recordTypeDevName) { - if (!recordTypesByName.containsKey(objectName)) { - fillMapsForObject(objectName); - } - Boolean isDefultMapping = getRecordTypeFromDevName(objectName, recordTypeDevName)?.isDefaultRecordTypeMapping(); - return isDefultMapping == null ? false : isDefultMapping; - } + String sObjectName = objectName.toLowerCase(); + if (!recordTypesByName.containsKey(sObjectName)) { + fillMapsForObject(sObjectName); + } + Boolean isDefaultMapping = getRecordTypeFromDevName(sObjectName, recordTypeDevName.toLowerCase())?.isDefaultRecordTypeMapping(); + return isDefaultMapping ?? false; + } + + /* private class RTInfoWrapper { + private String developerName; + private String name; + private Id recordTypeId; + private Boolean active; + private Boolean available; + private Boolean isDefault; + private Boolean master; + + public RTInfoWrapper(Schema.RecordTypeInfo rti){ + this.developerName = rti.getDeveloperName(); + this.name = rti.getName(); + this.recordTypeId = rti.getRecordTypeId(); + this.active = rti.isActive(); + this.available = rti.isAvailable(); + this.isDefault = rti.isDefaultRecordTypeMapping(); + this.master = rti.isMaster(); + } + } */ /** * @description If we are running a test, clear sObject maps. We do this to avoid using @testVisible on the maps themselves diff --git a/force-app/main/default/classes/RecordTypesTest.cls b/force-app/main/default/classes/RecordTypesTest.cls index 81985f6..6553e8b 100644 --- a/force-app/main/default/classes/RecordTypesTest.cls +++ b/force-app/main/default/classes/RecordTypesTest.cls @@ -6,6 +6,7 @@ * @author {@link [David Schach](https://github.com/dschach)} * @since 2021 Various enhancements and updates to use new Apex methods * @since 2023 Addition of methods to handle no profile-based RT permissions with permissions via permission set + * @since 2024 Check for case insensitivity * @group RecordTypes * @see RecordTypes */ @@ -107,6 +108,50 @@ private class RecordTypesTest { Assert.isFalse(RecordTypes.getAllRecordTypesForSelectList('Account').isEmpty(), 'Should return at least single item select list'); } + /** + * @description Test against account, an object with record types in package testing + * @author Evan Callahan + */ + @IsTest + private static void testAccountRecTypesCaseInsensitive() { + //Test with account + + Assert.isNotNull(RecordTypes.getRecordTypesForObject('account'), 'A real sobject should have at least Master record type'); + Assert.isNull(RecordTypes.getRecordTypeIdFromName('account', 'BogusRT'), 'BogusRT record type should not exist'); + Assert.isNull(RecordTypes.getRecordTypeDevNameFromName('account', 'BogusRT'), 'BogusRT record type should not exist'); + + Assert.isNull(RecordTypes.getRecordTypeNameFromDevName('account', 'BogusRT'), 'BogusRT record type should not exist'); + Assert.isNull(RecordTypes.getRecordTypeIdFromDevName('account', 'BogusRT'), 'BogusRT record type should not exist'); + + Assert.isNull(RecordTypes.getRecordTypeNameFromId('account', null), 'Null record type should not exist'); + Assert.isNull(RecordTypes.getRecordTypeDevNameFromId('account', null), 'Null record type should not exist'); + + Assert.isNull(RecordTypes.getRecordTypeIdFromDevName('account', 'BogusRT'), 'BogusRT record type should not exist'); + Assert.isNull(RecordTypes.getRecordTypeIdFromName('account', 'BogusRT'), 'BogusRT record type should not exist'); + + Assert.isNotNull(RecordTypes.getDefaultRecordTypeName('account'), 'A real sobject should have at least Master record type'); + Assert.isNotNull(RecordTypes.getDefaultRecordTypeDevName('account'), 'A real sobject should have at least Master record type'); + + Assert.isNotNull(RecordTypes.getRecordTypeDevNameIdMap('account'), 'A real sobject should return at least an empty map'); + + Assert.isNotNull( + RecordTypes.getAvailableRecordTypeDevNameIdMap('account'), + 'Should return at least an empty map, and populated one if we have record types for account.' + ); + Assert.isNotNull( + RecordTypes.getAvailableRecordTypeNameIdMap('account'), + 'Should return at least an empty map, and a populated one if we have record types for account.' + ); + + Assert.isNotNull( + RecordTypes.getAvailableRecordTypesIdSet('account'), + 'Should return at least an empty map, and populated one if we have record types for account.' + ); + + resetTest(); + Assert.isFalse(RecordTypes.getAllRecordTypesForSelectList('account').isEmpty(), 'Should return at least single item select list'); + } + /** * @description Test against Solution, an object likely to have no record types * @author Evan Callahan @@ -158,6 +203,16 @@ private class RecordTypesTest { Assert.isTrue(testSObjectRecordTypeReturns('Account'), 'Test failure checking Account'); } + /** + * @description Separate method for checking if we have Account record type, and then testing that + * @author {@link [David Schach](https://github.com/dschach)} + * @see testSObjectRecordTypeReturns + */ + @IsTest + private static void testAccountRecordTypesLowercase() { + Assert.isTrue(testSObjectRecordTypeReturns('account'), 'Test failure checking account (object name lowercase)'); + } + /** * @description Separate method for checking if we have Contact record type, and then testing that * @author {@link [David Schach](https://github.com/dschach)} @@ -186,6 +241,7 @@ private class RecordTypesTest { */ private static Boolean testSObjectRecordTypeReturns(String sobjectname) { List queriedRecordTypes = [SELECT Id, Name, DeveloperName FROM RecordType WHERE SObjectType = :sobjectname]; + //System.debug(queriedRecordTypes); RecordTypes.getRecordTypeNameIdMap(sobjectname); resetTest(); @@ -195,6 +251,7 @@ private class RecordTypesTest { if (!queriedRecordTypes.isEmpty()) { String defaultRTDevName = RecordTypes.getDefaultRecordType(sobjectName).getDeveloperName(); + //System.debug(sobjectname + ' default RT DevName = ' + defaultRTDevName); Assert.isTrue( RecordTypes.isRecordTypeDefault(sobjectName, defaultRTDevName), 'We should have found ' + sobjectname.capitalize() + ' default RT DevName as ' + defaultRTDevName @@ -214,8 +271,8 @@ private class RecordTypesTest { Assert.areEqual(rti.getDeveloperName(), rti2.getDeveloperName(), 'Overloaded ' + sobjectname.capitalize() + ' method failed to retrieve same RTId'); - Assert.areNotEqual(null, RecordTypes.getRecordTypeDevNameFromId(activeRT.Id), 'We should have had an' + sobjectname.capitalize() + ' RecordType'); - Assert.areNotEqual(null, RecordTypes.getRecordTypeNameFromId(activeRT.Id), 'We should have had an' + sobjectname.capitalize() + ' RecordType'); + Assert.areNotEqual(null, RecordTypes.getRecordTypeDevNameFromId(activeRT.Id), 'We should have had ' + sobjectname.capitalize() + ' Record Type'); + Assert.areNotEqual(null, RecordTypes.getRecordTypeNameFromId(activeRT.Id), 'We should have had ' + sobjectname.capitalize() + ' Record Type'); Assert.areEqual( activeRT.Id, diff --git a/package-lock.json b/package-lock.json index 1a6f3d1..11d190a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -468,6 +468,15 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -1230,9 +1239,9 @@ } }, "node_modules/joi": { - "version": "17.12.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.1.tgz", - "integrity": "sha512-vtxmq+Lsc5SlfqotnfVjlViWfOL9nt/avKNbKYizwf6gsCfq9NYY/ceYRMFD8XDdrjJ9abJyScWmhmIiy+XRtQ==", + "version": "17.12.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.12.2.tgz", + "integrity": "sha512-RonXAIzCiHLc8ss3Ibuz45u28GOsWE1UpfDXLbN/9NKbL4tCJf8TWYVKsoYuuh+sAUt7fsSNpA+r2+TBA6Wjmw==", "dev": true, "dependencies": { "@hapi/hoek": "^9.3.0", @@ -1278,15 +1287,6 @@ "url": "https://opencollective.com/lint-staged" } }, - "node_modules/lint-staged/node_modules/commander": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", - "dev": true, - "engines": { - "node": ">=16" - } - }, "node_modules/listr2": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.0.1.tgz", @@ -1553,9 +1553,9 @@ } }, "node_modules/npm-run-path": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", - "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "dependencies": { "path-key": "^4.0.0" diff --git a/scripts/orginit.sh b/scripts/orginit.sh index c433636..b4fcb8f 100644 --- a/scripts/orginit.sh +++ b/scripts/orginit.sh @@ -9,6 +9,7 @@ sf org create scratch --definition-file config/project-scratch-def.json --durati echo "Pushing metadata" sf project deploy start --source-dir force-app sf project deploy start --source-dir unpackaged +sf project deploy start --manifest unpackaged/manifest.xml --post-destructive-changes unpackaged/destructiveChangesPost.xml --wait 20 echo "Assigning permission set" sf org assign permset --name RecordTypes_DefaultRT_for_Testing diff --git a/unpackaged/main/default/classes/UnpackagedRecordTypesTest.cls b/unpackaged/main/default/classes/UnpackagedRecordTypesTest.cls new file mode 100644 index 0000000..ff4b8d9 --- /dev/null +++ b/unpackaged/main/default/classes/UnpackagedRecordTypesTest.cls @@ -0,0 +1,116 @@ +/** + * This class contains unit tests for validating the behavior of Apex classes + * and triggers. + * + * Unit tests are class methods that verify whether a particular piece + * of code is working properly. Unit test methods take no arguments, + * commit no data to the database, and are flagged with the testMethod + * keyword in the method definition. + * + * All test methods in an org are executed whenever Apex code is deployed + * to a production org to confirm correctness, ensure code + * coverage, and prevent regressions. All Apex classes are + * required to have at least 75% code coverage in order to be deployed + * to a production org. In addition, all triggers must have some code coverage. + * + * The @isTest class annotation indicates this class only contains test + * methods. Classes defined with the @isTest annotation do not count against + * the org size limit for all Apex scripts. + * + * See the Apex Language Reference for more information about Testing and Code Coverage. + */ +@isTest +private class UnpackagedRecordTypesTest { + /** + * @description Test against Account, an object with record types in package testing, but with lowercase RT name + *
Assumes we have a record type called `Default` and tests `Default` + * @author Evan Callahan + * @author David Schach + */ + @IsTest + private static void testAccountRecTypes() { + //Test with Account + + Assert.isNotNull(RecordTypes.getRecordTypesForObject('Account'), 'A real sobject should have at least Master record type'); + Assert.isNotNull(RecordTypes.getRecordTypeIdFromName('Account', 'Default'), 'Default record type should exist'); + Assert.isNotNull(RecordTypes.getRecordTypeDevNameFromName('Account', 'Default'), 'Default record type should exist'); + + Assert.isNotNull(RecordTypes.getRecordTypeNameFromDevName('Account', 'Default'), 'Default record type should exist'); + Assert.isNotNull(RecordTypes.getRecordTypeIdFromDevName('Account', 'Default'), 'Default record type should exist'); + + Assert.isNull(RecordTypes.getRecordTypeNameFromId('Account', null), 'Null record type should not exist'); + Assert.isNull(RecordTypes.getRecordTypeDevNameFromId('Account', null), 'Null record type should not exist'); + + Assert.isNotNull(RecordTypes.getRecordTypeIdFromDevName('Account', 'Default'), 'Default record type should exist'); + Assert.isNotNull(RecordTypes.getRecordTypeIdFromName('Account', 'Default'), 'Default record type should exist'); + + //Assert.areEqual('Default', RecordTypes.getDefaultRecordTypeName('Account'), 'Our default Account RT should be "Default"'); + //Assert.areEqual('Default', RecordTypes.getDefaultRecordTypeDevName('Account'), 'Our default Account RT should be "Default"'); + + Assert.areEqual( + 'Master', + RecordTypes.getDefaultRecordTypeName('Account'), + 'Our default Account RT should be "Master" because we assigned via permission set' + ); + Assert.areEqual( + 'Master', + RecordTypes.getDefaultRecordTypeDevName('Account'), + 'Our default Account RT should be "Master" because we assigned via permission set' + ); + + Assert.isNotNull(RecordTypes.getRecordTypeDevNameIdMap('Account'), 'A real sobject should return at least an empty map'); + + Assert.isNotNull( + RecordTypes.getAvailableRecordTypeDevNameIdMap('Account'), + 'Should return at least an empty map, and populated one if we have record types for Account.' + ); + Assert.isNotNull( + RecordTypes.getAvailableRecordTypeNameIdMap('Account'), + 'Should return at least an empty map, and a populated one if we have record types for Account.' + ); + + Assert.isNotNull( + RecordTypes.getAvailableRecordTypesIdSet('Account'), + 'Should return at least an empty map, and populated one if we have record types for Account.' + ); + } + + /** + * @description Test against Account, an object with record types in package testing, but with lowercase RT name + *
Assumes we have a record type called `Default` and tests `default` + * @author Evan Callahan + * @author David Schach + */ + @IsTest + private static void testAccountRecTypesLowercaseRT() { + //Test with Account + + Assert.isNotNull(RecordTypes.getRecordTypesForObject('Account'), 'A real sobject should have at least Master record type'); + Assert.isNotNull(RecordTypes.getRecordTypeIdFromName('Account', 'default'), 'default record type should exist'); + Assert.isNotNull(RecordTypes.getRecordTypeDevNameFromName('Account', 'default'), 'default record type should exist'); + + Assert.isNotNull(RecordTypes.getRecordTypeNameFromDevName('Account', 'default'), 'default record type should exist'); + Assert.isNotNull(RecordTypes.getRecordTypeIdFromDevName('Account', 'default'), 'default record type should exist'); + + Assert.isNotNull(RecordTypes.getRecordTypeIdFromDevName('Account', 'default'), 'default record type should exist'); + Assert.isNotNull(RecordTypes.getRecordTypeIdFromName('Account', 'default'), 'default record type should exist'); + } + + /** + * @description Test for running certain methods without filling maps + */ + @IsTest + static void testMethodsAfterReset() { + resetTest(); + List queriedRecordTypes = [SELECT Id, Name, DeveloperName FROM RecordType WHERE SObjectType = 'Account']; + Assert.isNotNull(RecordTypes.getRecordTypeNameFromId(queriedRecordTypes[0].Id), 'Error'); + } + + /** + * @description Clear all record type maps to enable using fewer test methods - reset within a method + * @author {@link [David Schach](https://github.com/dschach)} + */ + private static void resetTest() { + RecordTypes.clearMapsInTest(); + } +} \ No newline at end of file diff --git a/unpackaged/main/default/classes/UnpackagedRecordTypesTest.cls-meta.xml b/unpackaged/main/default/classes/UnpackagedRecordTypesTest.cls-meta.xml new file mode 100644 index 0000000..c14e405 --- /dev/null +++ b/unpackaged/main/default/classes/UnpackagedRecordTypesTest.cls-meta.xml @@ -0,0 +1,5 @@ + + + 59.0 + Active + diff --git a/unpackaged/main/default/destructiveChangesPost.xml b/unpackaged/main/default/destructiveChangesPost.xml new file mode 100644 index 0000000..e07021d --- /dev/null +++ b/unpackaged/main/default/destructiveChangesPost.xml @@ -0,0 +1,10 @@ + + + + Account-Account %28Marketing%29 Layout + Account-Account %28Sales%29 Layout + Account-Account %28Support%29 Layout + Layout + + 60.0 + diff --git a/unpackaged/main/default/layouts/Account-Account Layout.layout-meta.xml b/unpackaged/main/default/layouts/Account-Account Layout.layout-meta.xml new file mode 100644 index 0000000..330405a --- /dev/null +++ b/unpackaged/main/default/layouts/Account-Account Layout.layout-meta.xml @@ -0,0 +1,195 @@ + + + Submit + + false + false + true + + + + Edit + OwnerId + + + Required + Name + + + Edit + ParentId + + + Edit + RecordTypeId + + + + + + + false + false + true + + + + + + + false + false + true + + + + + + + false + false + true + + + + Readonly + CreatedById + + + + + Readonly + LastModifiedById + + + + + + false + false + true + + + + + + true + false + false + + + + + + + + + FeedItem.TextPost + + + FeedItem.ContentPost + + + NewTask + + + NewContact + + + NewCase + + + LogACall + + + NewNote + + + NewOpportunity + + + NewEvent + + + FeedItem.RypplePost + + + FeedItem.LinkPost + + + FeedItem.PollPost + + + FeedItem.QuestionPost + + + SendEmail + + + + FULL_NAME + CONTACT.TITLE + CONTACT.EMAIL + CONTACT.PHONE1 + RelatedContactList + + + OPPORTUNITY.NAME + OPPORTUNITY.STAGE_NAME + OPPORTUNITY.AMOUNT + OPPORTUNITY.CLOSE_DATE + RelatedOpportunityList + + + CASES.CASE_NUMBER + NAME + CASES.SUBJECT + CASES.PRIORITY + CASES.CREATED_DATE_DATE_ONLY + CASES.STATUS + OWNER_NAME + RelatedCaseList + + + TASK.SUBJECT + TASK.WHO_NAME + TASK.WHAT_NAME + ACTIVITY.TASK + TASK.DUE_DATE + TASK.STATUS + TASK.PRIORITY + CORE.USERS.FULL_NAME + RelatedActivityList + + + TASK.SUBJECT + TASK.WHO_NAME + TASK.WHAT_NAME + ACTIVITY.TASK + TASK.DUE_DATE + CORE.USERS.FULL_NAME + TASK.LAST_UPDATE + RelatedHistoryList + + + RelatedNoteList + + + ACCOUNT.NAME + OPPORTUNITY.NAME + PARTNER.ROLE + RelatedPartnerList + + ParentId + false + false + false + false + false + + 00hO3000001bqTr + 4 + 0 + Default + + diff --git a/unpackaged/main/default/manifest.xml b/unpackaged/main/default/manifest.xml new file mode 100644 index 0000000..150c862 --- /dev/null +++ b/unpackaged/main/default/manifest.xml @@ -0,0 +1,4 @@ + + + 60.0 + diff --git a/unpackaged/main/default/objects/Account/recordTypes/Default.recordType-meta.xml b/unpackaged/main/default/objects/Account/recordTypes/Default.recordType-meta.xml index fa0c7b9..73049da 100644 --- a/unpackaged/main/default/objects/Account/recordTypes/Default.recordType-meta.xml +++ b/unpackaged/main/default/objects/Account/recordTypes/Default.recordType-meta.xml @@ -2,6 +2,7 @@ Default true + Package record type for record-types testing AccountSource