2

Currently we have Sales Invoice custom object and we want to create "Email" list button that will send email to the "invoice_email" field (lookup to contact). I know how to send 1 invoice pdf per email. But the requirement is to send only one email with multiple invoice pdf as our customers get annoyed getting multiple emails with single file attached per each email.

Any assitance with sample code will be highly appreciated

2 Answers 2

1

I recommend you look at this post by @Eyescream which refers to blog posts by Jeff Douglass and a Helper Class that creates a map for the pages to be rendered as a single attachment. It's an answer to his own question describing the solution he came up with for doing the type of thing you describe.

0

I developed this a couple of years ago. It needs a bit of work however, I've just tried it out and it should give you what you want.

Another benefit to this is that it makes it easy to download .pdf attachments one after the other from Salesforce, because you can declaratively input a SOQL in the Visualforce page.

Step 1

Create a Custom Setting of type Hierarchy. Call this 'App Configurations'. Then create two fields, as per the screenshot.

enter image description here

Step 2

Create this Apex class.

public with sharing class AttachmentUtilityController {
    //Variables
    public String queryString{get; set;}
    public String selectedObject{get; set;}
    public String selectedField{get; set;}
    public String selectedOperator{get; set;}
    public String selectedValue{get; set;}
    public list<SelectOption> objects{get;set;}
    public list<SelectOption> returnFields{get;set;}
    public list<SelectOption> returnOperators{get;set;}
    public Integer maxSize{get; set;}

    /**
    Constructor
    */
    public AttachmentUtilityController(){
        objects = new list<SelectOption>();
        returnOperators = new list<SelectOption>();
        returnOperators.add(new SelectOption('equals', 'equals'));
        returnOperators.add(new SelectOption('not equals', 'not equals'));
        returnOperators.add(new SelectOption('less than', 'less than'));
        returnOperators.add(new SelectOption('greater than', 'greater than'));
        returnOperators.add(new SelectOption('less than or equals', 'less than or equals'));
        returnOperators.add(new SelectOption('greater than or equals', 'greater than or equals'));
        returnOperators.add(new SelectOption('contains', 'contains'));
        returnOperators.add(new SelectOption('does not contains', 'does not contains'));
        returnOperators.add(new SelectOption('starts with', 'starts with'));
        returnOperators.add(new SelectOption('does starts with', 'does starts with'));

        selectedObject = 'Account';
        queryString = 'Select Id From '+selectedObject;
        returnFields = getFields('Account');
        //Get All Objects Related to attachment
        Schema.DescribeFieldResult F = Attachment.ParentId.getDescribe();
        List<Schema.sObjectType> P = F.getReferenceTo();
        for(Schema.sObjectType sot : P){
            objects.add(new SelectOption(String.Valueof(sot), String.Valueof(sot)));
        }

        App_Configurations__c CS = App_Configurations__c.getOrgDefaults();
        maxSize = (CS.Max_File_Size__c != null ? Integer.ValueOf(CS.Max_File_Size__c) : 5120);

        listAttachment = new List<Attachment>();
        listAttachmentWrapper = new List<AttachmentWrapper>();
    }

    public static List<SelectOption> sortOptionList(List<SelectOption> ListToSort) {
        if(ListToSort == null || ListToSort.size() <= 1)
            return ListToSort;

        List<SelectOption> Less = new List<SelectOption>();
        List<SelectOption> Greater = new List<SelectOption>();
        integer pivot = ListToSort.size() / 2;

        // save the pivot and remove it from the list
        SelectOption pivotValue = ListToSort[pivot];
        ListToSort.remove(pivot);

        for(SelectOption x : ListToSort)
        {
            if(x.getLabel() <= pivotValue.getLabel())
                Less.add(x);
            else if(x.getLabel() > pivotValue.getLabel()) Greater.add(x);
        }
        List<SelectOption> returnList = new List<SelectOption> ();
        returnList.addAll(SortOptionList(Less));
        returnList.add(pivotValue);
        returnList.addAll(SortOptionList(Greater));
        return returnList;
    }
    //Change Query Values
    public void resetQuery(){
        queryString = 'Select Id From '+selectedObject;
        returnFields = getFields(selectedObject);
    }

    //Get fields per object
    //map<string, string> mapFieldNameType;
    private list<SelectOption> getFields(String obj){

          List<SelectOption> options = new List<SelectOption>();
          Map<String, Schema.SObjectType > gd = Schema.getGlobalDescribe();
          Schema.SObjectType objType = gd.get(obj);

          Schema.DescribeSObjectResult objDescribe = objType.getDescribe();
          map<String, Schema.SObjectField> fieldMap = objDescribe.fields.getMap();

          for(String fieldName : fieldMap.keyset()){
              Schema.SObjectField field = fieldMap.get(fieldName);
              Schema.DescribeFieldResult fieldDesc = field.getDescribe();
              options.add(new SelectOption(fieldDesc.getName(), fieldDesc.getLabel() ));
          }
          options.sort();
          options = sortOptionList(options);
          return options;
    }


    //Add Condition
    public void addCondition(){
        if(queryString.contains('Where'))
          queryString += ' AND ';
        else
          queryString += ' Where ';
      queryString += retunQuery(selectedField, selectedOperator, selectedValue);
    }

    //Return Final Query
    public string retunQuery(string field, string operator, string value){
        string conditionString = '';
        if(value != null){
          if(operator == 'equals'){
              conditionString = field + ' = ' + '\'' + value + '\'';
          } else if( operator == 'not equals'){
            conditionString = field + ' != ' + '\'' + value + '\'';
          }else if( operator == 'less than'){
            conditionString = field + ' < ' + value;
          }else if( operator == 'greater than'){
            conditionString = field + ' > ' + value;
          }else if( operator == 'less than or equals'){
            conditionString = field + ' <: ' + value;
          }else if( operator == 'greater than or equals'){
            conditionString = field + ' >: ' + value;
          }
          else if( operator == 'contains'){
            conditionString = field + ' Like \'%' + value + '%\'';
          }
          else if( operator == 'does not contains'){
            conditionString = '( Not ' + field + ' Like \'%' + value + '%\' )';
          }
          else if( operator == 'starts with'){
            conditionString = field + ' Like \'' + value + '%\'';
          }
          else if( operator == 'does starts with'){
            conditionString = '( Not ' + field + ' Like \'' + value + +  '%\' )';
          }
        }
        return conditionString;
    }

    //Method to fetch all attachment
    public List<AttachmentWrapper> listAttachmentWrapper{get;set;}
    public List<Attachment> listAttachment{get;set;}
    public PageReference fetchAttachments(){
        try {
            listAttachmentWrapper = new List<AttachmentWrapper>();
            set<string> setParentIds = new set<string>();
            try {
                for(SObject sObj : Database.query(queryString)){
                    setParentIds.add(sObj.Id);
                }
            }
            catch(Exception ex) {
                ApexPages.addMessage(new ApexPages.message(ApexPages.severity.ERROR,'Invalid Query : ' +  ex.getmessage()));
            }
            listAttachment = [Select Name, Id, ContentType, ParentId, BodyLength From Attachment Where ParentId IN : setParentIds];
            for(Attachment attach : listAttachment){
                listAttachmentWrapper.add(new AttachmentWrapper(false, attach));
            }
        }
        catch(Exception ex) {
            ApexPages.addMessages(ex);
        }
        return ApexPages.currentPage();
    }

    //Method to send Page
  public List<AttachmentWrapper> selectedAttachmentWrapper{get;set;}
    public PageReference next(){
      string attachIds = '';
      for (AttachmentWrapper a : listAttachmentWrapper){
            if(a.isSelected){
                if(attachIds == '')
                  attachIds = a.attach.Id;
                else
                  attachIds += ','+ a.attach.Id;
            }
        }
        Pagereference pg = new Pagereference('/apex/SendEmail?attachIds='+attachIds);
        pg.setRedirect(true);
      return pg;
    }

    //Wrapper Class
    public class AttachmentWrapper{
        public boolean isSelected{get; set;}
        public Attachment attach{get; set;}
        public Decimal bodyLengthInMB{get; set;}
        public AttachmentWrapper(boolean isSelected, Attachment attach){
            this.isSelected = isSelected;
            this.attach = attach;
            this.bodyLengthInMB = ( attach.BodyLength ) / ( 1024 * 1024 * 1.00 );
            //this.bodylengthInMB = bodylengthInMB.setScale( 2, RoundingMode.CEILING);
        }
    }
}

