Tuesday, December 20, 2022

Creating Attachments in Apex Code


Capturing notes and attachments against records is standard Salesforce functionality.  Recently I had a requirement to capture some notes and a subset of the record details at various points of the record's life.  Effectively the notes were specific to the record's current state and viewing either in isolation wouldn't give the desired outcome. While it would be possible to handle this with a combination of notes/attachments and field history tracking, that puts far too much emphasis on the user to pull the information from disparate places.


To satisfy this requirement I came up with a Visualforce page that is embedded in a record view and has a notes field to capture the free text plus a checkbox to indicate if the opportunity detail should be captured.  The page embedded into an Opportunity record is shown below:



The fields that are included into the attachment are coded into the page and controller in this case, but it would be straightforward to use Visualforce field sets to reduce the maintenance overhead.  The Visualforce markup is shown below:?

<apex:page standardController="Opportunity" extensions="OpportunityAttachmentController">
  <apex:outputText value="{!Opportunity.Account.Name}" rendered="false"/>
  <apex:outputText value="{!Opportunity.CloseDate}" rendered="false"/>
  <apex:outputText value="{!Opportunity.Amount}" rendered="false"/>
  <apex:outputText value="{!Opportunity.StageName}" rendered="false"/>
  <apex:outputText value="{!Opportunity.Probability}" rendered="false"/>
  <apex:outputText value="{!Opportunity.ExpectedRevenue}" rendered="false"/>
    
  <c:RefreshEmbeddedComponent id="refresher" location="/{!Opportunity.id}" active="{!refreshPage}"/>
  <apex:form >
   <apex:pageBlock title="Add Attachment" mode="mainDetail" id="pb">
      <apex:pageBlockSection columns="2">
         <apex:pageBlockSectionItem >
            <apex:outputLabel for="title" value="Title"/>
            <apex:inputText id="title" value="{!title}"/>
         </apex:pageBlockSectionItem>
         <apex:pageBlockSectionItem >
            <apex:outputLabel for="incsum" value="Include Opportunity Detail?"/>
            <apex:inputCheckbox id="incsum" value="{!includeOppDetail}"/>
         </apex:pageBlockSectionItem>
      </apex:pageBlockSection>
      <apex:pageBlockSection columns="1">
         <apex:pageBlockSectionItem >
            <apex:outputLabel for="notes" value="Notes"/>
            <apex:inputTextArea rows="15" style="width:80%" id="notes" value="{!notes}"/>
         </apex:pageBlockSectionItem>
      </apex:pageBlockSection>
      <apex:commandButton value="Add As Attachment" action="{!addAttachment}" rerender="refresher"/>
   </apex:pageBlock>
  </apex:form>
</apex:page>

The first part of the page simply pulls in the fields that are required by the controller. Note that these aren't rendered or used in the page, but without these components my controller would have to query the details from the database:
1
2
3
4
5
6
<apex:outputText value="{!Opportunity.Account.Name}" rendered="false"/>
  <apex:outputText value="{!Opportunity.CloseDate}" rendered="false"/>
  <apex:outputText value="{!Opportunity.Amount}" rendered="false"/>
  <apex:outputText value="{!Opportunity.StageName}" rendered="false"/>
  <apex:outputText value="{!Opportunity.Probability}" rendered="false"/>
  <apex:outputText value="{!Opportunity.ExpectedRevenue}" rendered="false"/>

The only other aspect that isn't vanilla Visualforce forms is the following custom component:?

<c:RefreshEmbeddedComponent id="refresher" location="/{!Opportunity.id}" active="{!refreshPage}"/>

This allows the entire record view page to be refreshed once a Boolean value from the controller is set to true - its the functionality from an earlier post embedded into a component to promote reuse.

Once the user has filled in the form, the following Apex action method is invoked:?

public void addAttachment()
{
 String bodyStr=title + '\n' +
                notes;
   
 if (includeOppDetail)
 {
  bodyStr+='\n\nOpportunity Summary\n' +
           'Account      : ' + opp.Account.Name + '\n' +
           'Close Date   : ' + opp.CloseDate + '\n' +
           'Amount       : ' + opp.Amount + '\n' +
           'Stage        : ' + opp.StageName + '\n' +
           'Probability  : ' + opp.Probability + '\n' +
           'Expected Rev : ' + opp.ExpectedRevenue;
 }
 bodyStr+='\n\n\n' +
           System.now();
   
 Attachment att=new Attachment();
 att.Body=Blob.valueOf(bodyStr);
 att.Name='Note_' + System.now().format('yyyy_MM_dd_hh_mm_ss') + '.txt';
 att.parentId=opp.id;
 insert att;
   
 refreshPage=true;
}

The first part of the method simply builds up the text body of the attachment in a String, appending the user's notes and, if requested, a snapshot of the opportunity fields.

The interesting part is where the attachment is created:?

Attachment att=new Attachment();
att.Body=Blob.valueOf(bodyStr);
att.Name='Note_' + System.now().format('yyyy_MM_dd_hh_mm_ss') + '.txt';
att.parentId=opp.id;
insert att;


As you can see there's not much to do - simply create a new attachment, convert the String containing the body to a Blob and store that in the attachment's body field, set up the name and which record its attached to and then insert. The final line sets the flag to refresh the record view page.

No comments:

Post a Comment

Understanding Wire vs Imperative Apex Method Calls in Salesforce Lightning Web Components (LWC)

Understanding Wire vs Imperative Apex Method Calls in Salesforce Lightning Web Components (LWC) Introduction: Salesforce Lightning Web ...