0

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 });
}
}
2
  • 1
    First error is because the (sobject) should actually be <sObject> trailhead.salesforce.com/en/content/learn/modules/…
    – manjit5190
    Commented Feb 26, 2021 at 14:34
  • @manjit5190 thanks! I had that before, but when I change it to <sObject> I get three other errors: line 24 Unexpected token 'Schema.Transaction__Share.rowCause.Manual' line 28 Missing " at 'for' line 1 Class LoanOfficerSharingRecalculation must implement the method: void Database.Batchable.finish(Database.BatchableContext) Commented Feb 26, 2021 at 17:41

1 Answer 1

1

This is likely a partial answer since I have not really dug into everything you are doing. However, I hope these points will be of use:

  1. When in an update within the trigger, you only need to do something when the Loan Officer has actually changed (use RHLtransaction.Loan_Officer__c != Trigger.oldMap(RHLtransaction.Id).Loan_Officer__c).

  2. Also, you only should create the share record when RHLtransaction.Loan_Officer__c != null.

  3. And finally, in this case, on the assumption that a previous loan officer assigned to the transaction should no longer see it when a different officer is assigned, you should delete the Transaction__Share for the out-going loan officer (Trigger.oldMap(RHLtransaction.Id).Loan_Officer__c). Make sure you bulkify the deletion of the Transaction__Share records.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .