function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Gabe Rothman 8Gabe Rothman 8 

Possible to use apex:actionSupport or Javascript to update values of one Opportunity Line Item with the values from another?

Hey folks, I have a somewhat complicated use-case I'm trying to solve.  I have a visualforce page that we use to configure products. What I'm trying to do is derive the quantity of one particular type of product based on the quantity or quantities of another type of products and I want to do this within the browser and then insert the derived value into the database on Save. Specifically we have a product called "MTP" and another generic "Support" product.  Whenever MTP product quantities are added or updated to the product "shopping-cart" visualforce page, I want ONLY the total of all MTP line-items to be automatically populated in the quantity field of the support product. See my example below:

User-added image
Here's my current code:
Controller:

public with sharing class opportunityProductEntryExtension {

    public Opportunity theOpp {get;set;} 
    public String searchString {get;set;}
    public opportunityLineItem[] shoppingCart {get;set;}
    public priceBookEntry[] AvailableProducts {get;set;}
    public Pricebook2 theBook {get;set;}   
    public String toSelect {get; set;}
    public String toUnselect {get; set;}
    public Decimal Total {get;set;}
    public Boolean overLimit {get;set;}
    public Boolean multipleCurrencies {get; set;} 
    private Boolean forcePricebookSelection = false;
    
    private opportunityLineItem[] forDeletion = new opportunityLineItem[]{};


    public opportunityProductEntryExtension(ApexPages.StandardController controller) {

        // Need to know if org has multiple currencies enabled
        multipleCurrencies = UserInfo.isMultiCurrencyOrganization();

        // Get information about the Opportunity being worked on
        if(multipleCurrencies)
            theOpp = database.query('select Id, Pricebook2Id, Pricebook2.Name, CurrencyIsoCode from Opportunity where Id = \'' + controller.getRecord().Id + '\' limit 1');
        else
            theOpp = [select Id, Pricebook2Id, PriceBook2.Name from Opportunity where Id = :controller.getRecord().Id limit 1];
        
        // If products were previously selected need to put them in the "selected products" section to start with
        shoppingCart = [select Id, Quantity, TotalPrice, UnitPrice, Description, List_Price__c, Disti_Transfer_Price2__c, Disti_Transfer_Price__c, PriceBookEntryId, PriceBookEntry.Name, PriceBookEntry.UnitPrice, PriceBookEntry.IsActive, PriceBookEntry.Product2Id, PriceBookEntry.Product2.Name, PriceBookEntry.Product2.Product_Term_Years__c, PriceBookEntry.PriceBook2Id, PriceBookEntry.Product2.SKU__c, PriceBookEntry.Product2.Family, PriceBookEntry.Product2.Type__c, Service_Term_in_Months__c, PriceBookEntry.Disti_Transfer_Price__c, Discount_off_list_manual__c from opportunityLineItem where OpportunityId=:theOpp.Id];

        // Check if Opp has a pricebook associated yet
        if(theOpp.Pricebook2Id == null){
            Pricebook2[] activepbs = [select Id, Name from Pricebook2 where isActive = true limit 2];
            if(activepbs.size() == 2){
                forcePricebookSelection = true;
                theBook = new Pricebook2();
            }
            else{
                theBook = activepbs[0];
            }
        }
        else{
            theBook = theOpp.Pricebook2;
        }
        
        if(!forcePricebookSelection)
            updateAvailableList();
    }
    
    // this is the 'action' method on the page
    public PageReference priceBookCheck(){
    
        // if the user needs to select a pricebook before we proceed we send them to standard pricebook selection screen
        if(forcePricebookSelection){        
            return changePricebook();
        }
        else{
        
            //if there is only one active pricebook we go with it and save the opp
            if(theOpp.pricebook2Id != theBook.Id){
                try{
                    theOpp.Pricebook2Id = theBook.Id;
                    update(theOpp);
                }
                catch(Exception e){
                    ApexPages.addMessages(e);
                }
            }
            
            return null;
        }
    }
       
    public String getChosenCurrency(){
    
        if(multipleCurrencies)
            return (String)theOpp.get('CurrencyIsoCode');
        else
            return '';
    }

    public void updateAvailableList() {
    
        // We dynamically build a query string and exclude items already in the shopping cart
        String qString = 'select Id, Name, Pricebook2.Name, Pricebook2Id, IsActive, Product2.Name, Product2.Family, Product2.Type__c, Product2.IsActive, Product2.Description, UnitPrice, Product2.SKU__c, Disti_Transfer_Price__c, Product2.Product_Term_Years__c, Product2.SKU_Level_Discount__c from PricebookEntry where IsActive=true and Pricebook2Id = \'' + theBook.Id + '\'';
        if(multipleCurrencies)
            qstring += ' and CurrencyIsoCode = \'' + theOpp.get('currencyIsoCode') + '\'';
        
        // note that we are looking for the search string entered by the user in the name OR description
        // modify this to search other fields if desired
        if(searchString!=null){
            qString+= ' and (Product2.Name like \'%' + searchString + '%\' or Product2.Description like \'%' + searchString + '%\')';
        }
        
        Set<Id> selectedEntries = new Set<Id>();
        for(opportunityLineItem d:shoppingCart){
            selectedEntries.add(d.PricebookEntryId);
        }
        
        if(selectedEntries.size()>0){
            String tempFilter = ' and Id not in (';
            for(Id i : selectedEntries){
                tempFilter+= '\'' + (String)i + '\',';
            }
            String extraFilter = tempFilter.substring(0,tempFilter.length()-1);
            extraFilter+= ')';
            
            qString+= extraFilter;
        }
        
        qString+= ' order by Product2.Name';
        qString+= ' limit 101';
        
        system.debug('qString:' +qString);        
        AvailableProducts = database.query(qString);
        
        // We only display up to 100 results... if there are more than we let the user know (see vf page)
        if(AvailableProducts.size()==101){
            AvailableProducts.remove(100);
            overLimit = true;
        }
        else{
            overLimit=false;
        }
    }
    
    public void addToShoppingCart(){
    
        // This function runs when a user hits "select" button next to a product
        list<Price_Book_Discounts__c> discounts = [SELECT Discount__c, Name
                                                   FROM Price_Book_Discounts__c 
                                                   WHERE Name =: theBook.Name];
        list<SKU_Discounts__c> skus = [SELECT Discount__c, SKU__c, Price_Book_Name__c
                                           FROM SKU_Discounts__c 
                                           WHERE Price_Book_Name__c =: theBook.Name];                
        Map<String, Decimal> pbNameToDiscountMap =  new Map<String, Decimal>();
		Map<String, Decimal> SkuToDiscountMap = new Map<String, Decimal>();
        for(Price_Book_Discounts__c discount : discounts){
            pbNameToDiscountMap.put(discount.Name, discount.Discount__c);
        }
        for(SKU_Discounts__c sku : skus){
            if (!SkuToDiscountMap.containsKey(sku.SKU__c)) {
                SkuToDiscountMap.put(sku.SKU__c, sku.Discount__c);
            } 
        } 
        for(PricebookEntry d : AvailableProducts){            
            if((String)d.Id==toSelect && d.Product2.Type__c == 'Support'){
                shoppingCart.add(new opportunityLineItem(OpportunityId = theOpp.Id, 
                                                        PriceBookEntry = d, 
                                                        PriceBookEntryId = d.Id
                                                        ));
                break;
            }
            if((String)d.Id==toSelect && d.Product2.Type__c == 'Non-Revenue'){
                shoppingCart.add(new opportunityLineItem(OpportunityId = theOpp.Id, 
                                                        PriceBookEntry = d, 
                                                        PriceBookEntryId = d.Id,
                                                        Service_Term_in_Months__c = 2,
                                                        Quantity = 100
                                                        ));
                break;
            }
            if((String)d.Id==toSelect && d.Product2.Type__c == 'Subscription'){
                if(SkuToDiscountMap.get(d.Product2.SKU__c)!=null){
                    shoppingCart.add(new opportunityLineItem(OpportunityId=theOpp.Id, PriceBookEntry=d, PriceBookEntryId=d.Id, UnitPrice=d.Disti_Transfer_Price__c, Disti_Transfer_Price2__c = d.Disti_Transfer_Price__c, List_Price__c = d.UnitPrice, Discount_off_list_manual__c = SkuToDiscountMap.get(d.Product2.SKU__c)));
                    break;
                }
                if(SkuToDiscountMap.get(d.Product2.SKU__c)==null){
                    shoppingCart.add(new opportunityLineItem(OpportunityId=theOpp.Id, PriceBookEntry=d, PriceBookEntryId=d.Id, UnitPrice=d.Disti_Transfer_Price__c, Disti_Transfer_Price2__c = d.Disti_Transfer_Price__c, List_Price__c = d.UnitPrice, Discount_off_list_manual__c = pbNameToDiscountMap.get(theBook.Name)));
                    break;
                }
            }            
        }
        
        updateAvailableList();  
    }  

    public PageReference calculateSupport(){
        List<Id> parents = new list<Id>();
        for(opportunityLineItem d : shoppingCart){
            parents.add(d.OpportunityId);
        }
        List<OpportunityLineItem> olis = [SELECT Quantity, PriceBookEntry.Product2.Family FROM OpportunityLineItem WHERE Id in: parents AND PriceBookEntry.Product2.Family =: 'MTP'];
        double suppQuant = 0;    
        for(OpportunityLineItem mtpOli : olis){
            suppQuant += mtpOli.Quantity;
        }             
        for(opportunityLineItem d : shoppingCart){
            if(d.PriceBookEntry.Product2.Type__c == 'Support'){
                d.Quantity = suppQuant;
            }else{
                d.Quantity = d.Quantity;
            }
        }
        
        return null;
    }

    public PageReference calculateSalesPrice(){
        
        for(opportunityLineItem d : shoppingCart){
            decimal price = d.PriceBookEntry.UnitPrice - (d.PriceBookEntry.UnitPrice * (d.Discount_off_list_manual__c/100));
            d.UnitPrice = price.setscale(2);
        }
        
        return null;
    }    

    public PageReference calculateDiscount(){
        
        for(opportunityLineItem d : shoppingCart){
            decimal discount = ((d.PriceBookEntry.UnitPrice - d.UnitPrice)/d.PriceBookEntry.UnitPrice)*100;            
            d.Discount_off_list_manual__c = discount.setscale(2);
        }
        
        return null;
    }        

    public PageReference removeFromShoppingCart(){
    
        // This function runs when a user hits "remove" on an item in the "Selected Products" section
    
        Integer count = 0;
    
        for(opportunityLineItem d : shoppingCart){
            if((String)d.PriceBookEntryId==toUnselect){
            
                if(d.Id!=null)
                    forDeletion.add(d);
            
                shoppingCart.remove(count);
                break;
            }
            count++;
        }
        
        updateAvailableList();
        
        return null;
    }
    
    public PageReference onSave(){
    
        // If previously selected products are now removed, we need to delete them
        if(forDeletion.size()>0)
            delete(forDeletion);
    
        // Previously selected products may have new quantities and amounts, and we may have new products listed, so we use upsert here
        try{
            if(shoppingCart.size()>0)
                upsert(shoppingCart);
        }
        catch(Exception e){
            ApexPages.addMessages(e);
            return null;
        }  
           
        // After save return the user to the Opportunity
        return new PageReference('/' + ApexPages.currentPage().getParameters().get('Id'));
    }
    
    public PageReference onCancel(){
 
        // If user hits cancel we commit no changes and return them to the Opportunity   
        return new PageReference('/' + ApexPages.currentPage().getParameters().get('Id'));
    }
    
    public PageReference changePricebook(){
    
        // This simply returns a PageReference to the standard Pricebook selection screen
        // Note that is uses retURL parameter to make sure the user is sent back after they choose
    
        PageReference ref = new PageReference('/oppitm/choosepricebook.jsp');
        ref.getParameters().put('id',theOpp.Id);
        ref.getParameters().put('retURL','/apex/opportunityProductEntry?id=' + theOpp.Id);
        
        return ref;
    }
}
Visualforce Page:
<apex:page standardController="Opportunity" extensions="opportunityProductEntryExtension" action="{!priceBookCheck}" >

    <apex:sectionHeader Title="Manage {!$ObjectType.Product2.LabelPlural}" subtitle="{!opportunity.Name}"/>
    <apex:messages style="color:red"/>

    <style>
        .search{
            font-size:14pt;
            margin-right: 20px;    
        }
        .fyi{
            color:red;
            font-style:italic;
        }
        .label{
            margin-right:10px;
            font-weight:bold;
            vertical-align: text-bottom;
        }
       .pbTitle {
            white-space: nowrap;
            
        }            

    </style>
    
    <script type='text/javascript'>
    
        // This script assists the search bar functionality
        // It will execute a search only after the user has stopped typing for more than 1 second
        // To raise the time between when the user stops typing and the search, edit the following variable:
        
        var waitTime = 0.1;
        
    
        var countDown = waitTime+1;
        var started = false;
        
        function resetTimer(){
        
            countDown=waitTime+0.1;
            
            if(started==false){
                started=true;
                runCountDown();
            }
        }
        
        function runCountDown(){
        
            countDown--;
            
            if(countDown<=0){
                fetchResults();
                started=false;
            }
            else{
                window.setTimeout(runCountDown,1000);
            }
        }    
    </script>
   
  
    <apex:form >
        <div id="InternalDiv" styleClass="pbTitle" style="width:600px; float:right; margin-top:-60px; font-size:9px;">     

         </div>
             <apex:outputPanel id="mainBody">
                <div id="pbName" styleClass="pbName" style="margin-top:0px;">
                    <apex:outputLabel styleClass="label">PriceBook: </apex:outputLabel>
                    <apex:outputText value="{!theBook.Name}"/>&nbsp;
                    <apex:commandLink action="{!changePricebook}" value="change" immediate="true"/>
                </div>                  
                    <br/>
                    <!-- not everyone is using multi-currency, so this section may or may not show -->
                    <apex:outputPanel rendered="{!multipleCurrencies}">
                        <apex:outputLabel styleClass="label">Currency: </apex:outputLabel>
                        <apex:outputText value="{!chosenCurrency}"/>
                        <br/>
                    </apex:outputPanel>
                <br/>
         
            
