3

I am trying to write a VF page-controller for an approval process which should submit over 1500 records at the same time. The approval is 2 stage.. after initial submission, it should be approved by approver1 and after that records should go to approver2 for approval. Post that, process should end. Now the challenge is SF limits the number of emails in an execution to 1000 only. This is preventing me from using standard approval process (Setup-->Build-->Create-->Approval Process).

[There might be a work around but I don't know it. I have checked with Salesforce Support/Architecture and they confirmed that email limitation is preventing standard approval process to approve records , as the process is trying to send over 1000 emails at the same time. Plus I cannot turn off sending emails upon approval process launch].

So I have to go custom. The point where I am getting stuck is submitting all selected records for approval. I am using a component titled "CheckAllOrNone" for selecting all records.

As per below code, I have Year and Quarter filters to select a subset of records. When checkbox on the header is checked, all records are selected on the 1st page, but none on the second page [pagination]. I would like some assist on how to be able to select records on all pages and then submit them for approval, where I can lock the records using "FOR UPDATE" from edit. Plus any guidance on custom approval process would be helpful.

[NOTE : I have purposely removed some code from both page (to reduce size as it wasn't relevant to this discussion) and class (approval process, as it wasn't correct)]

VF Page ..

<apex:page controller="FormerAuthorController"  sidebar="false" applyHtmlTag="true">

<apex:form >
    <apex:pageMessages id="errors" />

    <apex:pageBlock title="Former Author Payment!" mode="edit">

        <table width="100%" border="0">
            <tr>  
                <td width="200" valign="top">


                    <apex:pageBlock title="Parameters" mode="edit" id="criteria">

                        <script type="text/javascript">
                        function doSearch() {
                            searchServer(
                                document.getElementById("year").value,
                                document.getElementById("fiscal").options[document.getElementById("fiscal").selectedIndex].value
                            );
                        }
                        </script> 

                        <apex:actionFunction name="searchServer" action="{!runSearch}" rerender="results,debug,errors">
                            <apex:param name="year" value="" />
                            <apex:param name="fiscal" value="" />
                        </apex:actionFunction>

                        <table cellpadding="2" cellspacing="2">
                            <tr>
                                <td style="font-weight:bold;">Fiscal-Quarter<br/>
                                    <select id="fiscal" onchange="doSearch();">
                                        <option value=""></option>
                                        <apex:repeat value="{!fiscal}" var="quarter">
                                            <option value="{!quarter}">{!quarter}</option>
                                        </apex:repeat>
                                    </select>
                                </td>
                            </tr>
                            <tr>
                                <td style="font-weight:bold;">Year<br/>
                                    <input type="number" id="year" onkeyup="doSearch();"/>
                                </td>
                            </tr>
                            <!-- tr>
                            <td style="font-weight:bold;">Last Name<br/>
                            <input type="text" id="lastName" onkeyup="doSearch();"/>
                            </td>
                            </tr>
                            <tr>
                            <td style="font-weight:bold;">Account<br/>
                            <input type="text" id="accountName" onkeyup="doSearch();"/>
                            </td>
                            </tr -->

                        </table>

                    </apex:pageBlock>

                </td>
                <td valign="top">

                    <apex:pageBlock mode="edit" id="results">


                        <apex:pageBlockButtons >
                            <apex:commandButton value="Submit" action="{!submitForApproval}"/>
                            <apex:commandButton value="Cancel" action="{!Cancel}"/>
                            <apex:commandButton value="Approve" action="{!Approve}"/>
                            <apex:commandButton value="Reject" action="{!Reject}" />
                        </apex:pageBlockButtons>
                        <apex:pageBlockButtons >                          
                            <apex:commandButton value="First" action="{!Beginning}"/>
                            <apex:commandButton value="Previous" action="{!Previous}"/>
                            <apex:commandButton value="Next" action="{!Next}"/>
                            <apex:commandButton value="Last" action="{!End}" />

                        </apex:pageBlockButtons>
                        <apex:pageBlockTable value="{!monAct}" var="monRec">

                            <!-- apex:column >
                                <apex:facet name="header">
                                    <apex:inputCheckbox>
                                        <apex:actionSupport event="onClick" action="{!getSelected}" reRender="selection"/>
                                    </apex:inputCheckbox>
                                </apex:facet>
                                <apex:inputCheckbox value="{!monRec.Checkbox__c}" id="checkdone">
                                    <apex:actionSupport event="onClick" action="{!getSelected}" reRender="selection"/>
                                </apex:inputCheckbox>
                            </apex:column -->

                            <apex:column >
                                <apex:facet name="header">
                                    <c:CheckAllorNone />
                                </apex:facet>
                                <apex:inputCheckbox value="{!monRec.Checkbox__c}"/>
                            </apex:column>
                            <apex:column >
                                <apex:facet name="header">
                                    <apex:commandLink value="GID" action="{!toggleSort}" rerender="results,debug">
                                        <apex:param name="sortField" value="GID__c" assignTo="{!sortField}"/>
                                    </apex:commandLink>
                                </apex:facet>
                                <apex:outputField value="{!monRec.GID__c}"/>
                            </apex:column>
                            <apex:column >
                                <apex:facet name="header">
                                    <apex:commandLink value="Month" action="{!toggleSort}" rerender="results,debug">
                                        <apex:param name="sortField" value="Month_Text__c" assignTo="{!sortField}"/>
                                    </apex:commandLink>
                                </apex:facet>
                                <apex:outputField value="{!monRec.Month_Text__c}"/>
                            </apex:column>
                            <apex:column >
                                <apex:facet name="header">
                                    <apex:commandLink value="Quarter" action="{!toggleSort}" rerender="results,debug">
                                        <apex:param name="sortField" value="Quarter__c" assignTo="{!sortField}"/>
                                    </apex:commandLink>
                                </apex:facet>
                                <apex:outputField value="{!monRec.Quarter__c}"/>
                            </apex:column>
                            <apex:column >
                                <apex:facet name="header">
                                    <apex:commandLink value="Year" action="{!toggleSort}" rerender="results,debug">
                                        <apex:param name="sortField" value="year" assignTo="{!sortField}"/>
                                    </apex:commandLink>
                                </apex:facet>
                                <apex:outputField value="{!monRec.Year__c}"/>
                            </apex:column>                            


                        </apex:pageBlockTable>

                    </apex:pageBlock>

                </td>
            </tr>
        </table>

        <apex:pageBlock title="Debug - SOQL" id="debug">
            <apex:outputText value="{!debugSoql}" />           
        </apex:pageBlock>    

    </apex:pageBlock>

</apex:form>

Class:

    public with sharing class FormerAuthorController {
public List<Monthly_Activity__c> monAct {get; set;}
private String soql{get; set;}
public Boolean checkAll{get; set;}
private String fiscalPeriod;
private String yearValue;
private static final String EDITORIAL_GROUP_NAME = 'Editorial';
private static final String FINANCIAL_GROUP_NAME = 'Finance';
private integer counter=0;          //keeps track of the offset
private integer list_size=25;       //sets the page size or number of rows
public integer total_size;          //used to show user the total size of the list

//List<Monthly_Activity__c> selectedRecords = new List<Monthly_Activity__c>();

private static Id financeGroupId
{
    get
    {
        if (financeGroupId == null)
        {
            financeGroupId = [SELECT Id FROM Group WHERE Name = :FINANCIAL_GROUP_NAME][0].Id;
        }
        return financeGroupId;
    }
    private set;
}

private static Id editorialGroupId
{
    get
    {
        if (editorialGroupId == null)
        {
            editorialGroupId = [SELECT Id FROM Group WHERE Name = :EDITORIAL_GROUP_NAME][0].Id;
        }
        return editorialgroupId;
    }
    private set;
}

private static Id submitterProfileId
{
    get
    {
        if (submitterProfileId == null)
        {
            submitterProfileId = [SELECT Id FROM Profile WHERE Name = 'Submitters'][0].Id;
        }
        return submitterProfileId;
    }
    private set;
}

private static Id financeProfileId
{
    get
    {
        if (financeProfileId == null)
        {
            financeProfileId = [SELECT Id FROM Profile WHERE Name = 'Finance'][0].Id;
        }
        return financeProfileId;
    }
    private set;
}

private static Id editorialProfileId
{
    get
    {
        if (editorialProfileId == null)
        {
            editorialProfileId = [SELECT Id FROM Profile WHERE Name = 'Editorial'][0].Id;
        }
        return editorialProfileId;
    }
    private set;
}


private static Id adminProfileId
{
    get
    {
        if (adminProfileId == null)
        {
            adminProfileId = [SELECT Id FROM Profile WHERE Name = 'System Administrator'][0].Id;
        }
        return adminProfileId;
    }
    private set;
}    

// the current sort direction. defaults to asc
public String sortDir {
    get{ 
        if(sortDir == null){  
            sortDir = 'asc'; 
        } 
        return sortDir;  
    }
    set;
}

// the current field to sort by. defaults to last name
public String sortField {
    get{ 
        if (sortField == null) {
            sortField = 'Year__c'; 
        } return sortField;  
    }
    set;
}

// format the soql for display on the visualforce page
public String debugSoql {
    get{ 
        return soql + ' order by ' + sortField + ' ' + sortDir + ' limit '+list_size+' '+ 'offset '+counter;
    }
    set;
}

public FormerAuthorController(){
    total_size = [SELECT count() FROM Monthly_Activity__c WHERE Contract_Status__c =:'Former Author'];

} 

// toggles the sorting of query from asc<-->desc
public void toggleSort() {
    // simply toggle the direction
    sortDir = sortDir.equals('asc') ? 'desc' : 'asc';
    // run the query again
    runQuery();
}

// runs the actual query
public void runQuery() {

    try {
        //monAct = Database.query(soql + ' order by ' + sortField + ' ' + sortDir + ' '+' Limit '+list_size+' '+' OFFSET '+counter);
        monAct = Database.query(debugSoql);
        system.debug('RUN QUERY ' + monAct);
    } catch (Exception e) {
        ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'Something Went Wrong. Contact System Adminstrator!'));
        system.debug('Error Message ***'+ e.getMessage());
    }

}

