How To

Allow Users of the Same Group the Ability to Delete Each Other’s Records

In a public read/write sharing model, if a user can view the record and has delete permissions for the object, the user can delete the record. However, what if the company’s business model allows for the ability to read/write but they only want certain users to delete each other’s records, not everyone?

Or, your company has a private sharing model. While a user may have read/write ability to the record via sharing rules, the user cannot delete the someone else’s record unless the user is the record owner, the manager of the owner or you have Modify All permissions for the object or Modify All Data system permissions.

Bear with me. I’m going to get on my soapbox for a couple of minutes…

We should really avoid granting Modify All for the object, and only grant those “super powers” to those selected users in your Salesforce org, such as your Salesforce business administrators or operations users, whose job it is to manage the data in the org, including deleting records as business processes require it. I firmly believe granting users Modify All Data system permissions should really be left to the System Administrator profile only. There should be a strong justification for a user’s or profile’s need for Modify All Data permissions, including an integration user profile.

As a system administrator, you want to avoid handing out the delete permission out to regular users, where you can. However, that being said, I know that there may be situations where it may be okay to have regular users delete records from the system, esp. data that is considered optional, not critical to sales forecasts, sales goals, etc.

We can address both delete requirements above with some declarative creativity.

Here are a few lessons learned from implementing this use case:

  • You can use a visualforce page via a button to invoke a visual workflow. Within the visualforce page, you can pass parameters from the record to the visual workflow.
  • You can override the standard Salesforce buttons (Accept, Clone, Delete, Edit, New and View) with visualforce pages.
  • Avoid “hardcode” ID references in your visual workflow. Otherwise, you will have different versions of the visual workflow in the different regions of your org. I will take an extra flow element or two to perform a record lookup using the API name to obtain the ID. Alternatively, you can reference the ID in a custom metadata type/custom setting where you can modify the data outside the visual workflow itself.
  • Provide descriptions, where provided, in Salesforce. This may be tedious step, I know, but your future self will thank you when you are trying to remember what you configured or assist other/future admins when troubleshooting or enhancing what was built. This includes noting the data stored in a custom field, providing the purpose of a process builder, etc.
  • For any data actions (fast and record lookup, create, update and delete actions) performed in visual workflow, best practice is to include a flow element to send an email to your Salesforce administrator about the fault.

Flow trick: To getting the Fault connector to appear, either draw the regular connector link to another flow element or connect it to a temporary flow element. Draw the fault connector to the Send Email element. Then, go back and delete the regular connector.

 

Business Use Case: Addison Dogster is a system administrator at Universal Container. Sammy Sunshine is the Sales Manager. Currently, users are creating records in a custom object. Sammy approached Addison with a requirement that sales users belonging to the same group can read/write and delete each other’s records. Sales users of a different group cannot delete the first group’s records, but they can delete each others. Users who have Modify All Data and Modify All permissions for the object can still delete the record.

Solution: Addison is able to solve this declaratively with the following:

  • Public groups, to group users that need to be able to delete each other’s records. In this use case, we will create two groups.
  • Custom permission, to add to the profiles to continue to allow them to delete the records.
  • Visual workflow, to determine whether the current user belongs in the same group as the record owner or has the custom permission to allow the record deletion.
  • Visualforce page, to invoke the delete record visual workflow and pass record parameters to the flow for use. This visualforce page will need to be added to all profiles that will be using the delete function.
  • Object Delete button override, to override the standard delete record functionality with the visualforce page.
  • Add Object Delete permission to the regular user profile (or permission set) to allow the regular user to delete each other’s records.
  • Add the “Transfer Record” system permission to the regular user profile (or permission set) to allow the regular user to delete each other’s records.
  • Add the “Run Flows” system permission to allow users to run visual workflow.  

Delete Custom Object Record Same Sharing Group -No Title - flow.JPG

Quick Steps:

This assumes that you will set up the object sharing rules using the two groups we will create here, as needed.