<!-- this is the upper table... a.k.a. the "Shopping Cart"-->

            <!-- notice we use a lot of $ObjectType merge fields... I did that because if you have changed the labels of fields or objects it will reflect your own lingo -->
            <apex:pageBlock title="Selected {!$ObjectType.Product2.LabelPlural}" id="selected">
                       
                <apex:pageblockTable value="{!shoppingCart}" var="s">
                    
                    <apex:column >
                        <apex:commandLink value="Remove" action="{!removeFromShoppingCart}" reRender="selected,searchResults" immediate="true">
                            <!-- this param is how we send an argument to the controller, so it knows which row we clicked 'remove' on -->
                            <apex:param value="{!s.PriceBookEntryId}" assignTo="{!toUnselect}" name="toUnselect"/>
                        </apex:commandLink>
                    </apex:column>

                    <!--********** Product Name **********-->
                    <apex:column headerValue="{!$ObjectType.Product2.LabelPlural}" value="{!s.PriceBookEntry.Product2.Name}"/>

                    <!--********** Product SKU **********-->
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.SKU__c.Label}" value="{!s.PriceBookEntry.Product2.SKU__c}"/> 

                    <!--********** Annual Product Term **********-->
                    <apex:column headerValue="Annual Term" id="pt" rendered="false" value="{!s.PriceBookEntry.Product2.Product_Term_Years__c}"/>                         
                    
                    <!--********** Quantity **********-->
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Quantity.Label}">
                        <apex:inputField value="{!s.Quantity}" id="quant" style="width:70px" required="true" rendered="{!IF(s.PriceBookEntry.Product2.Type__c == 'Support', false, true)}">
                            <apex:actionSupport event="onkeyup" reRender="tot"/>
                            <apex:actionSupport event="onchange" action="{!calculateSupport}"/>
                        </apex:inputField>
                        <apex:outputText id="quants" value="{0, number, ###,###,###,##0.00}" rendered="{!IF(s.PriceBookEntry.Product2.Type__c == 'Support', true, false)}">
                            <apex:param value="{!s.Quantity}"/>
                        </apex:outputText> 
                    </apex:column>
                  

                    <!--********** Service Term in Months **********-->
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Service_Term_in_Months__c.Label}">
                        <apex:inputField value="{!s.Service_Term_in_Months__c}" id="svc" style="width:70px" required="true" rendered="{!IF(s.PriceBookEntry.Product2.Type__c == 'Support', false, true)}">
                            <apex:actionSupport event="onkeyup" reRender="tot"/>
                        </apex:inputField>
                        <apex:outputText id="svcs" value="${0, number, ###,###,###,##0.00}" rendered="{!IF(s.PriceBookEntry.Product2.Type__c == 'Support', true, false)}">
                            <apex:param value="{!s.PriceBookEntry.UnitPrice}"/>
                        </apex:outputText> 
                    </apex:column>                                        

                    <!--********** Discount (Manual) **********-->
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Discount_off_list_manual__c.Label}">
                        <apex:inputField value="{!s.Discount_off_list_manual__c}" id="disc" style="width:70px" required="True" rendered="{!IF(s.PriceBookEntry.Product2.Type__c == 'Support', false, true)}">
                            <apex:actionSupport event="onchange" reRender="tot"/>
                             <apex:actionSupport event="onkeyup" action="{!calculateSalesPrice}"/>
                        </apex:inputField>
                        <apex:outputText id="discs" value="${0, number, ###,###,###,##0.00}" rendered="{!IF(s.PriceBookEntry.Product2.Type__c == 'Support', true, false)}">
                            <apex:param value="{!s.PriceBookEntry.UnitPrice}"/>
                        </apex:outputText> 
                    </apex:column>                    
                    
                    <!--********** Sales Price **********-->
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.UnitPrice.Label}">
                        <apex:inputField value="{!s.UnitPrice}" id="sp" style="width:70px" required="true" rendered="{!IF(s.PriceBookEntry.Product2.Type__c == 'Support', false, true)}">
                            <apex:actionSupport event="onchange" reRender="tot"/>
                            <apex:actionSupport event="onkeyup" action="{!calculateDiscount}"/>
                        </apex:inputField>                    
                        <apex:outputText id="sps" value="${0, number, ###,###,###,##0.00}" rendered="{!IF(s.PriceBookEntry.Product2.Type__c == 'Support', true, false)}">
                            <apex:param value="{!s.PriceBookEntry.UnitPrice}"/>
                        </apex:outputText> 
                    </apex:column>                    

                    <!--********** Disti Transfer Price (Custom) **********-->
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Disti_Transfer_Price2__c.Label}">
                        <apex:outputText id="dpc" value="${0, number, ###,###,###,##0.00}">
                            <apex:param value="{!s.PriceBookEntry.Disti_Transfer_Price__c}"/>
                        </apex:outputText> 
                    </apex:column>

                    <!--********** List Price (Custom) **********-->
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.List_Price__c.Label}">
                        <apex:outputText id="lpc" value="${0, number, ###,###,###,##0.00}">
                            <apex:param value="{!s.PriceBookEntry.UnitPrice}"/>
                        </apex:outputText> 
                    </apex:column>

                    <!--********** Disti Transfer Price (Standard) Hidden **********-->
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Disti_Transfer_Price__c.Label}" value="{!s.PriceBookEntry.Disti_Transfer_Price__c}" id="dp" rendered="false"/>

                    <!--********** List Price (Standard) Hidden **********-->
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.ListPrice.Label}" id="lp" value="{!s.PriceBookEntry.UnitPrice}" rendered="false"/>
                    
                    <!--********** Total Price (display only field) **********-->
                    <apex:column headerValue="Total Price">
                        <apex:outputText id="tot" value="${0, number, ###,###,###,##0.00}">
                            <apex:param value="{!IF(s.PriceBookEntry.Product2.Product_Term_Years__c != null,(s.Quantity * s.Service_Term_in_Months__c * s.UnitPrice)/(s.PriceBookEntry.Product2.Product_Term_Years__c*12),(s.Quantity * s.UnitPrice)) }"/>
                        </apex:outputText> 
                    </apex:column>                                   
                    
                </apex:pageblockTable>
            
            
                <apex:pageBlockButtons >
                    <apex:commandButton action="{!onSave}" value="Save"/>
                    <apex:commandButton action="{!onCancel}" value="Cancel" immediate="true"/>
                </apex:pageBlockButtons>
            
            </apex:pageBlock>
    