// runs the search with parameters passed via Javascript
public PageReference runSearch() {
    system.debug('RUN SEARCH');
    String fiscal = Apexpages.currentPage().getParameters().get('fiscal');
    fiscalPeriod = fiscal;
    String year = Apexpages.currentPage().getParameters().get('year');
    yearValue = year;
    system.debug('FISCAL '+ fiscal);
    system.debug('YEAR '+ year);
    soql = 'SELECT Id, Name, Month__c, Checkbox__c FROM Monthly_Activity__c WHERE Contract_Status__c = \'Former Author\'';
    if (!year.equals(''))
        soql += ' and Year__c ='+ year;             
    if (!fiscal.equals(''))
        soql += ' and Quarter__c LIKE \'' +String.escapeSingleQuotes(fiscal)+'\''; 


    // run the query again
    runQuery();
    system.debug('RUN SEARCH QUERY');
    return null;
} 

// use apex describe to build the picklist values
public List<String> fiscal {
    get {
        if (fiscal == null) {

            fiscal = new List<String>();
            Schema.DescribeFieldResult field = Monthly_Activity__c.Fiscal_Quarter__c.getDescribe();

            for (Schema.PicklistEntry f : field.getPicklistValues())
                fiscal.add(f.getLabel());

        }
        return fiscal;          
    }
    set;
}


 //Approval Code

 public void beginApprovalProcess(Monthly_Activity__c monAct){
    Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest();
    req.setComments('Submitting Records for Approval via VisualForce Page');
    req.setObjectId(monAct.id);

   }

 //  Button Code  


