Introduction
An external configurator, in the context of Salesforce CPQ (Configure, Price, Quote), is a tool or application that extends the capabilities of Salesforce CPQ by allowing organizations to manage complex product configurations and pricing scenarios seamlessly. This addition significantly enhances the efficiency and effectiveness of the quote-to-cash process.
Objectif
Our objective is to implement a mapping solution that enables us to specify multiple delivery locations and automatically determine the distance between each pair of points (segments), considering this distance as a quantitative measure.
Github link : https://platform.openai.com/docs/introduction
Section 1 : Understand how it works
Section 2: Preparing your Environment
We begin by creating the dynamic bundle (Delivery product) and configuring it
Next, we create the 'Start' and 'End' custom fields and select 'Geolocation' as the data type.
Subsequently, we include the 'Leaflet.js' library as static resources.
Finally, we add ‘Visualforce Page’, ‘Aura Application’, ‘Delivery LWC’  that uses the ‘leaflet.js’ library to display the maps and configure Salesforce CPQ (installed package)
Code of visualForce Page
<apex:page sidebar="false" showHeader="false" applyHtmlTag="false" applyBodyTag="false" docType="html-5.0" standardStylesheets="false">
<html xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg">
<head>
<apex:slds />
<apex:includeLightning />
<title>External Configurator</title>
<script type="text/javascript" src="{!$Resource.SBQQ__easyXDM}"></script>
</head>
<body>
<div id="deliveryComponent"> </div>
</body>
<script type="text/javascript" id="script">
$Lightning.use("c:deliveryApp", function() {
$Lightning.createComponent("c:delivery",
{
"xdm": easyXDM
},
"deliveryComponent",
function(cmp) {
// Callback
//debugger;
console.log("Delivery component successfully created");
});
});//Lightning use
</script>
</html>
</apex:page>
Code of Delivery LWC
Delivery.js :
import { LightningElement, api, track } from 'lwc';
import { loadScript, loadStyle } from 'lightning/platformResourceLoader';
import leaflet from '@salesforce/resourceUrl/leaflet';
export default class Delivery extends LightningElement {
@api xdm;
mapMarkers = [];
configObj = {} ;
optionArray = [];
optionId = 'a1JAY000002LX7t2AG'; // This is the delivery charge option Id
map;
rpc;
renderedCallback() {
console.log(this.refs.myDiv);
}
connectedCallback(){
Promise.all([
loadScript(this, leaflet + '/leaflet.js'),
loadStyle(this, leaflet + '/leaflet.css'),
]).then(() => {
this.draw();
})
}
draw () {
// Initialize the Leaflet Map
let container = this.refs.myDiv;
let position = [37.789785, -122.396964];
this.map = L.map(container).setView(position, 15);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="<https://www.openstreetmap.org/copyright>">OpenStreetMap</a> contributors',
}).addTo(this.map);
this.map.on("click", (e) => {
let marker = L.marker([e.latlng.lat, e.latlng.lng]).addTo(this.map);
console.log("Position: ",marker.getLatLng());
//this.mapMarkers.add(marker);
this.mapMarkers.push(marker);
console.log("Map Markers: ", this.mapMarkers);
})
this.rpc = new this.xdm.Rpc({}, {
// method defined in Salesforce CPQ
remote: {
postMessage: {}
},
// method for receiving configuration JSON from Salesforce CPQ
local: {
postMessage: (message) => {
this.configObj = JSON.parse(message);
console.log("configObj: ",this.configObj);
}
}
});
}
resetMarkers() {
// Logique pour réinitialiser les marqueurs
optionArray = [];
// remove markers from the leaflet map
this.mapMarkers.forEach( (marker) => {
this.map.removeLayer(marker);
});
this.mapMarkers = [];
console.log("Reset map Markers: ", this.mapMarkers);
}
broadcast() {
// Logique pour sauvegarder la configuration
// Loop through the markers array and create product options for each segement
for (var i = 0; i < this.mapMarkers.length - 1; i++) {
this.createOption(this.mapMarkers[i].getLatLng(), this.mapMarkers[i + 1].getLatLng());
}
// Set the newly created options to the Delivery Options feature
console.log("this.optionArray: ", this.optionArray);
this.configObj.product.optionConfigurations["Delivery Options"] = this.optionArray;
// Send the JSON configuration String back to Salesforce CPQ
this.rpc.postMessage(JSON.stringify(this.configObj));
}
createOption(startPoint, endPoint) {
// calculate the distance based on the start and end lat / long
var distance = this.getDistanceFromLatLonInKm(startPoint.lat, startPoint.lng, endPoint.lat, endPoint.lng);
// create the new option
var optData = {
"optionId": this.optionId,
"Quantity": distance, // kms
"selected": true,
"ProductName": "Delivery Charge (km)",
"index": this.optionArray.length,
"readOnly": {}, // this is where quote line data gets stored after they are created
configurationData: {
"Start__latitude__s": startPoint.lat,
"Start__longitude__s": startPoint.lng,
"End__latitude__s": endPoint.lat,
"End__longitude__s": endPoint.lng,
}
}
// add the new option to the array
this.optionArray.push(optData);
}
// https://en.wikipedia.org/wiki/Haversine_formula
// Haversine formula to calculate distance based on lat and long
getDistanceFromLatLonInKm(lat1, lon1, lat2, lon2) {
var R = 6371; // mean radius of the earth in km
var dLat = this.deg2rad(lat2 - lat1); // deg2rad below
var dLon = this.deg2rad(lon2 - lon1);
var a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(this.deg2rad(lat1)) * Math.cos(this.deg2rad(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2)
;
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c; // Distance in km
return d;
}
// Convert degrees to radians
deg2rad(deg) {
return deg * (Math.PI / 180)
}
}
Delivery.html :
<template>
<article class="slds-card" style="width: 100%; height: 700px">
<!-- Header -->
<div class="slds-card__header slds-grid">
<header class="slds-media slds-media_center slds-has-flexi-truncate">
<div class="slds-media__figure">
<lightning-icon icon-name="standard:location" size="medium" alternative-text="Map"></lightning-icon>
</div>
<div class="slds-media__body">
<h2 class="slds-text-heading_small">Delivery Configuration Map</h2>
<p class="slds-text-body_small slds-line-height_reset">Place start and end point markers on the map.</p>
</div>
<div class="slds-col slds-no-flex slds-grid slds-align-top">
<lightning-button label="Reset Markers" onclick={resetMarkers} class="slds-p-right_small"></lightning-button>
<lightning-button label="Save" onclick={broadcast} variant="brand"></lightning-button>
</div>
</header>
</div>
<!-- End Header -->
<div class="slds-card__body slds-card__body_inner slds-p-top_large">
<div lwc:ref = "myDiv" id="map" class="map-container" style="width: 100%; height: 450px; border: 1px solid #1798c1;"></div>
</div>
</article>
</template>
Code of deliveryApp (Aura Application)
<aura:application access="GLOBAL" extends="ltng:outApp">
<aura:dependency resource="c:delivery" />
</aura:application>
Github link : https://platform.openai.com/docs/introduction
Commenti