In the previous section, we showed you how to automate document generation using Flows.
However, if you need to add some additional implementations that the 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.
To avoid complexity, we'll assume the latter is true and follow the same approach as in the Process Builder section by creating 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.
We want to create a Document Request when an Opportunity reaches the "Proposal/Price Quote" stage and has Products.
Let's create our Trigger:
Because we don't want to go too deep here — and we're not here to teach you about basic programming — we'll just put the code here that covers the logic behind our trigger.
Salesforce recommends that the logic behind handling triggers be 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 the 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 article we showed you how to retrieve IDs of the Document Solution and Document Template records so we won't repeat it 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;
}
}
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).
We included an example of this in the [Flows](https://docs.mavenmule.com/maven-documents/automate-with-process-flow article so you can check it out there.
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);
}
}
})