RIUG-Helping Admins Makes Better Developers

Helping Admins Makes Us Better
Developers
Jeff May
Independent Consultant
@JeffMTI
Jeff May
Independent Consultant
Traditional Development vs Salesforce
Traditional Development
Salesforce Development
1. All layers of the application
1. External Data Layer and Presentation Layer
2. Total control over Business Processes
2. Inserted into external Business Processes
3. Once installed, the application runs
3. Application needs Admin configuration
User Relationships
Executives
Managers
Admins
Developers
Users
The Truth about being a Salesforce Developer
 No one is impressed with your programming skills
 No one worries about whether it was easy or hard to do
 No one cares which Salesforce features you used
 Everyone measures your success by whether they get the functionality they need
Design Decision: Which feature(s) to use
Formula Fields
Process & Flow
Trigger / Apex

Every record has the
correct value

Self-documenting

Complex algorithms

Easy to update / test

Easy conditional execution

First in the Execution Order

Reduce SOQL calls

Access other Configuration
features

No Admin Control
Formula Fields
 Address:
BillingCity + ', ' + BillingState + ' ' + BillingCountry
 Sales Tax:
CASE(BillingState, 'MA', 0.05, 'RI', 0.07, 0.0)
 Related Record Info:
Account.Owner.IsActive
Process & Flow
Lightning Processes
Flows
 IF / THEN / ELSE
 Branching code
 Related Record Field Updates
 Loops
 Quick Actions
 Access to any records
 Flows
 Multiple object create / update
Trigger: set all Contact Owners to Account Owner
trigger AccountTrigger on Account (after update) {
Map<Id, Id> acctIdToOwnerIdMap = new Map<Id, Id>();
for (Account a : trigger.new){
// if this Account's Owner changed, change all the Contact owners to that same Owner
if (a.OwnerId != trigger.oldMap.get(a.Id).OwnerId ){
acctIdToOwnerIdMap.put(a.Id, a.OwnerId);
}
}
// if any Accounts have changed owners, we need to find/update all Contacts
if (acctIdToOwnerIdMap.size() > 0){
List<Contact> contactsToUpdate = new List<Contact>();
for (Contact c : [select Id, AccountId, OwnerId from Contact where AccountId in :acctIdToOwnerIdMap.keySet()]){
if (c.OwnerId != acctIdToOwnerIdMap.get(c.AccountId)){
c.OwnerId = acctIdToOwnerIdMap.get(c.AccountId);
contactsToUpdate.add(c);
}
}
if (contactsToUpdate.size() > 0){ update contactsToUpdate;}
}
}
Process: set all Contact Owners to Account Owner
Goal: Create an Order from an Opportunity
Opportunity
Order
Line Item
Line Item
Line Item
Line Item
Line Item
Line Item
PriceBookEntry
PriceBook
Product
Flow: create Orders from an Opportunity
Development Practices For Happy
Admins
1. Formula Fields
2. User Fields
3. Custom Settings
Formula Fields – Control Business Logic
trigger OpportunityTrigger on Opportunity (before update) {
Map<Id, Opportunity> oppMapForAutoCloseProcessing = new Map<Id, Opportunity>();
for (Opportunity o : trigger.new) {
if (!o.isClosed && (o.RecordTypeId != null) && (o.CampaignId != '70150000000SjkF')) {
oppMapForAutoCloseProcessing.put(o.Id, o);
}
}
// Process the Opps
}
Formula Fields – Define the Field
Formula Fields – Control Business Logic
trigger OpportunityTrigger on Opportunity (before update) {
Map<Id, Opportunity> oppMapForAutoCloseProcessing = new Map<Id, Opportunity>();
for (Opportunity o : trigger.new) {
if (o.Opp_Needs_Closing__c) {
oppMapForAutoCloseProcessing.put(o.Id, o);
}
}
// Process the Opps
}
Formula Fields – Save SOQL Calls
trigger ContactTrigger on Contact (after update) {
for (Contact c : trigger.new){
if (c.AccountId != null){
acctIdsToGet.add(c.AccountId);
}
}
for (Account a : [select Id, OwnerId from Account where Id in :acctIdsToGet]){
acctIdToOwnerIdMap.put(a.Id, a.OwnerId);
ownerIdsToGet.add(a.OwnerId);
}
Map<Id, User> ownerIdToUser = new Map<id, User>([select Id from User where Id in :ownerIdsToGet
and IsActive = true]);
for (Contact c : trigger.new){
// Do Work
}
// Do Work
}
Formula Fields – Define the Field
Formula Fields – Save SOQL Calls
trigger ContactTrigger on Contact (after update) {
for (Contact c : trigger.new){
if (c.AccountId != null){
acctIdsToGet.add(c.AccountId);
}
}
for (Account a : [select Id, OwnerId from Account where Id in :acctIdsToGet
and Owner_is_Active__c = true]){
acctIdToOwnerIdMap.put(a.Id, a.OwnerId);
}
for (Contact c : trigger.new){
// Do Work
}
// Do Work
}
User Fields
trigger OpportunityTrigger on Opportunity (before delete) {
if (trigger.isDelete){
try{
Map<Id, User> userlist = new Map<Id, User>([select id, Name from User
where username in ('[email protected]',
'[email protected]')]);
for (Opportunity opp : trigger.old){
if (opp.Type == 'Renewal'){
if (!userlist.containskey(UserInfo.getUserId())){
opp.addError('Only Special Users can delete this Opportunity');
}
}
}
} catch (Exception e){
}
}
}
Configure the User Field
Review a User Field
User Fields in the Code
trigger OpportunityTrigger on Opportunity (before delete) {
if (trigger.isDelete){
try{
Map<Id, User> userlist = new Map<Id, User>([select id, Name from User
where Can_Delete_Opps__c = true)]);
for (Opportunity opp : trigger.old){
if (opp.Type == 'Renewal'){
if (!userlist.containskey(UserInfo.getUserId())){
opp.addError('Only Special Users can delete this Opportunity');
}
}
}
} catch (Exception e){
}
}
}
Custom Settings
What they are
Why you want them
 Like Custom Objects
 You deploy code when YOU want
 Apply to All users, or Role-specific
 Can control logging and business logic
 Admin-controlled
 Do not count against SOQL limits