public void submitForApproval(){
    if(fiscalPeriod <> Null && yearValue <> Null) {
        if(UserInfo.getProfileId()== submitterProfileId || UserInfo.getProfileId()== adminProfileId){
                //beginApprovalProcess();    

        }   
    }
}


public PageReference Cancel(){
    PageReference page = new PageReference('/FormerAuthorPage');
    page.setRedirect(true);
    return page;
}

public PageReference Approve(){
    if(fiscalPeriod <> Null && yearValue <> Null) {
        if(UserInfo.getProfileId()== financeProfileId || UserInfo.getProfileId()== editorialProfileId){


        }   
    }
    PageReference page = new PageReference('/FormerAuthorPage');
    page.setRedirect(true);
    return page;
}

public PageReference Reject(){
    if(fiscalPeriod <> Null && yearValue <> Null) {
        if(UserInfo.getProfileId()== financeProfileId || UserInfo.getProfileId()== editorialProfileId){


        }   
    }
    PageReference page = new PageReference('/FormerAuthorPage');
    page.setRedirect(true);
    return page;
}

//  Pagination Code

public PageReference Beginning() { //user clicked first
    counter = 0;
    runQuery();
    return null;
}

public PageReference Previous() { //user clicked previous 
    counter -= list_size;
    runQuery();
    return null;
}

