1

I am relatively new to LWC and think I could probably handle this ask using Aura, but I really want to get into the new LWC direction so here I am. This ask is for Salesforce CPQ and Docusign. What I am trying to do is utilize Quote Document(Parent record/LWC) to display a LWC with fields from both Quote(SBQQ__Quote__c) and Quote Lines(SBQQ__QuoteLine__c), Quote Document and Quote Lines both have lookups to the Quote record. Since this is living on Quote Document, I am trying to pull the Quote's ID and pass it down to the Quote LWC and the Quote Lines LWC as parameters for their @wire calls to my apex controller, if that is even necessary. What I mean by that is that I am wondering if I can just go LDS all the way here and pass that quoteId as the recordId param in the @wire getRecord call. Neither of which routes seem to be working at all and console errors just keep yelling at me that everything is undefined. I will note that I AM getting the QuoteId from the Quote Document record in my console but passing it down just isn't happening. I feel like with Aura I could throw this in a doInit JS controller function and get it to pop up that way, is there an alternative to this functionality in LWC? I am literally open to any ideas here because I'd really like to utilize this methodology for this client and impress my employer since none of us devs have gone the LWC route quite yet, at least not to this capacity, to my knowledge anyway. Anyway, here is my code:

Apex Controller querying for the Quote and Quote Line records based on the Quote that the Quote Document is linked to:

public with sharing class QuoteWindowInfo {
    @AuraEnabled(cacheable=true)
    public static List<SBQQ__Quote__c> getQuote(Id qid){

        return [
            SELECT
            Name,
            SBQQ__Introduction__c,
            SBQQ__PaymentTerms__c,
            SBQQ__StartDate__c,
            SBQQ__SubscriptionTerm__c
            FROM SBQQ__Quote__c
            WHERE Id = :qid
        ];

    }

    @AuraEnabled(cacheable=true)
    public static List<SBQQ__QuoteLine__c> getQuoteLines(Id quoteId){

        return [
            SELECT
            Id,
            SBQQ__ListTotal__c,
            SBQQ__ProductName__c,
            SBQQ__Quote__c
            FROM SBQQ__QuoteLine__c
            WHERE SBQQ__Quote__c = :quoteId
        ];

    }
}

parent LWC:

<template>

    <div class="bg border">

        <div class="center">lets call this the title</div>
        <template if:true={qid}>
            <c-quote-window-quote qid={qid}></c-quote-window-quote>
        </template>     

        <div class="center">The incentives included on this quote are:</div>
        <!-- <c-quote-window-quote-lines></c-quote-window-quote-lines> -->

        <div>
            <lightning-record-edit-form
            record-id={recordId}
            object-api-name={objectApiName}
            onsuccess={successToast}
            >                        
                <lightning-input-field field-name="DocusignCheck1__c"></lightning-input-field>                             
                <lightning-input-field field-name="DocusignCheck2__c"></lightning-input-field>                             
                <lightning-input-field field-name="DocusignCheck3__c"></lightning-input-field>                             
                <br/>            
                <lightning-button class="slds-align_absolute-center" variant="brand" type="submit" label="Submit for Docusign"></lightning-button>                        
            </lightning-record-edit-form>     
        </div>           

    </div>    

</template>

parent JS, this might look a little messy, everything started from here before I figured it would be better to break these all out to their own components, be gentle:

import { LightningElement, api, wire, track } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import { ShowToastEvent } from 'lightning/platformShowToastEvent'


export default class QuoteWindow extends LightningElement {

    @api recordId;
    @api objectApiName;
    @api fields;
    @api record;
    @api error;
    @api qid;

    @track relatedQuote;
    @track quoteLines;

    // @track record;
    // @track error;

    // @wire(getRecord,
    //     {recordId: '$recordId', fields: FIELDS })
    //     quote;
    // wiredRecord({ data, error }){
    //     if(data){
    //         console.log('record data: ', data);
    //         const name = data.fields.Name.value;
    //         const intro = data.fields.SBQQ__Introduction__c.value;
    //         const paymentTerms = data.fields.SBQQ__PaymentTerms__c.value;
    //         const startDate = data.fields.SBQQ__StartDate__c.value;
    //         const term = data.fields.SBQQ__SubscriptionTerm__c.value;
    //         console.log('recordID: ', record);
    //         console.log('QUOTE NAME: ', name);
    //         console.log('QUOTE INTRO: ', intro);
    //         console.log('QUOTE PAYMENT TERMS: ', paymentTerms);
    //         console.log('QUOTE START DATE: ', startDate);
    //         console.log('QUOTE TERM LENGTH: ', term, ' months');
    //     }
    //     if(error){
    //         console.error('record errors: ', error);
    //     }
    // };

    @wire(getRecord, { recordId: '$recordId', layoutTypes: ['Full'], modes: ['View']})
    wiredQuoteDoc({ data, error }){
        if(data){
            console.log('There is data!');
            this.record = data;
            console.log('record data: ', data);
            const name = data.fields.Name.value;
            let quoteId = data.fields.SBQQ__Quote__c.value;
            // this.relatedQuote = quoteId;
            console.log('Quote Doc Name: ' , name);
            console.log('Quote Doc SBQQ__Quote__c: ' , quoteId);
            qid = quoteId;
            error = undefined;
        }
        if(error){
            console.error('record errors are: ', error);
            data = undefined;
        }
    };

    successToast() {
        const event = new ShowToastEvent({
            title: 'SUCCESS',
            message: 'Docusign checked',
            variant: "success"
        });
        this.dispatchEvent(event);
    }

}

Child LWC containing the Quote record:

