In the previous section, we showed you how to automate document generation using Flows. However, if you need to add some additional implementations that Process Builder can't provide you - or you're just a hardcore programmer and you need to write some code to be happy – you can also create Document Requests from Apex. Since we don't want things to get too complicated here, we'll assume that the latter is true and we'll do the same thing that we did in the Process Builder section – we'll create quotes for Opportunities that reach the Proposal/Price Quote stage. So let's get started!
You can create Document Requests in Apex Classes and Triggers just like any other Salesforce standard or custom objects. We'll show you how to do that with a simple example by creating an Apex Trigger. As we said before, we want to create a Document Request when an Opportunity reaches the Proposal/Price Quote stage and has some Products related to it. So let's create our Trigger. Open the Developer Console, create a new Trigger on the Opportunity object, name it OpportunityQuoteTrigger, and make it execute after update.
Because we don't want to go too deep here – and we're not really here to teach you about basic programming – we'll just put the code here that covers the logic behind our trigger. Also, Salesforce recommends that logic behind handling triggers is written in separate handler classes. But again, we don't want to complicate things too much, so we'll write our logic directly in the trigger to keep it simple.
Here is the code for our trigger:
trigger OpportunityQuoteTrigger on Opportunity (after update) {
List<mmdoc__Document_Request__c> newRequests = new List<mmdoc__Document_Request__c>();
for (Id oppId : Trigger.NewMap.keySet()) {
if(Trigger.NewMap.get(oppId).StageName == 'Proposal/Price Quote' &&
Trigger.OldMap.get(oppId).StageName != 'Proposal/Price Quote' &&
Trigger.NewMap.get(oppId).HasOpportunityLineItem) {
//new Document Request here
}
}
if(!newRequests.isEmpty()) {
insert newRequests;
}
}
We made sure that a new Document Request is created only when it's stage has been changed to Proposal/Price Quote, because we don't want to generate a new quote when irrelevant changes are made to the record (we did the same thing when were doing this in Process Builder).
As you can see, our code is only missing the part where we need to create our Document Request. Here are all the field values you'll need to enter:
In the Flows section we showed you how to get IDs of the Document Solution and Document Template records, so we won't do that again here.
Finally, here's how the code will look in the end:
trigger OpportunityQuoteTrigger on Opportunity (after update) {
List<mmdoc__Document_Request__c> newRequests = new List<mmdoc__Document_Request__c>();
for (Id oppId : Trigger.NewMap.keySet()) {
if(Trigger.NewMap.get(oppId).StageName == 'Proposal/Price Quote' &&
Trigger.OldMap.get(oppId).StageName != 'Proposal/Price Quote' &&
Trigger.NewMap.get(oppId).HasOpportunityLineItem) {
newRequests.add(
new mmdoc__Document_Request__c(
mmdoc__Record_Id__c = oppId,
mmdoc__Document_Solution__c = 'YOUR SALES QUOTE ID',
mmdoc__Document_Template__c = 'YOUR SALES QUOTE TEMPLATE HERE',
mmdoc__Document_Format__c = 'PDF',
mmdoc__Save_As__c = 'File',
mmdoc__Status__c = 'New'
)
);
}
}
if(!newRequests.isEmpty()) {
insert newRequests;
}
}
And that's it, when you save the trigger it's automatically activated and ready to use. You can test the trigger by changing the stage of any Opportunity to Proposal/Price Quote (make sure it has some Products as well). In the Flows article, we showed an example of that so you can check it out.
LMS (Lightning Message Service) — provides a simple API to publish messages throughout Lightning Experience and subscribe to messages that originated from anywhere within Lightning Experience.
Once document generation is finished with processing, a new mmdoc__DocumentRequestMessageChannel__c Lightning Message will be dispatched.
The structure of that message is as follows:
message = {
recordId: "ID of the record that Document generation has started from",
recordData: { value: "mmdoc__Document_Request__c record" }
};
You can now easily subscribe to this lightning message in your Visualforce page, Lightning Web Component, or Aura, and build your custom logic that continues with the custom process afterward.
Here are a couple of examples of how you can do that:
Visualforce page
<apex:page>
...
<script>
// Load the Message Channel token in a variable
var SAMPLEMC = "{!$MessageChannel.mmdoc__Document_Request__c}";
var subscription;
function handleMessage(message) {
var textArea = document.querySelector("#messageTextArea");
textArea.innerHTML = message ? JSON.stringify(message, null, '\t') : 'no message payload';
}
function subscribeMC() {
if (!subscription) {
subscription = sforce.one.subscribe(SAMPLEMC, handleMessage);
}
}
function unsubscribeMC() {
if (subscription) {
sforce.one.unsubscribe(subscription);
subscription = null;
}
}
</script>
...
</apex:page>
LWC
// mySubscriberComponent.js
import { LightningElement, track } from 'lwc';
import { createMessageContext, releaseMessageContext
subscribe, unsubscribe } from 'lightning/messageService';
import SAMPLEMC from "@salesforce/messageChannel/mmdoc__Document_Request__c";
export default class MySubscriberComponent extends LightningElement {
context = createMessageContext();
subscription = null;
@track receivedMessage = '';
subscribeMC() {
if (this.subscription) {
return;
}
this.subscription = subscribe(this.context, SAMPLEMC, (message) => {
this.handleMessage(message);
});
}
unsubscribeMC() {
unsubscribe(this.subscription);
this.subscription = null;
}
handleMessage(message) {
this.receivedMessage = message ? JSON.stringify(message, null, '\t') : 'no message payload';
}
disconnectedCallback() {
releaseMessageContext(this.context);
}
}
Aura
<!-- mySubscriberComponent.cmp -->
<aura:component>
<aura:attribute name="recordValue" type="String"/>
<lightning:formattedText value="{!v.recordValue}" />
<lightning:messageChannel type="mmdoc__Document_Request__c"
onMessage="{!c.handleMessage}"/>
</aura:component>
// mySubscriberComponentController.js
({
handleMessage: function(cmp, message, helper) {
// Read the message argument to get the values in the message payload
if (message != null && message.getParam("recordData") != null) {
cmp.set("v.recordValue", message.getParam("recordData").value);
}
}
})