1

I'm in a Community context, I got users that are kindly invited to upload files using our community Portal.

If they did not validate their folder in (n) days, I have to deactivate their accounts, and send for analysis the files they (potentially) uploaded.

I got a batch doing it, but the thing is I'm reaching limits ->

Error 'Too many queueable jobs added to the queue: 2'

Each Usercan hold a maximum of 20 Justificatif__c objects to send, and enqueuing jobs seems not to be the right way to do it.

How can I handle it ?

For each Justificatif, I have to 1- create a folder on the client depending on the account's informations 2- send the Justificatif__c for analysis 3- get Analysis from the client, and fill Salesforce with analysis

It seems too much for SF, the way I'm taking it ... Any help would be appreciate, Thanks !!

In the execute(), I got that part :

for(User u : scope) {
        // enqueue analysis for loaded documents
        System.debug('\n\nBatch_DeactivateUser -- Execute () -- \nEnqueue Analysis on user '+ u.Name+'\nDescription user : '+u);
        validerDossierAutomatique(u.AccountId);
        u.IsActive =false;
        u.canAccessPortal__c=false;

    }

The ValidateDossierAutomatique() method :

public static void validerDossierAutomatique(Id accountId){


    // RECUPERATION DE TOUS LES JUSTIFS A COLLECTER (traités, OK, KO, bref tous)
    List<Justificatif__c> allJustifList = [SELECT Id, DocumentJouve__c, Type_Source__c, DocumentJouve_verso__c, NbValidation__c, Client__c, A_Afficher_Portail__c, Statut_SGEBS_Remediation__c, Piece_Recto__c, Piece_verso__c, techTriggerEmail__c, tech_NbRelance__c,Nb_Envoi_Email__c // Nom_Doc_Portail__c,Type_Document__c,Info_Bulle_Longue__c,Info_Bulle_Portail__c, Statut_SGEBS_Remediation__c , Nombre_de_document__c
                                            FROM Justificatif__c
                                            WHERE Client__c = :accountId
                                                AND A_Afficher_Portail__c  = TRUE
                                                AND Statut_SGEBS_Remediation__c  = :Portal_Constantes.Statut_SGEBS_Remediation_A_Collecter
                                                AND Statut_Jouve__c!= :KYC_Constantes.OKradlad];

    // get the list of justif to be treated, for the PP (accountId), allJustifList.isEmpty() toujours faux on ne se préoccupe pas de cette condition
    List<Justificatif__c> justifToTreatList = new List<Justificatif__c>();
    for(Justificatif__c just : allJustifList) {


        // ajout selon les conditions qui font que le justif doit être traité
        if(just.A_Afficher_Portail__c && just.Statut_SGEBS_Remediation__c == Portal_Constantes.Statut_SGEBS_Remediation_A_Collecter
                && (just.DocumentJouve__c!=null || just.DocumentJouve_verso__c!=null)) {




            justifToTreatList.add(just);
            // System.debug('\n\njustificatif à traiter : '+just+'\nNbVal du justif: '+ just.NbValidation__c+'\nDocument_Jouve du justif: '+ just.DocumentJouve__c);
        }
    }


    if(!justifToTreatList.isEmpty()) {
        for(Justificatif__c just : justifToTreatList) {

            /* RECTO CASE : treat if the document is Justificatif is in Jouve and is Recto (nb : DocumentJouve__c is the path of the file in Jouve) */
            if(just.DocumentJouve__c!=null) {
                System.enqueueJob(new KYC_DocumentAnalysis(just.DocumentJouve__c, just.Id,false));
            }

            /* VERSO CASE : treat if the document is Justificatif is in Jouve and is Verso */
            if(just.Piece_verso__c && just.DocumentJouve_verso__c!= null) {
                System.enqueueJob(new KYC_DocumentAnalysis(just.DocumentJouve_verso__c, just.Id,true));
            }


        }
    }
}

And that's the System.enqueueJob(new KYC_DocumentAnalysis(just.DocumentJouve_verso__c, just.Id,true)); part that is an issue..

EDIT 20/03 : So I took advantages of sfdc's answer,

My code now is :

The execute() method of my batch loop over users in scope, and call this treatment

