top of page

How to implement External configurator in Salesforce CPQ Using LWC


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.



Section 1 : Understand how it works





You choose the dynamic bundle then click select
Add the Dynamic Bundle


Select the different delivery points and click on save
Selection of delivery points


Get information from the external configurator

Section 2: Preparing your Environment


We begin by creating the dynamic bundle (Delivery product) and configuring it



Add the delivery product and in the Salesforce CPQ Configuration select the configuration as shown in the screenshot
Configuring the bundle


Add the product Delivery Charge
Creation of related product


Add the feature Delivery Options
Creation of feature for the bundle


Add options for the dynamic bundle

Next, we create the 'Start' and 'End' custom fields and select 'Geolocation' as the data type.



Adding the 'Start' custom field


Adding the 'End' custom field

Subsequently, we include the 'Leaflet.js' library as static resources.



Add the library Leaflet as static resources
Leaflet.js Library

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)


Visualforce Page


Configure Salesforce CPQ, in Installed package, click on configure and configure it as shown in the screenshot
Add configuration


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: '&copy; <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>	

15 views0 comments

Recent Posts

See All

Commenti

Valutazione 0 stelle su 5.
Non ci sono ancora valutazioni

Aggiungi una valutazione
bottom of page