Among the many limitations of the standard OpportunityContactRole object in Salesforce is the inability to put a trigger on it. The use case I wanted to solve was to aggregate and copy the contact information related to certain roles to the parent opportunity. Some options I considered:
1. Create a batch job to query OCR nightly and update the parent opportunity
2. Create a trigger on Opportunity updates to fetch the OCR data
3. A combination of 1 and 2
4. Replace the standard OCR with a custom OCR and create the necessary trigger
I finally came across this blog post, and with some tinkering, found that it worked for our use-case. The idea is to hide a inline visualforce page within the standard layout and use that vf page's action property to run some code. The author chose an asynchronous opportunity update whereas I went with a synchronous update. The action property on the page tag is generally used for redirects, but could certainly be used for defaulting data as well.
Some issues I came across:
1. Since visualforce is served up from a different domain, I was seeing my page redirected by a servlet to a "page not found". I was able to stop this by returning a non null page reference.
2. The js console in chrome logs some refusal errors due to the different domains. This went away with the resolution to #1.
3. I was unable to refresh the parent page from the inline vf page after the update, so the user has to manually refresh the opportunity to see the effect. This is still the case but our users were ok with it.
4. The hidden vf page's controller is trying to execute a DML. This is usually followed by a page refresh. The problem is that the page refresh is occurring within the inline VF, which,when it renders, again executes the controller's action. This can cause some serious looping, which is why it is imperative for the action method to check whether an update is required or not.
Showing posts with label trigger. Show all posts
Showing posts with label trigger. Show all posts
Friday, August 9, 2013
Tuesday, May 21, 2013
User Hierarchy based Sharing
I have a client who has flattened out their role hierarchy to enable some other business processes within Salesforce. So when it came for them to implement an object in Salesforce that required some hierarchy based sharing we had to build something custom based on the user record's manager hierarchy. For example, if Joe owns a record, his manager, Jane, should have read access to the record. Jane's manager Mike, should also have read access. This access should continue to the top of the hierarchy.
In the code below, keep this in mind:
Programmatically, it is pretty easy to create a trigger on an object and create a related share for the object's owner. Salesforce's documentation of apex managed sharing does an adequate job of this. Where I scratched my head a little was figuring out how to share all the way up the hierarchy. And do the sharing without hitting any governor limits. I did some searching of blogs and found an interesting post by Jeff Douglas. Jeff's solution is elegant, but I wanted to try another way myself. In plain english here is what I wanted to do:
1. Create an object share record for the record's owner's manager
2. Then, create another object share for that manager's manager
3. and so on...
4. Insert list of object share records
It felt like a loop to me, so what I tried initially was to iterate through the object share collection, and for each record in the collection, create another share record, assign the manager, then add it to the collection and continue until a manager is no longer found for the user. In this snippet, the collection of all users and their managers is held in a map:
for(User u: [Select u.Id,u.ManagerId from User u])
{
mapUserManagers.put(u.Id,u.ManagerId);
}
for(someObject__Share os: sharesToCreate)
{
if(mapUserManagers.get(os.UserOrGroupId) != null)
{
someObject__Share os_mgr = new someObject__Share ();
os_mgr.AccessLevel = MGR_ACCESS;
os_mgr.ParentId = es_mgr.ParentId;
os_mgr.RowCause = MGR_ROW_CAUSE;
os_mgr.UserOrGroupId = mapUserManagers.get(os_mgr.UserOrGroupId);
sharesToCreate.add(os_mgr);
}
}
This approach throws an error: "Cannot Modify a Collection While It Is Being Iterated"
Some folks over at stackexchange explain the issue with the index behind this loop.
So, I took a slightly different approach to the loop:
allUsers = [Select u.Id,u.ManagerId from User u];
for(User u: allUsers)
{
mapUserManagers.put(u.Id,u.ManagerId);
}
Id managerId;
for(SomeObject__c so: RecsToProcess)
{
managerId = mapUserManagers.get(so.OwnerId);
do{
SomeObject__Share sos = new SomeObject__Share ();
sos.AccessLevel = SHARE_MGR_ACCESS;
sos.ParentId = so.Id;
sos.RowCause = SHARE_MGR_ROW_CAUSE;
sos.UserOrGroupId = managerId;
sharesToCreate.add(sos);
managerId = mapUserManagers.get(managerId);
} while (managerId !=null);
}
//allow for partial successes
Database.SaveResult[] srList = Database.insert(sharesToCreate, false);
This compiles without issue and achieves the effect of creating a collection of share records for each of the managers with only 1 SOQL call. It's another way to solve the hierarchy loop challenge. Hope it helps you find a solution to your own problem.
In the code below, keep this in mind:
Joe -> Jane -> Mike -> Mary
In our org, the sensitive records that require Private org wide defaults give record owner's read-write access by virtue of their ownership of the record. For all others to have read access, we have to create sharing records for the object.
A share is composed of the following elements:
- ParentId - the record that you want to share with others
- RowCause - the custom reason you are sharing this record
- AccessLevel - the level of access being given (in our ex: read)
- UserOrGroupId - the user (or group) who should have access
By creating a record with these required elements, these private records can be shared with those users (or groups) mentioned in the shares.
Programmatically, it is pretty easy to create a trigger on an object and create a related share for the object's owner. Salesforce's documentation of apex managed sharing does an adequate job of this. Where I scratched my head a little was figuring out how to share all the way up the hierarchy. And do the sharing without hitting any governor limits. I did some searching of blogs and found an interesting post by Jeff Douglas. Jeff's solution is elegant, but I wanted to try another way myself. In plain english here is what I wanted to do:
1. Create an object share record for the record's owner's manager
2. Then, create another object share for that manager's manager
3. and so on...
4. Insert list of object share records
It felt like a loop to me, so what I tried initially was to iterate through the object share collection, and for each record in the collection, create another share record, assign the manager, then add it to the collection and continue until a manager is no longer found for the user. In this snippet, the collection of all users and their managers is held in a map:
for(User u: [Select u.Id,u.ManagerId from User u])
{
mapUserManagers.put(u.Id,u.ManagerId);
}
for(someObject__Share os: sharesToCreate)
{
if(mapUserManagers.get(os.UserOrGroupId) != null)
{
someObject__Share os_mgr = new someObject__Share ();
os_mgr.AccessLevel = MGR_ACCESS;
os_mgr.ParentId = es_mgr.ParentId;
os_mgr.RowCause = MGR_ROW_CAUSE;
os_mgr.UserOrGroupId = mapUserManagers.get(os_mgr.UserOrGroupId);
sharesToCreate.add(os_mgr);
}
}
This approach throws an error: "Cannot Modify a Collection While It Is Being Iterated"
Some folks over at stackexchange explain the issue with the index behind this loop.
So, I took a slightly different approach to the loop:
allUsers = [Select u.Id,u.ManagerId from User u];
for(User u: allUsers)
{
mapUserManagers.put(u.Id,u.ManagerId);
}
Id managerId;
for(SomeObject__c so: RecsToProcess)
{
managerId = mapUserManagers.get(so.OwnerId);
do{
SomeObject__Share sos = new SomeObject__Share ();
sos.AccessLevel = SHARE_MGR_ACCESS;
sos.ParentId = so.Id;
sos.RowCause = SHARE_MGR_ROW_CAUSE;
sos.UserOrGroupId = managerId;
sharesToCreate.add(sos);
managerId = mapUserManagers.get(managerId);
} while (managerId !=null);
}
//allow for partial successes
Database.SaveResult[] srList = Database.insert(sharesToCreate, false);
This compiles without issue and achieves the effect of creating a collection of share records for each of the managers with only 1 SOQL call. It's another way to solve the hierarchy loop challenge. Hope it helps you find a solution to your own problem.
Subscribe to:
Posts (Atom)