Step 3

Create this Test class.

@isTest(seeAllData = false)
public class AttachmentUtilityControllerTest {
    //Unit Test Method
    static testMethod void unitTest01() {
        String folderId = [Select Id From Folder limit 1].id;

        //Create Custom setting
        App_Configurations__c CS = new App_Configurations__c();
        CS.Folder_Id__c = folderId;
        insert CS;

        //Create Account
        Account account = new Account(Name = 'Test');
        insert account;

        //Create Attachment
        Attachment a = new Attachment();
        a.name= 'Test Attachment Name';
        a.body= blob.valueOf('Test Body');
        a.parentID= account.id;
        insert a;

        //Start Test method
        Test.startTest();
            AttachmentUtilityController adc = new AttachmentUtilityController();
            adc.resetQuery();
            adc.fetchAttachments();
            adc.saveToDocument();
        Test.stopTest();
        //End of test method
    }
}

Step 4

Create this Visualforce page. Call the page 'AttachmentUtility'.

<apex:page controller="AttachmentUtilityController" id="pgId" sidebar="false">
    <apex:pageMessages id="pgMsg"/>
    <apex:sectionHeader subtitle="Attachment Downloader" title="Download Attachments"/>
    <script>
        function callNext(){
            if( totalFileSize <= 0 ){
                alert('Please select atleast one attachment');
                return false;
            }
            else if( totalFileSize > totalFileSizeAllowed ){
                alert('Selected item size exceeds the limit : ' + '{!maxSize}' + ' bytes.' );
                return false;
            }
            else {
                jsNext();
                return true;
            }
        }

        var totalFileSize = 0;
        // take this from custom setting
        var totalFileSizeAllowed = {!maxSize};
        function calculateFileSize( chkCtrl, fileSize ) {
            try {
                var selectAllid = document.getElementById("pgId:fromId:pbId:opResultantPBSId:pbtId:slctAll");
                if(document.getElementById("pgId:fromId:pbId:opResultantPBSId:pbtId:slctAll").checked) {
                    document.getElementById("pgId:fromId:pbId:opResultantPBSId:pbtId:slctAll").checked = chkCtrl.checked;
                } else {
                    var isAllselect = true;
                    for(var i=0; i<{!listAttachmentWrapper.size}; i++) {
                        var id = "pgId:fromId:pbId:opResultantPBSId:pbtId:"+i+":slctSingle";
                        if(!document.getElementById(id).checked ) {
                            isAllselect = false ;
                            break ;
                        }
                    }
                    if(isAllselect) {
                        document.getElementById("pgId:fromId:pbId:opResultantPBSId:pbtId:slctAll").checked = true;
                    }
                }

                if( chkCtrl.checked ) {
                    totalFileSize = totalFileSize * 1 + fileSize * 1;
                }
                else {
                    totalFileSize = totalFileSize * 1 - fileSize * 1;
                }
                if( totalFileSize > totalFileSizeAllowed ) {
                    // make this "opLimit" output panel visisble
                    document.getElementById("pgId:fromId:pbId:opResultantPBSId:olLimit").style.visibility = 'visible';
                }
                else {
                    // make this "opLimit" output panel hidden
                    document.getElementById("pgId:fromId:pbId:opResultantPBSId:olLimit").style.visibility = 'hidden';
                }
                document.getElementById('pgId:fromId:pbId:opResultantPBSId:opFileSizeArea').innerHTML = 'Combined file size of selected attachements: <B>'+totalFileSize+'</B>';
            }
            catch(e) {
                alert(e);
            }
            return false;
        }

        function checkAll(cb){
            totalFileSize = 0;
            for(var i=0; i<{!listAttachmentWrapper.size}; i++) {
                document.getElementById("pgId:fromId:pbId:opResultantPBSId:pbtId:"+i+":slctSingle").checked = cb.checked;
                if(cb.checked){
                    var fileSize = document.getElementById("pgId:fromId:pbId:opResultantPBSId:pbtId:"+i+":singleSelectHidden").value
                    totalFileSize = totalFileSize*1 + fileSize*1;
                }
            }
            if( totalFileSize > totalFileSizeAllowed ) {
                // make this "opLimit" output panel visisble
                document.getElementById("pgId:fromId:pbId:opResultantPBSId:olLimit").style.visibility = 'visible';
            }
            else {
                // make this "opLimit" output panel hidden
                document.getElementById("pgId:fromId:pbId:opResultantPBSId:olLimit").style.visibility = 'hidden';
            }
            document.getElementById('pgId:fromId:pbId:opResultantPBSId:opFileSizeArea').innerHTML = 'Combined file size of selected attachments: <B>'+totalFileSize+'</B>';
        }
    </script>

    <!-- Form -->
    <apex:form id="fromId">
        <apex:actionFunction name="jsResetObject" action="{!resetQuery}" reRender="opTextArea, opField"/>
        <apex:actionFunction name="jsNext" action="{!next}" reRender="pgMsg"/>
        <apex:actionFunction name="jsAddCondition" action="{!addCondition}" reRender="pgMsg, opTextArea"/>

        <!-- Block for Search Attachment -->
        <apex:pageBlock id="pbId" title="Search Attachments"  >
            <apex:pageBlockButtons id="pbbId" location="top">
                <apex:commandButton value="Get Attachment" action="{!fetchAttachments}"/>
            </apex:pageBlockButtons>

            <apex:pageBlockSection id="pbsId" columns="1">
                <!-- Section Item for Object Picklist -->
                <apex:pageBlockSectionItem >
                    <apex:outputLabel value="Object"/>
                    <apex:outputPanel >
                        <apex:selectList size="1" label="Object" value="{!selectedObject}" onchange="jsResetObject();return false;">
                            <apex:selectOptions value="{!objects}"/>
                        </apex:selectList>
                    </apex:outputPanel>
                </apex:pageBlockSectionItem>

                <!-- Section Item for Object Picklist -->
                <apex:pageBlockSectionItem >
                    <apex:outputLabel value="Where Clause"/>
                    <apex:outputPanel id="opField" >
                        <apex:selectList size="1" label="Field" value="{!selectedField}">
                            <apex:selectOptions value="{!returnFields}"/>
                        </apex:selectList>
                        <apex:selectList size="1" label="Operator" value="{!selectedOperator}">
                            <apex:selectOptions value="{!returnOperators}"/>
                        </apex:selectList>
                        <apex:inputText id="valueId" value="{!selectedValue}"/>
                        <apex:commandButton value="Add Condition" onclick="jsAddCondition();return false;"/>
                    </apex:outputPanel>
                </apex:pageBlockSectionItem>

                <!-- Section Item for Search Query -->
                <apex:pageBlockSectionItem >
                    <apex:outputLabel value="Search Query"/>
                    <apex:outputPanel id="opTextArea" >
                        <apex:inputTextarea value="{!queryString}" style="margin-left:0px;width:500px;" rows="5"/>
                    </apex:outputPanel>
                </apex:pageBlockSectionItem>
            </apex:pageBlockSection>

            <!-- Block for Resultant Attachment -->
            <apex:outputPanel id="opResultantId">
                <apex:pageBlockSection title="Attachments" columns="1" id="opResultantPBSId">
                    <apex:commandButton value="Email Attachments" style="margin-left: 32%;"  onclick="callNext(); return false;" rendered="{!listAttachmentWrapper.size > 0}" />
                    <apex:pageMessage severity="info" strength="3" summary="No record found" rendered="{!listAttachment.size = 0}"/>
                    <apex:outputLabel rendered="{!listAttachmentWrapper.size > 0}"> Total Number of Records : <B> {!TEXT(listAttachmentWrapper.size)} </B> </apex:outputLabel>
                    <apex:outputLabel rendered="{!listAttachmentWrapper.size > 0}" id="opFileSizeArea"></apex:outputLabel>
                    <apex:outputLabel id="olLimit" style="color: red; font-weight: bold;" > File size Exceeds the limit : {!maxSize} bytes.</apex:outputLabel>
                    <script>
                        document.getElementById("pgId:fromId:pbId:opResultantPBSId:olLimit").style.visibility = 'hidden';
                    </script>
                    <apex:pageBlockTable id="pbtId" value="{!listAttachmentWrapper}" var="la" rendered="{!listAttachmentWrapper.size > 0}">

                        <apex:column >
                            <apex:facet name="header">
                                <apex:inputCheckBox id="slctAll"  onclick="checkAll(this);" />
                            </apex:facet>
                            <apex:inputCheckbox id="slctSingle" value="{!la.isSelected}" onchange="calculateFileSize(this, '{!la.attach.BodyLength}');"/>
                            <apex:inputhidden value="{!la.attach.BodyLength}" id="singleSelectHidden"/>
                        </apex:column>

                        <apex:column headerValue="Name">
                            <apex:outputLink value="/{!la.attach.Id}" target="_blank">{!la.attach.Name}</apex:outputLink>
                        </apex:column>

                        <apex:column headerValue="File Size (Bytes)">
                            <apex:outPutText value="{0, number, ###,###,###,##0}">
                                <apex:param value="{!la.attach.BodyLength}"/>
                            </apex:outPutText>
                        </apex:column>

                        <apex:column headerValue="File Size (MB)">
                            <apex:outPutText value="{0, number, ###,###,###,##0.00000}">
                                <apex:param value="{!la.bodyLengthInMB}"/>
                            </apex:outPutText>
                        </apex:column>

                        <apex:column >
                            <apex:outputLink value="{!URLFOR($Action.Attachment.Download, la.attach.id)}" target="_blank">
                              Download to Desktop
                            </apex:outputLink>
                        </apex:column>
                    </apex:pageBlockTable>
                </apex:pageBlockSection>
            </apex:outputPanel>
        </apex:pageBlock>
    </apex:form>

</apex:page>

Step 5

Go to Setup | Develop | Visualforce Pages and click on the page that you have just created.

You will see that you can query all of the attachments per objects in your Org and you can input a SOQL declaratively to return the set that you are interested in. You can use all of the operators that you would expect from the Salesforce report writer and Workbench.

enter image description here

Step 6

In this example, I've used a SOQL to return the set of Opportunities for whose attachments I wish to email. Simply select the attachments you wish to email on the right hand side, then select 'Email Attachments'.

enter image description here

Step 7

You will then be auto-navigated to this screen here, where you will have to input values into the mandatory fields else a validation will be hit. When you're ready, select 'Send'.

In addition to this, there are a number of 'email attachment' apps on the AppExchange.

Hope this helps.

enter image description here

You must log in to answer this question.

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