Custom Settings - Defining
Custom Settings - Configuring
Custom Settings – Control Deployed Code
trigger OpportunityTrigger on Opportunity (before delete) {
if (trigger.isDelete){
try{
Map<Id, User> userlist = new Map<Id, User>([select id, Name from User
where Can_Delete_Opps__c = true)]);
for (Opportunity opp : trigger.old){
if (opp.Type == 'Renewal'){
if (!userlist.containskey(UserInfo.getUserId())){
opp.addError('Only Special Users can delete this Opportunity');
}
}
}
} catch (Exception e){
}
}
}
Custom Settings - Control Execution
trigger OpportunityTrigger on Opportunity (before delete) {
Trigger_Control__c tc = Trigger_Control__c.getInstance('OpportunityTrigger');
if (!Test.isRunningTest() || (tc != null && tc.Disabled__c)) { return;}
if (trigger.isDelete){
try{
Map<Id, User> userlist = new Map<Id, User>([select id, Name from User
where Can_Delete_Opps__c = true)]);
for (Opportunity opp : trigger.old){
if (opp.Type == 'Renewal'){
if (!userlist.containskey(UserInfo.getUserId())){
opp.addError('Only Special Users can delete this Opportunity');
}
}
}
} catch (Exception e){
}
}
}
Custom Settings - Control Business Logic
trigger OpportunityTrigger on Opportunity (before delete) {
Trigger_Control__c tc = Trigger_Control__c.getInstance('OpportunityTrigger');
if (Test.isRunningTest() || (tc != null & tc.Disabled__c)) { return;}
if (trigger.isDelete){
try{
Map<Id, User> userlist = new Map<Id, User>([select id, Name from User
where Can_Delete_Opps__c = true)]);
for (Opportunity opp : trigger.old){
if (opp.Type == 'Renewal'){
if (!userlist.containskey(UserInfo.getUserId())){
opp.addError('Only Special Users can delete this Opportunity');
}
}
}
} catch (Exception e){
}
}
}
Custom Settings - Configuring
Custom Settings - Control Business Logic
trigger OpportunityTrigger on Opportunity (before delete) {
Trigger_Control__c tc = Trigger_Control__c.getInstance('OpportunityTrigger');
if (Test.isRunningTest() || (tc != null & tc.Disabled__c)) { return;}
Opp_Processing__c op = Opp_Processing__c.getInstance(‘Default');
if (trigger.isDelete){
try{
Map<Id, User> userlist = new Map<Id, User>([select id, Name from User
where Can_Delete_Opps__c = true)]);
for (Opportunity opp : trigger.old){
if (opp.Type == op.Type_Value_for_Renewal__c){
if (!userlist.containskey(UserInfo.getUserId())){
opp.addError('Only Special Users can delete this Opportunity');
}
}
}
} catch (Exception e){
}
}
}
Custom Settings – Admin View
Custom Settings - Control Integrations
trigger CaseTrigger on Case (after insert) {
Integration_Control__c ic;
if ([SELECT Id, IsSandbox FROM Organization LIMIT 1].IsSandbox){
ic = Integration_Control__c.getInstance('Testing');
} else {
ic = Integration_Control__c.getInstance('Production');
}
// Do Work
}
Getting Started as a Salesforce Developer
What you need
What it Costs
1) A Salesforce org to work in
2) Training on Developer Tools
3) Training on Salesforce features
4) API Documentation
5) Sample code and ‘snippets’
6) Assistance and mentoring
0
Developer Community
https://developer.salesforce.com
Trailhead
https://developer.salesforce.com/trailhead
To Recap
 Your Relationship with your Admins is crucial to your success
 Use the right tool for each project
 Use Formula Fields to add Flexibility and Reduce SOQL
 Use Custom Data fields and Custom Settings to give Admins control of your code
 Use the Free Developer Community Resources to become Great!