1. Create a two public groups (Manage Users | Public Groups) called CustomObjectGroupA and CustomObjectGroupB. Note: The Group Name will be used in the visual workflow during the Record Lookup flow elements.

CustomObject-PublicGroup.JPG

CustomObject-PublicGroupB.JPG

2. Create a custom permission (Develop | Custom Permissions). Click on the New button. Provide the label name “Delete Custom Object Records.”

DeleteCustomObjectRecord_CustomPermission.JPG

Best Practice Tip: Don’t forget to provide a description so you and other/future admins know what this custom permission is used for.

3. Create a visual workflow (Create | Workflows & Approvals | Flows). This flow will look up the group of the user and the record owner and determine whether a user is in the same group as the record owner. If the user is in the same group, then the flow will change the record owner to the user and delete the record. If the user is not in the same group as the user, then the flow will determine whether the user has the custom permission Delete Custom Records. If the user has this custom permission, then the flow will delete the record. If the user does not have this custom permission, then the user has no delete access and are presented with an error message.

Delete Custom Object Record Same Sharing Group -flow.JPG

A. We will create 3 variables upfront. These will be passed as parameters from the visualforce page we will create in Step 4. These variables will store the user’s ID, the custom record’s owner ID and the custom record’s ID.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what these variables are used for.

varUserID.JPG

varCustomObjectRecordID.jpg

B. Create the Record Lookup, that will lookup the Group object to obtain the ID for the CustomObjectGroupA public group. We will perform the query using the DeveloperName (i.e. API Name). This step prevents the need to embed ID references within the flow. Note: The value must match the Group Name in the public group record.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this Record Lookup does.

CustomObject-PublicGroup-DeveloperName.JPG

DeleteRecordViewing-RecordLookup-PublicGroupID.JPGDeleteRecordViewing-RecordLookup-PublicGroupID1.JPG

Once we locate the group record, we want to take the group ID and store the value in a newly created variable per the screenshot below.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for.

varCustomObjectPublicGroupAID.JPG

C. Now, we will do the same to do a Record Lookup to obtain the ID of the CustomObjectGroupB public group.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this Record Lookup does.

CustomObject-PublicGroup-DeveloperNameB.JPG

DeleteRecordViewing-RecordLookup-PublicGroupBID.JPG

DeleteRecordViewing-RecordLookup-PublicGroupBID1.JPG

Once we locate the group record, we want to take the group ID and store the value in a newly created variable per the screenshot below.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for.

varCustomObjectPublicGroupBID

D. We will perform a Record Lookup to now pull the record owner’s public group. The lookup will be performed on the GroupMember object using the query to where the Group ID equals the variable varCustomObjectPublicGroupAID or varCustomObjectPublicGroupBID and UserID equals the variable varCustomObjectOwnerID.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this Record Lookup does.

DeleteRecordViewing-RecordLookup-RecordOwnerPublicGroupID.JPGDeleteRecordViewing-RecordLookup-RecordOwnerPublicGroupID1.JPG

Once found, we will store the GroupID in a new variable called varRecordOwnerAssignedCustomObjectPublicGroupID.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for.

varRecordOwnerAssignedCustomObjectPublicGroupID.JPG

E. Let’s perform another Record Lookup flow element. This time, we are looking up the user’s public group. The lookup will be performed on the GroupMember object using the query to where the Group ID equals the variable varCustomObjectPublicGroupAID or varCustomObjectPublicGroupBID and UserID equals the variable varUserID.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this Record Lookup does.

DeleteRecordViewing-RecordLookup-UserPublicGroupID.JPGDeleteRecordViewing-RecordLookup-UserPublicGroupID1.JPG

Once found, we will store the GroupID in a new variable called varUserAssignedCustomObjectPublicGroupID.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this variable is used for.

varUserAssignedCustomObjectPublicGroupID.JPG

F. Now that we have the public group IDs for the record owner and user, we now need to make a decision using the Decision flow element.

Let’s name the editable outcome as “Same Public Group”, where {!varUserAssignedCustomObjectPublicGroupID} equals {!varRecordOwnerAssignedCustomObjectPublicGroupID}

