2

I am working on a requirement to achieve the below:

  1. Whenever a lead is created by a user:
    • Check if that user is Rep Type (field present at user level), then add that user to Lead Share object with Edit Access Note: The person who creates this lead, doesn't becomes the lead owner because either a Queue or some other user becomes the Owner

Example: User-1 should become part of Lead Share record for this Lead

  1. Now when the Lead Owner is Updated:
    • Check if the new user/owner is Rep Type. If yes, then add him to Lead Share object with Edit Access. Here we also need to ensure, that the previous users added to Lead Share object should retain the Edit Access

Example: So basically both User-1 & User-2 should be present in Lead Share object now.

The 1st Part of requirement is fulfilled through writing a method which would be executed After Insert "leadShareToRepNew"(working absolutely fine!!). But the issue is with the 2nd Part which is executed After Update"leadShareToRepUpdate".

Error: After Creation of Lead by User-1, when User-1 changes the owner to User-2, the following is shown "System.DmlException: Insert failed. First exception on row 0; first error: INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY, insufficient access rights on cross-reference id: []"

PS: Lead sharing model is set to Private. Both the users have same profile and the "Transfer Lead" permission on that is set to true

Can someone suggest something here ?

public static void leadShareToRepNew(final List<Lead> listOfNewLeads){

    final Id directRecordTypeId = Schema.SObjectType.Lead.RecordTypeInfosByName.get('Direct').RecordTypeId;
    final Id forwardRecordTypeId = Schema.SObjectType.Lead.RecordTypeInfosByName.get('Forward').RecordTypeId;
    final Id rotaryRecordTypeId = Schema.SObjectType.Lead.RecordTypeInfosByName.get('Rotary').RecordTypeId;
    final Id convertRecordTypeId = Schema.SObjectType.Lead.RecordTypeInfosByName.get('VSG Convert Lead').RecordTypeId;

    set<Id> setRecordTypeId = new set<Id>{forwardRecordTypeId,directRecordTypeId,rotaryRecordTypeId,convertRecordTypeId};

    set<Id> setOfLeadRepUserIds = new set<Id>();
    set<Id> setOfLeadUserIds = new set<Id>();
    List<LeadShare> leadShareListNew = new List<LeadShare>();

    System.debug('@@@@Inside leadShareToRep method');

    for(Lead instOfLead : listOfNewLeads){
        setOfLeadUserIds.add(instOfLead.CreatedById);
    }
    System.debug('@@@@setOfLeadUserIds'+setOfLeadUserIds);

    for( User instOfUser : [Select Id, Dvp2_IsPartnerUser__c, Dvp2_Is_Rep__c from User where Id IN :setOfLeadUserIds and Dvp2_IsPartnerUser__c = true and Dvp2_Is_Rep__c = true ]){
        setOfLeadRepUserIds.add(instOfUser.Id);
    }
    System.debug('@@@@setOfLeadRepUserIds'+setOfLeadRepUserIds);

    for(Lead newLead : listOfNewLeads){
        System.debug('@@Condition-1'+setRecordTypeId.contains(newLead.RecordTypeId));
        System.debug('@@Condition-2'+setOfLeadRepUserIds.contains(newLead.CreatedById));

        if( setRecordTypeId.contains(newLead.RecordTypeId) && setOfLeadRepUserIds.contains(newLead.CreatedById) ){

            System.debug('@@@@Condition met'+newLead.Id+'@@@@'+newLead.CreatedById+'@@@@'+newLead);

            LeadShare ls = new LeadShare();
            ls.leadId = newLead.Id;
            ls.leadAccessLevel = 'Edit';
            ls.userOrGroupId = newLead.CreatedById;

            System.debug('@@Printing LeadShare record'+ls);

            leadShareListNew.add(ls);
        }


    }

    System.debug('@@leadShareList.isEmpty()'+leadShareListNew.isEmpty());

    if(!leadShareListNew.isEmpty()){

        System.debug('@@@@Inside leadShareListNew'+leadShareListNew);
        insert leadShareListNew;
    }

}

