SF admin here, writing my first Apex trigger, and I need your expertise!
I am learning Apex Managed Sharing, and I have set up a trigger that works when tested in sandbox. We have a custom object called Transaction and a field on the object called Loan Officer. This field is a lookup field to a user record. The OWD is private, and we need to grant READ access to the user listed in this field, so they will only see the records for which they are listed as the Loan Officer.
I referenced SF documentation on Apex Managed Sharing found here and modified it, but I don't completely understand the bottom half (lines 37-65 in Apex Managed Sharing Example), especially because I am doing both insert AND update, so I think I may still need to make some modification to my code there, but not sure what to modify for the before update part.
Additionally, because I need to do both after insert AND before update, when there is an update, I want to remove the user who was given access previously and only allow the read access to the new user in the Loan Officer field. I have read about recalculating Apex Managed Sharing here and think this might be the solution, but having issues setting this up. I created an Apex class (with a couple of problems, only one of which I can't clear up), but I'm not sure how to integrate this into my code so that it runs when a record is updated.
Below is my code for both the trigger and the class for recalculating. Any help is appreciated...first-timer here, and not sure if I'm on the right track!
Apex trigger
trigger LoanOfficerSharing on Transaction__c (after insert, before update) {
if(trigger.isInsert){
//Create a new list of sharing objects for Transaction
List<Transaction__Share> transactionShrs = new List<Transaction__Share>();
//Declare variables for loan officer sharing
Transaction__Share loShr;
for(Transaction__c RHLtransaction : trigger.new){
//Instantiate the sharing objects
loShr = new Transaction__Share();
//Set the ID of record being shared
loShr.ParentID = RHLtransaction.Id;
//Set the ID of user or group being granted access
loShr.UserOrGroupId = RHLtransaction.Loan_Officer__c;
//Set the access level
loShr.AccessLevel = 'read';
//Set the Apex sharing reason for loan officer
loShr.RowCause = Schema.Transaction__Share.RowCause.Manual;
//Add objects to list for insert
transactionShrs.add(loShr);
}
}else if(trigger.isUpdate){
//Create a new list of sharing objects for Transaction
List<Transaction__Share> transactionShrs = new List<Transaction__Share>();
//Declare variables for loan officer sharing
Transaction__Share loShr;
for(Transaction__c RHLtransaction : trigger.new){
//Instantiate the sharing objects
loShr = new Transaction__Share();
//Set the ID of record being shared
loShr.ParentID = RHLtransaction.Id;
//Set the ID of user or group being granted access
loShr.UserOrGroupId = RHLtransaction.Loan_Officer__c;
//Set the access level
loShr.AccessLevel = 'read';
//Set the Apex sharing reason for loan officer
loShr.RowCause = Schema.Transaction__Share.RowCause.Manual;
//Add objects to list for insert
transactionShrs.add(loShr);
}
//Insert sharing records and capture save result
//The false parameter allows for partial processing if multiple records are passed
//into the operation
Database.SaveResult[] lsr = Database.insert(transactionShrs,false);
//Create counter
Integer i=0;
//Process the save results
for(Database.SaveResult sr : lsr){
if(!sr.isSuccess()){
//Get the first save result error
Database.Error err = sr.getErrors()[0];
//Check if the error is related to a trivial access level
//Access levels equal or more permissive than the object's default
//access level are not allowed.
//These sharing records are not required and thus an insert exception is
//acceptable.
if(!(err.getStatusCode() == StatusCode.FIELD_FILTER_VALIDATION_EXCEPTION
&& err.getMessage() .contains('AccessLevel'))){
//Throw an error when the error is not related to trivial access level.
trigger.newMap.get(transactionShrs[i].ParentId).
addError('Unable to grant sharing access due to the following exception:'
+ err.getMessage());
}
}
i++;
}
}
}
Apex Class Problem on line 1 says: Expecting '{' but was '('
global class LoanOfficerSharingRecalculation implements Database.Batchable(sObject) {
//String to hold email address that emails will be sent to.
//Replace its value with a valid email address.
static String emailAddress = '[email protected]';
//The start method is called at the beginning of a sharing recalculation.
//This method returns a SOQL query locator containing the records
//to be recalculated.
global Database.QueryLocator start(Database.BatchableContext BC){
return Database.getQueryLocator([SELECT Id, Loan_Officer__c FROM Transaction__c]);
}
//The executeBatch method is called for each chunk of records returned from start.
global void execute(Database.BatchableContext BC, List<sObject> scope);
//Create a list of Transaction__Share objects to be inserted.
List<Transaction__Share> newTransactionShrs = new List<Transaction_Share>();
//Locate all existing sharing records for the Transaction records in the batch.
//Only records using an Apex sharing reason for this app should be returned.
List<Transaction__Share> oldTransactionShrs = [SELECT Id FROM Transaction__Share WHERE ParentID IN
:transactionMap.keySet() AND
(RowCause = Schema.Transaction__Share.rowCause.Manual)];
}
//Construct new sharing records for the loan officer on each Transaction record.
for(Transaction__c transaction : transactionMap.values()){
Transaction__Share transactionloShr = new Transaction__Share();
//Set the ID of user (loan officer) on the Transaction record being granted access.
transactionloShr.UserOrGroupId = transaction.Loan_Officer__c;
//The hiring manager on the transaction should always have 'Read Only' access.
transactionloShr.AccessLevel = 'Read';
//The ID of the record being shared
transactionloShr.ParentId = transactionId;
//Set the rowCause to the Apex sharing reason for the loan officer.
//This establishes the sharing record as Apex managed sharing.
transactionloShr.RowCause = Schema.Transaction__Share.RowCause.Loan_Officer__c;
//Add sharing record to list for insertion.
newTransactionShrs.add(transactionloShr);
}
try {
//Delete the existing sharing records.
//This allows new sharing records to be written from scratch.
Delete oldTransactionShrs;
//Insert the new sharing records and capture the save result.
//The false parameter allows for partial processing if multiple records are
//passed into operation.
Database.SaveResult[] lsr = Database.insert(newTransactionShrs,false);
//Process the save results for insert.
for(Database.SaveResult sr : lsr){
if(!sr.isSuccess()){
//Get the first save result error.
Database.Error err = sr.get Errors()[0];
//Check if the error is related to trivial access level.
//Access levels equal or more permissive than the object's default
//access leve are not allowed.
//These sharing records are not required and thus an insert exception
//is acceptable.
if(!(err.getStatusCode() == StatusCode.FIELD_FILTER_VALIDATION_EXCEPTION
&& err.getMessage().contains('AccessLevel'))){
//Error is not related to trivial access level.
//Send an email to the Apex job's submitter.
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {emailAddress};
mail.setToAddresses(toAddresses);
mail.setSubject('Apex Sharing Recalculation Exception');
mail.setPlainTextBody(
'The Apex sharing recalculation threw the following exception:' +
err.getMessage());
Messaging.sendEmail(new Messaging.SingleEmailMessage[] {mail});
}
}
}
} catch(DmlException e) {
//Send an email to the Apex job's submitter on failure.
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {emailAddress};
mail.setToAddresses(toAddresses);
mail.setSubject('Apex Sharing Recalculation Exception');
mail.setPlainTextBody(
'The Apex sharing recalculation threw the following exception:' +
e.getMessage());
Messaging.sendEmail(new Messaging.SingleEmailMessage[] {mail});
}
}
//The finish method is called at the end of a sharing recalculation.
global void finish(Database.BatchableContext BC){
//Send an email to the Apex job's submitter notifying of job completion.
Messaging.SingleEmailMessage mail = new Messaging.SingleEmailMessage();
String[] toAddresses = new String[] {emailAddress};
mail.setToAddresses(toAddresses);
mail.setSubject('Apex Sharing Recalculation Completed.');
mail.setPlainTextBody('The Apex sharing recalculation finished processing');
Messaging.sendEmail(new Messaging.SingleEmailMessage[] { mail });
}
}
(sobject)
should actually be<sObject>
trailhead.salesforce.com/en/content/learn/modules/…