We’ll name the Default Outcome as “Not the Same Group.”

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this Decision does.

DeleteRecordViewing-Decision-UserandOwnerSameGroup.JPG

G. We are now going to create a formula resource that will reference the custom permission “Delete Custom Object Records”.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this formula is.

varUserProfileCustomPermissionDeleteCustomObjectRecord.JPG

H. We need to make another Decision flow element. This time to see if the user has the custom permission Delete Custom Object Record.

Let’s name the editable outcome as “Delete Custom Object Record Custom Permission”, where {!varUserProfileCustomPermissionDeleteCustomObjectRecord} equals {!$GlobalConstant.True}.

We’ll name the Default Outcome as “No Access.”

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this Decision does.

DeleteRecordViewing-Decision-CustomPermission.JPG

I. Create a Screen to show an error message to show the user that s/he does not have access to delete the record.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this Screen does.

NoAccessScreen.JPG

J. We create a Record Update flow to update the ownerID to the current user ({!varUserID}) where the custom object record’s ID equals {!varCustomObjectRecordID}.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this Record Update does.

DeleteRecordViewing-RecordUpdate-Ownership.JPG

DeleteRecordViewing-RecordUpdate-Ownership1.JPG

K. Create a Record Delete flow element performed on the custom object where the ID equals the variable {!varCustomObjectRecordID}.

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this Record Delete does.

DeleteRecordViewing-RecordDelete.JPG

L. Draw the Decision connectors from the Decision flow element “Are the User and Owner in the Same Group”.

Draw the “Not the Same Group” connector from the Decision flow element “Are the User and Owner in the Same Group” to the Decision “Determine Delete Custom Object Record Custom Permission”

NotSameGroupConnector.JPG

Draw the“Same Public Group” connector from the Decision flow element “Are the User and Owner in the Same Group” to the Record Update flow element “Change Owner to Current User”

The end result looks like this.

Delete Custom Object Record Same Sharing Group-DecisionConnectors1.JPG

M.Draw the Decision connectors from the Decision flow element “Determine Delete Custom Object Record Custom Permission”.

Draw the “No Access” connector from the Decision flow element “Determine Delete Custom Object Record Custom Permission” to the Screen “No Access”

NoAccessConnector.JPG

Draw the “Delete Custom Object Record Custom Permission” connector from the Decision flow element “Determine Delete Custom Object Record Custom Permission” to the Record Delete “Delete the Custom Object Record”.

Delete Custom Object Record Same Sharing Group-DecisionConnectors2.JPG

N. Draw the other connectors between the flow elements.

