7

I have an odd issue where I am hoping its just something obvious that I am missing, so hoping to get another set of eyes on it.

I have a fairly simple batch class that is looking for CampaignMembers that were created a week ago or more, and haven't changed status yet. Those that it finds, it updates the Status and reassigns the lead to a specific queue.

For the purpose of this, I simplified the code and broke it down to its core basic functionality.

public without sharing class JACampaignCleanUpBatch implements Database.Batchable<SObject>, Schedulable {

    public static String DefaultSched = '0 00 00 * * ?';  //Every Day at Midnight 
    public static JA_Campaign_Clean_Up_Settings__c setting = JA_Campaign_Clean_Up_Settings__c.getOrgDefaults();

    public final string query;

    public JACampaignCleanUpBatch() {

        Date sevenDaysAgo = system.today().addDays(-7);
        DateTime sevenDaysAgoDT = DateTime.newInstance(sevenDaysAgo.year(), sevenDaysAgo.month(), sevenDaysAgo.day());
        String sevenDaysAgoDTText = sevenDaysAgoDT.format('yyyy-MM-dd\'T\'hh:mm:ss\'Z\'');
        query = 'Select Id, Status, Lead.Id, CreatedDate From CampaignMember Where ' +
                'Campaign.JA_Campaign__c = True And Status = \'' + setting.Status__c + '\' And ' +
                'CreatedDate <= ' + sevenDaysAgoDTText;
    }

    public Database.QueryLocator start(Database.BatchableContext bc) {
        return Database.getQueryLocator(query);
    }

    public void execute(Database.BatchableContext bc, List<CampaignMember> scope) {

        list<CampaignMember> members2Update  = new list<CampaignMember>();

        set<Id> leadIDs = new set<Id>();

        for(CampaignMember member : scope){
            member.Status = 'Not Contacted';
            if(member.LeadId != null){
                leadIDs.add(member.Lead.Id);
            }   
            members2Update.add(member);            
        }

        Group recycledQueue = [Select Id from Group where Type = 'Queue' AND Name =: setting.Queue_Name__c LIMIT 1];

        list<Lead> leads = [Select Id, OwnerId From Lead Where Id In : leadIDs and isConverted = false];
        for(Lead l : leads){
            l.OwnerId = recycledQueue.Id;
        }

        update members2Update;  **//THIS IS CAUSING THE ERROR**
        update leads;         
    }

    public void finish(Database.BatchableContext bc) {
        system.debug('JOB IS DONE!!');
    }
}

The issue that I run into is that I am getting access errors when running the batch. I get an error like this

First error: Update failed. First exception on row 0 with id 00v37000005jXdlAAE; first error: INSUFFICIENT_ACCESS_OR_READONLY, insufficient access rights on object id: []

There error comes when trying to update the CampaignMember Status. It's as if I don't have full access to the record. SO then I tried using Apex Anon to update the member status like below

CampaignMember memb = [Select Id, Name, Status From CampaignMember Where Id = '00v37000005jXdlAAE'];
memb.Status = 'Not Contacted';
update memb;

No issue, no error. So is there something I am doing in my batch?

Also worth noting that it works for a large majority of the CampaignMembers, it seems to only be tripped up on certain ones. Can;t find a pattern though.

Lead Sharing is private but I am running as sys admin and apex runs in system context anyway. The batch running user does have Marketing User permission set. I am just stumped as to what the issue is.

Again, I am fully expecting (or hoping I guess) that this is something silly I am overlooking. Just hoping you all can give me an extra set of eyes and tell me what I am doing wrong.

EDIT

Based on some comments and answers, I removed the Lead portions and changed the class to literally just update Campaign Member statuses, and I am still getting the same behavior.