QueueableChain chain = new QueueableChain();
    if(!justifToTreatList.isEmpty()) {
        for(Justificatif__c just : justifToTreatList) {

            /* RECTO CASE : treat if the document is Justificatif is in Jouve and is Recto (nb : DocumentJouve__c is the path of the file in Jouve) */
            if(just.DocumentJouve__c!=null) {
                //System.enqueueJob(new KYC_DocumentAnalysis(just.DocumentJouve__c, just.Id,false));
                chain.add(new KYC_DocumentAnalysis(just.DocumentJouve__c, just.Id,false));
            }

            /* VERSO CASE : treat if the document is Justificatif is in Jouve and is Verso */
            if(just.Piece_verso__c && just.DocumentJouve_verso__c!= null) {
                //System.enqueueJob(new KYC_DocumentAnalysis(just.DocumentJouve_verso__c, just.Id,true));
                chain.add(new KYC_DocumentAnalysis(just.DocumentJouve_verso__c, just.Id,true));
            }
        }
        System.debug(' Finale chain to enqueue : ' + chain);
        System.enqueueJob(chain);
        //chain.enqueueJob();
    }

The class you offered was implemented in my code as below, I had to make a minor change (hope it's minor tho ?) in the add() method, because it wasn't compiling, it is :

public class QueueableChain implements Queueable {
Queueable[] chain = new Queueable[0];

public void add(Queueable chainLink) {
    System.debug('add a job to the queue \n\n    chain vaut : ' + chain);
    chain.add(chainLink);
    // return new QueueableChain(); // TODO pas bon
}

public Id enqueueJob() {
    if(chain.isEmpty()) {
        return null;
    }
    return System.enqueueJob(this);
}

public void execute(QueueableContext context) {
    chain.remove(0).execute(context);
    enqueueJob();
}

}

The chaining seems to work, because the debug shows that the chain is constructing by iterations (so the add() method works), but still... ->

enter image description here

1 Answer 1

3

You'll probably just want to create a QueueableChain:

public class QueueableChain implements Queueable {
    Queueable[] chain = new Queueable[0];
    public QueueableChain add(Queueable chainLink) {
        chain.add(chainLink);
    }
    public Id enqueueJob() {
        if(chain.isEmpty()) {
            return null;
        }
        return System.enqueueJob(this);
    }
    public void execute(QueueableContext context) {
        chain.remove(0).execute(context);
        enqueueJob();
    }
}

To use this:

QueueableChain chain = new QueueableChain();
for(Justificatif__c just : justifToTreatList) {
    /* RECTO CASE : treat if the document is Justificatif is in Jouve and is Recto (nb : DocumentJouve__c is the path of the file in Jouve) */
    if(just.DocumentJouve__c!=null) {
        chain.add(new KYC_DocumentAnalysis(just.DocumentJouve__c, just.Id,false));
    }

    /* VERSO CASE : treat if the document is Justificatif is in Jouve and is Verso */
    if(just.Piece_verso__c && just.DocumentJouve_verso__c!= null) {
        chain.add(new KYC_DocumentAnalysis(just.DocumentJouve_verso__c, just.Id,true));
    }
}
chain.enqueueJob();

P.S. You don't need to check if a list is empty before you iterate over it. It's perfectly acceptable to iterate over an empty list without an exception.

You're only allowed one System.enqueueJob per asynchronous transaction, so this just gathers them all up and chains them together. Other designs might are also possible if this is a common problem you're getting during recursive triggers, etc.

6
  • Ok so basically, it helped but still got the error.. Can't understand why and I'm desperate about this. I'm editing my question for update, Thanks again for your precious advises ;) Mar 20, 2020 at 17:01
  • @AlexisMASSON are you only getting this error while running a test class? Because in test class you're not allowed to chain queuable jobs: "You can’t chain queueable jobs in an Apex test. Doing so results in an error. To avoid getting an error, you can check if Apex is running in test context by calling Test.isRunningTest() before chaining jobs."
    – Kasper
    Mar 20, 2020 at 17:54
  • Hi, no it's not in a test class. And the thing is, I created a component that just has one button, the clic launches the logic that fail in batch context ... And it works with the clic !! Can't understand why, ideas ?? Mar 20, 2020 at 18:18
  • I read this, also. Exactly my issue, and it goes in sfdxfox's direction, but I wasn't able to produce a code that works in batch context -> medium.com/@CoachRWZ/… Mar 20, 2020 at 18:20
  • @AlexisMASSON Yeah, that article is (currently) wrong, although there was a bug of that nature some time ago (this is from 2018, I believe it was fixed in 2019).
    – sfdcfox
    Mar 20, 2020 at 18:29

You must log in to answer this question.

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