[Note: The visual flow diagram includes the Record Lookup element: Using Custom Metadata Type in Visual Workflow Fault Email.

Delete Custom Object Record Same Sharing Group -No Title - flow

As a best practice, where there is a DML action (fast or record create, update, delete or lookup action) in a flow, you should also include notification of a flow fault.

Note: To avoid “hardcoding” email addresses in a fault email, refer to a post Using Custom Metadata Type in Visual Workflow Fault Emails.

Select the Send Email flow element that is listed under Static Actions, not under Quick Actions.

StaticActions

Best practice tip: Don’t forget to provide a description so you and other/future admins know what this send email element is supposed to do.

Configure the body to show the fault message, subject and email address(es).

Body: Fault Message: {!$Flow.FaultMessage}

Subject: Error: Delete Custom Object Record with the Same Sharing Group

Email Addresses (comma-separated): <email address>

DeleteRecordViewing-SendFaultEmail.JPG

You will need to draw a connector line between the Record Lookup, Record Update and Send Email elements.

Can’t seem to get the fault connector to appear? Create a temporary flow element (circled below), draw your first connector to that temporary flow element. Then, draw another connector to the send element. This connector has the word “FAULT” in it. Once that is completed, delete the temporary flow element you created.

This is the end result.

[Note: The visual flow diagram includes the Record Lookup element: Using Custom Metadata Type in Visual Workflow Fault Email. If you choose not to implement that, your starting element is the Record Lookup “Lookup data from the Custom Data Metadata Type Flow Reference”.]

Delete Custom Object Record Same Sharing Group -No Title - flow

O. Draw the other connectors between the flow elements to match the diagram above.

P. Click on the Save button and provide the following information

Best Practice Tip: Don’t forget to provide a description so you and other/future admins know what this visual workflow is for.

DeleteRecordViewing-FlowProperties.JPG

Q. Click the “Close” button.

R. On the flows screen, activate the flow.

DeleteRecordViewing-ActivateFlow.JPG

4. Create a visualforce page (Develop | Visualforce Pages) that invokes the visual workflow created in Step 3. We will call this page “DeleteCustomObjectRecord”.

Best Practice Tip: Don’t forget to provide a description so you and other/future admins know what this visualforce page does.

DeleteCustomObjectRecord-Visualforcepage.JPG

Don’t let this part scare you. I’m not a developer so if I can copy, paste and tweak, so can you.

Here are the contents of your visualforce page:

<apex:page standardController=”Custom_Object__c“>
    <flow:interview name=”Delete_Custom_Object_Record_Same_Sharing_Group” finishLocation=”{!URLFOR(‘/a09/o‘)}”>
         <apex:param name=”varCustomObjectOwnerID” value=”{!Custom_Object__c.Owner}”/>
        <apex:param name=”varCustomObjectRecordID” value=”{!Custom_Object__c.Id}”/>
        <apex:param name=”varUserID” value=”{!$User.Id}”/>
    </flow:interview>
</apex:page>

Let me explain the bolded items you will need to modify in your visualforce page:

  • standardController: Reference the object (API name) that this visual flow will execute on.
  • interview name: This is the name of your Flow Name.

Delete Custom Object Record Same Sharing Group -reference in VF.JPG

5. Now, we need to override the Delete button on the custom object (Create | Objects | <custom object> | Buttons, Links, and Actions | click on the Edit link next to the Delete button.) Select to visualforce page created in Step 4 to override the button with.

Best Practice Tip: Don’t forget to provide a comment so you and other/future admins know what this button override does.

DeleteButtonOverride.JPG

6. For the users who need the ability to perform this delete function, you will need to make a few changes to those profile changes:

Manage Users | Profiles | <profile> | System Permissions. Enable “Transfer Record” and “Run Flows”.

Manage Users | Profiles | <profile>  | Objects | <custom object>, ensure the Delete permission is set.

Manage Users | Profiles | <profile>  | Visualforce pages, add the Visualforce page created in Step 4. “DeleteCustomObjectRecord”.

VisualforcePage.JPG

7. For users that have Modify All Data or Modify All object permissions, you need to add the custom permission “Delete Custom Object Records.” (Manage Users | Profile | <profile> | Custom Permissions), edit, add “Delete Custom Object Records” and save.

AddDeleteCustomPermissionToUser.JPG

8. You may consider automating the assignment of the users to the two public groups. Read my post Auto Assign Public Groups to Users Based on Profile.

 

That’s it. Congrats, you’ve implemented the solution!

Now, before you deploy the changes to Production, you need to test your configuration changes.

Deployment Notes/Tips:

  • Public groups, custom permission, visualforce page and visual workflow can be deployed to Production in a change set.
  • For the profile changes and the button override, if you have a tool like Snapshot by Dreamfactory, you can use the tool to deploy changes. Otherwise, these changes need to be made manually.
  • You will find the custom metadata type information under the Custom Metadata Type in the Component Type dropdown. However, locate the data record for the custom metadata types by looking for the custom metadata type name in the Component Types dropdown.
  • You will find the visual workflow component in a change set under the Flow Definition component type.
  • Activate the visual flow after it is deployed in Production as flows are deployed as inactive.
  • Once you deploy the public groups, you will need to add the appropriate users to both public groups.
Advertisements

4 thoughts on “Allow Users of the Same Group the Ability to Delete Each Other’s Records

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