public static void leadShareToRepUpdate(final List<Lead> listOfNewLeads){

    final Id directRecordTypeId = Schema.SObjectType.Lead.RecordTypeInfosByName.get('Direct').RecordTypeId;
    final Id forwardRecordTypeId = Schema.SObjectType.Lead.RecordTypeInfosByName.get('Forward').RecordTypeId;
    final Id rotaryRecordTypeId = Schema.SObjectType.Lead.RecordTypeInfosByName.get('Rotary').RecordTypeId;
    final Id convertRecordTypeId = Schema.SObjectType.Lead.RecordTypeInfosByName.get('VSG Convert Lead').RecordTypeId;

    set<Id> setRecordTypeId = new set<Id>{forwardRecordTypeId,directRecordTypeId,rotaryRecordTypeId,convertRecordTypeId};

    set<Id> setOfLeadRepUserIds = new set<Id>();
    set<Id> setOfLeadUserIds = new set<Id>();
    List<LeadShare> leadShareListUpdate = new List<LeadShare>();

    System.debug('@@@@Inside leadShareToRepUpdate method');

    for(Lead instOfLead : listOfNewLeads){
        setOfLeadUserIds.add(instOfLead.OwnerId);

    List<LeadShare> lstOfLeadShare1 = [ Select Id, LeadId, userOrGroupId from LeadShare where LeadId = :instOfLead.Id ];
    System.debug('@@lstOfLeadShare-1'+lstOfLeadShare1);

    }


    for( User instOfUser : [Select Id, Dvp2_IsPartnerUser__c, Dvp2_Is_Rep__c from User where Id IN :setOfLeadUserIds and Dvp2_IsPartnerUser__c = true and Dvp2_Is_Rep__c = true ]){
        setOfLeadRepUserIds.add(instOfUser.Id);
    }
    System.debug('@@@@setOfLeadRepUserIds'+setOfLeadRepUserIds);

    for(Lead newLead : listOfNewLeads){
        System.debug('@@Condition-1'+setRecordTypeId.contains(newLead.RecordTypeId));
        System.debug('@@Condition-2'+setOfLeadRepUserIds.contains(newLead.OwnerId));
        System.debug('@@Printing Lead'+newLead.Id+'@@@@'+newLead.OwnerId+'@@@@'+newLead.OwnerId+'@@@@'+newLead);

        if( setRecordTypeId.contains(newLead.RecordTypeId) && setOfLeadRepUserIds.contains(newLead.OwnerId) ){

            System.debug('@@@@Condition met'+newLead.Id+'@@@@'+newLead.OwnerId+'@@@@'+newLead);

            LeadShare ls = new LeadShare();
            ls.leadId = newLead.Id;
            ls.leadAccessLevel = 'Edit';
            ls.userOrGroupId = newLead.OwnerId;

            System.debug('@@Printing LeadShare record'+ls);

            leadShareListUpdate.add(ls);
        }

    List<LeadShare> lstOfLeadShare2 = [ Select Id, LeadId, userOrGroupId from LeadShare where LeadId = :newLead.Id ];
    System.debug('@@lstOfLeadShare-2'+lstOfLeadShare2);

    }

    System.debug('@@leadShareListUpdate.isEmpty()'+leadShareListUpdate.isEmpty());

    if(!leadShareListUpdate.isEmpty()){

        System.debug('@@@@Inside leadShareListNew'+leadShareListUpdate);
        insert leadShareListUpdate;
    }

}
  • Issue was happening because for a record if the userId is already added to the corresponding share object, then the same userId can't be added again. If any attempt is made to add it programatically "INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY" error would be received – Lord Voldemort Mar 5 at 18:22
1

The "Transfer Lead" permission is not the issue here, nor the users' Profiles. Rather, it's that once lead ownership is transferred away from the running user, based on your Private Organization-Wide Default, the running user no longer has the right to add a LeadShare entry in the after update event (they're not the owner anymore). If, as I assume is the case, your trigger handler is running with sharing, that OWD is going to be enforced and result in this exception.

The blunt-instrument fix is to run your trigger handler without sharing. In many circumstances that's fine for a trigger handler, but only you can determine whether it's appropriate here.

The finer-instrument fix is to use a small helper class that runs without sharing. This can be an inner class of your trigger handler, and its only job would be to add sharing records:

private without sharing class ShareHelper {
    private static void addShares(List<LeadShare> shares) {
        // Do stuff
    }
}

I'm not sure I follow the logic of adding share records to the current owner of the Lead. That person naturally has edit rights based on their ownership. Is the overall objective to maintain something like a "Lead Team", and retain all previous owners' edit access on the Lead? Your current code isn't looking at the pre-update values of OwnerId, so it's only trying adding shares for the current owner.

  • Thanks @David Reed for taking out time to respond. The classes in my case (both LeadAction & LeadTriggerHandler) are without sharing. You can call out this requirement close to Lead Team member. But here, Lead might change many owners, hence only when the new owner is of Rep-Type, would he be added to LeadShare object. All previous owner would retain access provided they are of Rep-Type. – Lord Voldemort Jul 1 '18 at 14:49
1

I would recommend you move the actual apex managed sharing operation "into the future" by writing it to a temporary object where the record insert causes a trigger to execute that calls a queuable to perform the sharing calculations. The queuable queries the temporary object for the records which simply hold strings with the information needed to create the sharing records.

This is a common pattern described in Dan Appleman's Book Advanced Apex Programming. It moves the execution of the sharing operation into a different execution context where it can be done asynchronously while sharing tables are updated without the constraints of "with sharing". It also allows for improved error handling and recovery should you need it; something I wouldn't expect for a Lead record since there shouldn't be record locking contention.

This will help you out a LOT if there are a great number of Leads being created and transferred all at once.

0

Issue was happening because for a record if the userId is already added to the corresponding share object, then the same userId can't be added again. If any attempt is made to add it programatically "INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY" error would be received

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.