<!-- this is the lower table: search bar and search results -->
    
            <apex:pageBlock >
            
                <apex:outputPanel styleClass="search">
                    Search for {!$ObjectType.Product2.LabelPlural}:
                </apex:outputPanel>

                <apex:actionRegion renderRegionOnly="false" immediate="true">
                
                    <apex:actionFunction name="fetchResults" action="{!updateAvailableList}" reRender="searchResults" status="searchStatus"/>
                    
                    <!-- here we invoke the scripting to get out fancy 'no button' search bar to work -->
                    <apex:inputText value="{!searchString}" onkeydown="if(event.keyCode==13){this.blur();}else{resetTimer();}" style="width:300px"/>
                    &nbsp;&nbsp;
                    <i>
                        <!-- actionStatus component makes it easy to let the user know when a search is underway -->
                        <apex:actionStatus id="searchStatus" startText="searching..." stopText=" "/>
                    </i>
                    
                </apex:actionRegion>
            
                <br/>
                <br/>
            
                <apex:outputPanel id="searchResults">
                
                    <apex:pageBlockTable value="{!AvailableProducts}" var="a">
                    
                        <apex:column headerValue="{!$ObjectType.Product2.Fields.Name.Label}" value="{!a.Product2.Name}" />
                        
                        <apex:column headerValue="{!$ObjectType.Product2.Fields.SKU__c.Label}" value="{!a.Product2.SKU__c}"/>
                        
                        <apex:column headerValue="{!$ObjectType.Product2.Fields.Description.Label}" value="{!a.Product2.Description}"/>
                        
                        <apex:column >
                            <!-- command button in a column... neato -->
                            <apex:commandButton value="Select" action="{!addToShoppingCart}" reRender="selected,searchResults" immediate="true">
                                <!-- again we use apex:param to be able to tell the controller which row we are working with -->
                                <apex:param value="{!a.Id}" assignTo="{!toSelect}" name="toSelect"/>
                            </apex:commandButton>
                        </apex:column>
                        
                    </apex:pageBlockTable>
                    
                    <!-- We put up a warning if results exceed 100 rows -->
                    <apex:outputPanel styleClass="fyi" rendered="{!overLimit}">
                        <br/>
                        Your search returned over 100 results, use a more specific search string if you do not see the desired {!$ObjectType.Product2.Label}.
                        <br/>
                    </apex:outputPanel>
                    
                </apex:outputPanel>
            
            </apex:pageBlock>
            
        </apex:outputPanel>

    </apex:form>

</apex:page>

 
Gabe Rothman 8Gabe Rothman 8
Bump -- Anyone have any ideas?