public PageReference Next() { //user clicked next 
    counter += list_size;
    runQuery();        
    return null;
}

public PageReference End() { //user clicked last
    counter = total_size - math.mod(total_size, list_size);
    runQuery();
    return null;
}

public Boolean getDisablePrevious() { 
    //this will disable the previous and beginning buttons
    if (counter>0) return false; 
    else return true;

}

public Boolean getDisableNext() { //this will disable the next and end buttons
    if (counter + list_size < total_size) 
        return false; else return true;

}

public Integer getTotal_size() {
    return total_size;
}

public Integer getPageNumber() {
    return counter/list_size + 1;
}

public Integer getTotalPages() {
    if (math.mod(total_size, list_size) > 0) {
        return total_size/list_size + 1;
    } else {
        return (total_size/list_size);
    }
}  
}

Component :

<apex:component >
<script>
function cvCheckAllOrNone(allOrNoneCheckbox) {

    // Find parent table
    var container = allOrNoneCheckbox;
    while (container.tagName != "TABLE") {
        container = container.parentNode;
    }

    // Switch all checkboxes
    var inputs = container.getElementsByTagName("input");
    var checked = allOrNoneCheckbox.checked;
    for (var i = 0; i < inputs.length; i++) { 
        var input = inputs.item(i);
        if (input.type == "checkbox") {
            if (input != allOrNoneCheckbox) {
                input.checked = checked;
            }
        }
    }
}
</script>

<apex:inputCheckbox onclick="cvCheckAllOrNone(this)" title="Toggle All Rows"/>

Any help in this matter is greatly appreciated. Thanks!

1

Thank you for sharing a nice scenario.

Rather than going into custom development route, you can achieve through Standard Approval process.

First, in the approval process, remove approval assignment email template.

In the user record, choose Never for Receive Approval Request Emails. So no emails will fire from Approval processes.

settings

Now, if you want to send emails to the approvers you should leverage workflow email alert and this will be within your daily limit.

Daily limit of Email alert

The daily allocation for emails sent through email alerts is 1,000 per standard Salesforce license per org—except for free Developer Edition and trial orgs, where the daily workflow email allocation is 15. The overall org allocation is 2,000,000. This allocation applies to emails sent through email alerts in workflow rules, approval processes, flows, processes, or the REST API

Refer Workflow limit

Update based on comments

You need to submit records where max 999 records in a batch so that it will not hit the limit.

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.