<template>
    <lightning-card>
        <div class="slds-m-around_medium bg2 border">
            <table>
                <tr>
                    <td colspan ="2" class="center">The name of this quote is: {name}.</td>
                </tr>
                <tr>
                    <td class="bg">Its start date is: </td>
                    <td><lightning-formatted-date-time value={startDate}></lightning-formatted-date-time>.</td>
                </tr>
                <tr>
                    <td>Its Payment Terms are: </td>
                    <td>{paymentTerms}.</td>
                </tr>
                <tr>
                    <td>Its Subscription Term length is </td>
                    <td>{term} months.</td>
                </tr>
                <tr>
                    <td colspan="2" class="center">{intro}.</td>
                </tr>
            </table>
            <br/>            
        </div>    
    </lightning-card>
</template>

Child Quote LWC JS:

import { LightningElement, api, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import getQuote from '@salesforce/apex/QuoteWindowInfo.getQuote';
import NAME_FIELD from '@salesforce/schema/SBQQ__Quote__c.Name';
import INTRO_FIELD from '@salesforce/schema/SBQQ__Quote__c.SBQQ__Introduction__c';
import PAYMENTTERMS_FIELD from '@salesforce/schema/SBQQ__Quote__c.SBQQ__PaymentTerms__c';
import STARTDATE_FIELD from '@salesforce/schema/SBQQ__Quote__c.SBQQ__StartDate__c';
import TERM_FIELD from '@salesforce/schema/SBQQ__Quote__c.SBQQ__SubscriptionTerm__c';

const FIELDS = [
    NAME_FIELD,
    INTRO_FIELD,
    PAYMENTTERMS_FIELD,
    STARTDATE_FIELD,
    TERM_FIELD
];

export default class QuoteWindowQuote extends LightningElement {

    // @api recordId;
    @api objectApiName;
    @api qid;
    @api record;

    @wire(getRecord, { recordId: '$qid', fields: FIELDS })
    quoterecord({data, error}){
        if(data){
            console.log('getRecord quote-id: ', quoteId);
            console.log('getRecord QUOTE DATA::: ', data);
            this.record = data;
        }
        if(error){
            console.log('we did not get any quote data from getRecord in quoteWindowQuote')
        }
    };

    // @wire(getQuote, { qid: '$quoteId' })
    // quoteTest({ data, error}){
    //     if(data){
    //         console.log('quote-id: ', quoteId);
    //         console.log('QUOTE DATA::: ', data);
    //         this.record = data;
    //     }
    //     if(error){
    //         console.log('failing SPANNING_QUOTE_ID: ', SPANNING_QUOTE_ID);
    //         console.log('failing relatedQuote: ', this.relatedQuote);
    //         console.error('there are errors getting the related quote ', error);
    //     }

    // };

    // get name() {
    //     let name = getFieldValue(this.record.data, NAME_FIELD);
    //     console.log('HTML RELATED QUOTE NAME: ', name);
    //     return name;
    // }
    // get quote() {
    //     let quoteIDD = getFieldValue(this.record.data, Quote_ID);
    //     console.log('QUOTE ID: ', SPANNING_QUOTE_ID);
    //     return quoteIDD;
    // }

    // get intro() {
    //     let intro = getFieldValue(this.quote.data, INTRO_FIELD);
    //     console.log('QUOTE INTRO: ', intro);
    //     return intro;
    // }

    // get paymentTerms() {
    //     let terms =  getFieldValue(this.quote.data, PAYMENTTERMS_FIELD);
    //     console.log('QUOTE PAYMENT TERMS: ', terms);
    //     return terms;
    // }

    // get startDate() {
    //     let startDate =  getFieldValue(this.quote.data, STARTDATE_FIELD);
    //     console.log('QUOTE START DATE: ', startDate);
    //     return startDate;
    // }

    // get term() {
    //     let term =  getFieldValue(this.quote.data, TERM_FIELD);
    //     console.log('QUOTE TERM LENGTH: ', term, ' months');
    //     return term;
    // }

}

Child Quote Lines LWC(commented out currently because it blows everything up):

<template>
    <template if:true={quoteLines.data}>
            <div class="slds-m-around_medium bg2 border">
                <table>
                    <tbody>
                        <template for:each={quoteLines.data} for:item="ql">
                            <tr key={ql}>
                                <td><div class="slds-truncate" key={ql.Id}>${ql.SBQQ__ListTotal__c}</div></td>                    
                                <td><div class="slds-truncate" key={ql.Id}>{ql.SBQQ__ProductName__c}</div></td>                       
                            </tr>
                        </template>
                    </tbody>
                </table>           
            </div>
        </template>
</template>

Child Quote Lines LWC JS:

import { LightningElement, api, wire } from 'lwc';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
import getQuoteLines from '@salesforce/apex/QuoteWindowInfo.getQuoteLines';

export default class QuoteWindowQuoteLines extends LightningElement {

    @api recordId;
    @api objectApiName;
    @api quoteId;

    @wire(getQuoteLines, { quoteId: '$quoteId' })
    quoteLinesGo({ data, error}){
        if(data){
            console.log('there is quote lines data! yay!');
            console.log('quote lines data = ', data);
            this.quoteLines = data;
            error = undefined;
        }
        if(error){            
            console.log('failing quote lines : ', this.quoteLines);
            console.error('there are errors getting the quote lines related to the quote ', error);
            data = undefined;
        }
    };

}

so I will add a footnote here, it is almost like the parent LWC is trying to load the children BEFORE the Quote ID is obtained/passed, I am not really at all familiar with the async/await areas of JS yet(rookie) and am wondering if that could be what is going on here? Almost like I need to just tell my LWC "hey go get these things, THEN try loading it all up". Hopefully somebody is up for (what I think is) a challenge on this fine Sunday, thanks in advance to whoever saves my life!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.