Hey guys, today in this post we are going to learn about How to Create Quote and QuoteLineItem From Opportunity in Lightning Web Component – LWC Salesforce.
Quote Line Items are automatically created when the Quote is created from an Opportunity based on the Opportunity Products attached to that Opportunity. Creating a Quote from an Opportunity in Classic always results in Quote Line Items created but in Lightning for the same record it is possible that no Quote Line Items are created. To know more about Quote and QuoteLineItem, Click Here.
Final Output →
You can download file directly from github by Click Here.
Other related post that would you like to learn in Salesforce
- Find the below steps ▾
Create Lightning Web Component HTML
Step 1:- Create Lightning Web Component : createQuoteAndLineItemLwc.html
SFDX:Lightning Web Component >> New >> createQuoteAndLineItemLwc.html
createQuoteAndLineItemLwc.html [Lightning Web Component HTML]
<template>
<lightning-card title="Create Quote and Quote Line Item from Opportunity" icon-name="custom:custom17">
<div class="slds-p-around_medium">
<div class="slds-m-bottom_large">
<h2 class="slds-section-title--divider"><strong>Quote</strong></h2>
</div>
<table border="0" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
<tr >
<td>
<div style="height: 600px; overflow:auto; width:100%;">
<table border="0" cellspacing="0" cellpadding="0" style="border-collapse:collapse;">
<tr for:each={oppProductLineItemArr} for:item='optFormData' key={rowId} for:index='index' >
<td>
<form data-name="opptForm">
<div class="slds-grid slds-wrap">
<div class="slds-col slds-size_6-of-12">
<div class="slds-form-element_controller">
<label class="slds-form-element__label">Name</label>
<lightning-input type="text" class="inpFld" data-type="input-field"
name="Name" value={optFormData.Name} style="width: 200px;">
</lightning-input>
</div>
</div>
<div class="slds-col slds-size_6-of-12">
<div class="slds-form-element_controller">
<label class="slds-form-element__label">Quote</label>
<lightning-input type="text" class="inpFld" data-type="input-field"
name="OpportunityId" value={optFormData.Id} data-inx={index} style="width: 200px;">
</lightning-input>
</div>
</div>
<div class="slds-col slds-size_6-of-12">
<div class="slds-form-element_controller">
<label class="slds-form-element__label">Description</label>
<lightning-input type="text" class="inpFld" data-type="input-field"
name="Description" value={optFormData.Description} style="width: 200px;">
</lightning-input>
</div>
</div>
<div class="slds-col slds-size_6-of-12">
<div class="slds-form-element_controller">
<label class="slds-form-element__label">Pricebook2Id</label>
<lightning-input type="text" class="inpFld" data-type="input-field"
name="Pricebook2Id" value={Pricebook2Id} style="width: 200px;">
</lightning-input>
</div>
</div>
</div>
</form>
</td>
</tr>
</table>
<div class="slds-m-bottom_large">
<h2 class="slds-section-title--divider slds-m-top_large"><strong>Quote Line Item</strong></h2>
</div>
<div class="slds-m-around--x-small container-fluid">
<div class="slds-float_right slds-p-bottom_small">
<h1 class="slds-page-header__title">
<lightning-button-icon icon-name="utility:add" value="Add Quote Line Item" size="large" variant="brand" alternative-text="Add" onclick={addRow}> Add Quote Line Item </lightning-button-icon>
</h1>
</div>
<div class="container-fluid">
<table class="slds-table slds-table_bordered slds-table_cell-buffer">
<template for:each={quoteLineItemList} for:item="qItem" for:index="indx">
<tr key={qItem.key} id={qotLine.key}>
<td class="slds-hide">{indx}</td>
<td>
<p>Description:- <lightning-input class="reqInpFld" data-id={indx} value={qItem.Description} onchange={handleDescriptionChange}></lightning-input></p>
</td>
<td>
<p>UnitPrice:- <lightning-input class="reqInpFld" data-id={indx} value={qItem.UnitPrice} onchange={handleUnitPriceChange}></lightning-input></p>
</td>
<td>
<p>Quantity:- <lightning-input class="reqInpFld" data-id={indx} value={qItem.Quantity} onchange={handleQuantityChange}></lightning-input></p>
</td>
<td style="display:none;">
<p>QuoteId:- <lightning-input class="reqInpFld" data-id={indx} value={quoteRecId}></lightning-input></p>
</td>
<td>
<p>Product2Id:- <lightning-input class="reqInpFld" data-id={indx} value={Product2Id}></lightning-input></p>
</td>
<td>
<div class="slds">
<br/><br/>
<lightning-button-icon icon-name="utility:delete"
data-id={indx}
alternative-text="Delete"
class="slds-m-left_xx-small"
onclick={removeRow}
title="Delete"></lightning-button-icon>
</div>
</td>
</tr>
</template>
</table>
</div>
</div>
<!--End Line Item-->
<!--Start Save Button-->
<div class="slds-align_absolute-center slds-p-top_small">
<lightning-button name="Save" variant="brand" label="Save" onclick={saveRecord} ></lightning-button>
</div>
<!--End Save Button-->
</div>
</td>
</tr>
</table>
</div>
</lightning-card>
</template>
Create Lightning Web Component JavaScript
Step 2:- Create Lightning Web Component : createQuoteAndLineItemLwc.js
SFDX:Lightning Web Component >> New >> createQuoteAndLineItemLwc.js
createQuoteAndLineItemLwc.js [LWC JavaScript File]
import { LightningElement, track,api, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
import submitOptRecord from '@salesforce/apex/createQuoteAndLineItemLwcCtrl.submitOptRecord';
import getOpportunityList from '@salesforce/apex/createQuoteAndLineItemLwcCtrl.getOpportunityList';
import saveQuoteLineItem from '@salesforce/apex/createQuoteAndLineItemLwcCtrl.saveQuoteLineItem';
//import NAME_FIELD from '@salesforce/schema/QuoteLineItem.Name';
import Description_FIELD from '@salesforce/schema/QuoteLineItem.Description';
import UnitPrice_FIELD from '@salesforce/schema/QuoteLineItem.UnitPrice';
import Quantity_FIELD from '@salesforce/schema/QuoteLineItem.Quantity';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import {NavigationMixin} from 'lightning/navigation';
export default class CreateQuoteAndLineItemLwc extends NavigationMixin(LightningElement) {
@track quoteLineItemList = [];
@track index = 0;
@api recordId;
@api quoteRecId;
// @track name = NAME_FIELD;
@track Description = Description_FIELD;
@track UnitPrice = UnitPrice_FIELD;
@track Quantity = Quantity_FIELD;
@track quoteLineRecoreId;
@track message;
@track error;
isLoaded = false;
@track optItemList ;
@track oppProductLineItemArr=[];
@track optFormData = {};
@track Product2Id= '01t5g000003OxvQAAS';
@track PricebookEntryId='01u5g000003gfcsAAA';
@track Pricebook2Id='01s5g00000KmN13AAF';
//Start ConnectedCallback
connectedCallback() {
}
@wire(getOpportunityList,{recId:'$recordId'})
getInfos({error,data}){
if(error){
console.log('error == '+JSON.stringify(error));
}else if(data){
console.log('data == ', JSON.stringify(data));
this.oppProductLineItemArr = JSON.parse(JSON.stringify(data));
return;
}
}
//Start Quote
//Start Quote Line Item
// acc
qotLine = {
Name : this.name,
Description : this.Description,
UnitPrice : this.UnitPrice,
Quantity : this.Quantity,
QuoteId : '',
Product2Id :this.Product2Id,
PricebookEntryId :this.PricebookEntryId,
key : ''
}
addRow(){
this.index++;
var i = this.index;
this.qotLine.key = i;
this.quoteLineItemList.push(JSON.parse(JSON.stringify(this.qotLine)));
console.log('Enter ',this.quoteLineItemList);
}
removeRow(event){
this.isLoaded = true;
var selectedRow = event.currentTarget;
var key = selectedRow.dataset.id;
if(this.quoteLineItemList.length>1){
this.quoteLineItemList.splice(key, 1);
this.index--;
this.isLoaded = false;
}else if(this.quoteLineItemList.length == 1){
this.quoteLineItemList = [];
this.index = 0;
this.isLoaded = false;
}
}
handleNameChange(event) {
var selectedRow = event.currentTarget;
console.log('selectedRow_! ' + selectedRow );
var key = selectedRow.dataset.id;
var accountVar = this.quoteLineItemList[key];
this.quoteLineItemList[key].Name = event.target.value;
console.log('handleNameChange# ' + this.quoteLineItemList[key].Name);
}
handleDescriptionChange(event) {
var selectedRow = event.currentTarget;
var key = selectedRow.dataset.id;
var lineItemVar = this.quoteLineItemList[key];
this.quoteLineItemList[key].Description = event.target.value;
}
handleUnitPriceChange(event) {
var selectedRow = event.currentTarget;
var key = selectedRow.dataset.id;
var lineItemVar = this.quoteLineItemList[key];
this.quoteLineItemList[key].UnitPrice = event.target.value;
}
handleQuantityChange(event) {
var selectedRow = event.currentTarget;
var key = selectedRow.dataset.id;
var lineItemVar = this.quoteLineItemList[key];
this.quoteLineItemList[key].Quantity = event.target.value;
}
//Start Quote Javascript
//Start Quote and Quote Lline Item Save Functionality
saveRecord(){
//Start Quote
let flag = true;
for (const elem of [...this.template.querySelectorAll('form[data-name="opptForm"] [data-type="input-field"]')]) {
this.optFormData[elem.name] = elem.value;
console.log('aaaaa' , elem.value);
}
console.log('optFormData## ', this.optFormData);
console.log('optFormDataStringyFy',JSON.stringify(this.optFormData));
const data = {
optDataFyObj: this.optFormData,
};
console.log('optDataFyObj## ',JSON.stringify(data));
//if(flag){
const result = submitOptRecord({
jsonDataStr: JSON.stringify(data)
})
.then(result => {
//resultData1 =JSON.stringify(result);
console.log('result1' , JSON.parse(result));
// console.log('result1_A' , result);
this.quoteJsonData = result[0].Id;
console.log('quoteJsonData' , this.quoteJsonData);
console.log('result2', result[0].id);
var quoteId;
var obj = JSON.parse(result, function (key, value) {
if(key == 'id' && value != undefined && value != null) {
console.log('Quote id '+ key+' :: '+value);
quoteId = value;
}
});
console.log('quoteId 175 #' + quoteId );
this.quoteRecId=quoteId;
for(let i of this.quoteLineItemList){
i['QuoteId'] =quoteId;
i['Name'] =quoteId;
console.log('aaa Item_' + JSON.stringify(i));
}
console.log('quoteLineItemListQQ ' + this.quoteLineItemList);
//Start Qoute Line Item
saveQuoteLineItem({quoteItemList : this.quoteLineItemList})
.then(result => {
console.log('result_11' , JSON.parse(result));
this.quoteLineRecoreId = result.Id;
console.log('resultId ' + result[0].Id);
console.log('quoteLineRecoreId@@@ ' + this.quoteLineRecoreId);
this.message = result;
this.error = undefined;
})
.catch(error => {
this.message = undefined;
this.error = error;
console.log("error", JSON.stringify(this.error));
});
//End Qoute Line Item
//Start Toast Message
const toastEvent = new ShowToastEvent({
title:'Success!',
message:'Record created successfully',
variant:'success'
});
this.dispatchEvent(toastEvent);
//End Toast Message
/*Start Navigation*/
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: quoteId,
objectApiName: 'Quote',
actionName: 'view'
},
});
/*End Navigation*/
});
//}
//End Quote
}
}
Create Lightning Web Component Meta XML
Step 3:- Create Lightning Web Component : createQuoteAndLineItemLwc.js-meta.xml
SFDX:Lightning Web Component >> New >> createQuoteAndLineItemLwc.js-meta.xml
createQuoteAndLineItemLwc.js-meta.xml [LWC Meta Data XML]
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>56.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__AppPage</target>
<target>lightning__HomePage</target>
<target>lightning__RecordPage</target>
<target>lightning__RecordAction</target>
<target>lightning__UtilityBar</target>
</targets>
<targetConfigs>
<targetConfig targets="lightning__AppPage, lightning__HomePage, lightning__RecordPage">
<supportedFormFactors>
<supportedFormFactor type="Large" />
<supportedFormFactor type="Small" />
</supportedFormFactors>
</targetConfig>
</targetConfigs>
</LightningComponentBundle>
Create Apex Class Controller
Step 4:- Create Apex Controller : insertLookupFieldValueLwcCtrl.cls
SFDX:Create Apex Class >> New >> createQuoteAndLineItemLwcCtrl.cls
createQuoteAndLineItemLwcCtrl.cls [Apex Class]
public WITH sharing class createQuoteAndLineItemLwcCtrl {
public createQuoteAndLineItemLwcCtrl() {
}
@AuraEnabled(cacheable=TRUE)
public static List<Opportunity> getOpportunityList(Id recId){
List<Opportunity> optList = [SELECT Id, Name, Description FROM Opportunity WHERE Id=:recId];
RETURN optList;
}
//START Quote
@AuraEnabled
public static String submitOptRecord(String jsonDataStr) {
Map<String, Object> RESULT = NEW Map<String, Object>();
String DataRespoce ='';
try {
Map<String, Object> formDataMap = (Map<String, Object>)JSON.deserializeUntyped(jsonDataStr);
Map<String, Object> OptDataMap = (Map<String, Object>)formDataMap.get('optDataFyObj');
// Opportunity optObj = NEW Opportunity();
Quote optObj = NEW Quote();
optObj.Name = getStringValueFromMap(OptDataMap, 'Name');
optObj.OpportunityId = getStringValueFromMap(OptDataMap, 'OpportunityId');
optObj.Description = getStringValueFromMap(OptDataMap, 'Description');
optObj.Pricebook2Id = getStringValueFromMap(OptDataMap, 'Pricebook2Id');
List<DATABASE.SaveResult> insertResult = DATABASE.insert(NEW List<Quote>{optObj});
DataRespoce =JSON.serialize(insertResult);
}catch(Exception ex) {
RESULT.put('status', 500);
RESULT.put('message', 'Exception ' + ex.getMessage() + ',line' + ex.getLineNumber());
}
RETURN DataRespoce;
}
public static String getStringValueFromMap(Map<String, Object> dataMap, String fieldName) {
String VALUE;
try {
IF(dataMap.containsKey(fieldName)) {
VALUE = String.valueOf(dataMap.get(fieldName));
}
VALUE = String.isEmpty(VALUE) ? VALUE : String.valueOf(VALUE);
} catch(Exception ex) {
System.debug('Exception getValueFromMap : '+ ex.getMessage() + ' line ' + ex.getLineNumber());
}
RETURN VALUE;
}
public static DATE getDateValueFromMap(Map<String, Object> dataMap, String fieldName) {
DATE VALUE;
try {
String str;
IF(dataMap.containsKey(fieldName)) {
str = String.valueOf(dataMap.get(fieldName));
}
VALUE = String.isEmpty(str) ? VALUE : DATE.valueOf(str);
} catch(Exception ex) {
System.debug('Exception getIntValueFromMap : '+ ex.getMessage() + ' line ' + ex.getLineNumber());
}
RETURN VALUE;
}
//START Quote Line Item
@AuraEnabled
public static void saveQuoteLineItem(List<QuoteLineItem> quoteItemList){
try {
INSERT quoteItemList;
System.debug('quoteItemList## ' + quoteItemList);
}
catch(Exception ex) {
System.debug('Exception getValueFromMap : '+ ex.getMessage() + ' line ' + ex.getLineNumber());
}
}
//END Quote Line Item
}
Further post that would you like to learn in Salesforce
Can I create quote without opportunity?
A quote is always associated with an opportunity. You can either create a quote for an existing opportunity or create a quote even without the existence of an opportunity for it. If you create a quote without a preexisting opportunity, the opportunity is automatically created when the quote is created.
What is quote and quote line in Salesforce?
Quote lines store information about products that a sales rep has quoted. With certain page layout and field-level security settings, some fields aren't visible or editable.
What is the difference between opportunity and quote?
An opportunity acts as a container for holding a quote or related alternative quotes that are to be presented to a customer. A quote, on the other hand, is a commercial document that allows a customer to see a predetermined set of products, their quantity, predetermined price, and delivery date.
Related Topics | You May Also Like
Our Free Courses →
👉 Get Free Course →
📌 Salesforce Administrators 📌 Salesforce Lightning Flow Builder 📌 Salesforce Record Trigger Flow Builder |
👉 Get Free Course →
📌 Aura Lightning Framework 📌 Lightning Web Component (LWC) 📌 Rest APIs Integration |