This is the simplified class that just updates statuses.

    public without sharing class JACampaignCleanUpBatch implements Database.Batchable<SObject>, Schedulable {

        public static String DefaultSched = '0 00 00 * * ?';  //Every Day at Midnight 
        public static JA_Campaign_Clean_Up_Settings__c setting = JA_Campaign_Clean_Up_Settings__c.getOrgDefaults();

        public final string query;

        public JACampaignCleanUpBatch() {

            Date sevenDaysAgo = system.today().addDays(-7);
            DateTime sevenDaysAgoDT = DateTime.newInstance(sevenDaysAgo.year(), sevenDaysAgo.month(), sevenDaysAgo.day());
            String sevenDaysAgoDTText = sevenDaysAgoDT.format('yyyy-MM-dd\'T\'hh:mm:ss\'Z\'');
            query = 'Select Id, Status, Lead.Id, CreatedDate From CampaignMember Where ' +
                    'Campaign.JA_Campaign__c = True And Status = \'' + setting.Status__c + '\' And ' +
                    'CreatedDate <= ' + sevenDaysAgoDTText;
        }

        public Database.QueryLocator start(Database.BatchableContext bc) {
            return Database.getQueryLocator(query);
        }

        public void execute(Database.BatchableContext bc, List<CampaignMember> scope) {

            list<CampaignMember> members2Update  = new list<CampaignMember>();
            for(CampaignMember member : scope){
                member.Status = 'Not Contacted';
                members2Update.add(member);            
            }

            update members2Update;  **//THIS IS CAUSING THE ERROR**      
        }

        public void finish(Database.BatchableContext bc) {
            system.debug('JOB IS DONE!!');
        }
    }
  • I'm assuming so, but just to be clear, does the user running the batch have Marketing User set? – Adrian Larson Aug 28 '16 at 12:18
  • 2
    Ok, so lots could be going on here. @ChrisDuncombe Did you adjust your batch query to only return the ONE record that is causing the error (since you know the ID). Set the logs to finest and sprinkle liberal debugs to see what is going on? Since you know the ID somethings a thorough log review with the batch acting on that one record will show the problem. OR it could actually work in which case you would at least know the error is misrepresented as being with that record. Could other code be causing the issue? – Eric Aug 29 '16 at 1:01
  • 2
    @Eric, so I did exactly what you suggested. And the update worked! So this lead me to start playing with the number of rows in the query, and it seems that it has to do with the number of rows returned in the query in the start method. I took it a step further and just tried in Apex anon to query for 1000 campaign members, update the status of each, and it failed wth the same error message. It seems that it has to do with mass updating CampaignMember records. There are no triggers, or other code that I can see involved at all. Its a pretty new and blank org. – Chris Duncombe Aug 29 '16 at 1:53
  • 2
    I'm both sad and glad to report that I have been on the phone with Tier 1 and Tier 2 SF support today. Neither could make heads or tails of it. T3 tomorrow..... stay tuned – Chris Duncombe Aug 29 '16 at 2:40
  • 1
    I tried just a batch size of 1, tried 50, 100, 150, 200 etc. I feel like I am going nuts....lol. Thanks for all the help though, it is much appreciated – Chris Duncombe Aug 29 '16 at 3:32
2

It appears to me that the code you're operating on isn't linking back to what you're primarily working with: CampaignMember. Instead, you're operating on Lead retrieved from CampaignMember without confirming that what's returned from your query is in sync with CampaignMember; instead expecting the results to apply to CampaignMember and the CampaignMember Ids that your QueryPointer is iterating on.

I recommend you refactor you code to something along the lines where the results of your query on Leads is synced with CampaignMember before you execute the statement update members2Update; It also may be as simple as moving the statement update leads to execute before the update on CampaignMembers statement.

In your code below, you're updating all CampaignMembers whether they're Leads or not. Do you have any that might be returned that are now Contacts that perhaps have an old LeadId associated with them? Those records wouldn't be returned in your query on Lead.

    for(CampaignMember member : scope){
        member.Status = 'Not Contacted'; 
        // 'Not Contactd' Status assigned regardless of whether lead or contact
        if(member.LeadId != null){
            leadIDs.add(member.Lead.Id);
            // could member now have a `contact.Id` if has been converted and lead.Id 
            // not removed from campaignmember record or some other system failure?
        }   
        members2Update.add(member);
        // ALL members are being updated whether lead or contact            
    }

It would seem important to test to see if leads.size() = leadIds.size(). If not, there would seem to be a higher likelihood for a potential with an issue to occur during the update on CampaignMember.

If they're not the same, then you'd seemingly want to add logic to remove the CampaignMember with the LeadId that's not in leads from members2Update.

  • I guess I don't understand how any of that would cause an error. What in this explanation could cause an error. I get most of what you are saying, but none of it seems to add up to causing this error, unless I am misunderstanding. I appreciate the time of looking it over for me, I just don't see how any of this would result in the error. P.S. I tried to update leads first, as you suggested and that doesnt change anything, still get the error – Chris Duncombe Aug 28 '16 at 21:08
  • I would look at the leadId that gave you the error but which you could update manually and see if any of what I've mentioned above applies or what might be different about that CampaignMember record compared to another in the same batch that was successful. The issue only seems to be related to leads from what you're telling us, but you're not matching them back up with CampaignMember, so something has to be out of sync. BTW, the without sharing would only apply to queries, not to crud FLS. – crmprogdev Aug 28 '16 at 21:23
  • 1
    I don't think its related to leads at all. I just commented out the lead portions, so no updates on leads at all. Its just a simple batch that cycles through and updates campaign member statuses. Still getting the error. I think this has to do with mass updating campaignmembers. I just cannot think of why though – Chris Duncombe Aug 28 '16 at 21:29
  • 2
    Updated the question to reflect the above. – Chris Duncombe Aug 28 '16 at 21:33

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.