mercoledì 5 settembre 2012

Salesforce Opportunity Product extension

Hi,
 I'm adding this post, to share some of the development we done to fit the Salesforce Opportunity Product into our needs.

First of all please allow me to thank michaelforce (http://www.michaelforce.org/) for his great help, most of the code 
comes from him.
I've just customized it, added a second search/filter method, a 'Select All'


We do not actually 'sell' products, but provide 'services' as a configuration, therefore we do not really use prices / quantities / amounts, but these can be added, and need to :
1. Keep track of the configurations
2. Send out configuration orders to other departments

We had the need to create at least one Visual force page (actually we created more, since we use a number of Opportunity record types)

The page includes a javascript to help with the search.
and actually is made of two parts :
- upper, we named it 'shopping cart'
- lower, the 'product basket'

in the middle we have the search bar, which allows two different type of search, and eventually allows to add in one go all the searched results.






here's the code for the page :


<apex:page standardController="Opportunity" extensions="opportunityProductEntryExtension" sidebar="false" action="{!priceBookCheck}" >

    <apex:sectionHeader Title="Manage {!$ObjectType.Product2.LabelPlural}" subtitle="{!opportunity.Name}"/>
    <apex:messages style="color:red"/>
<apex:outputText value="{!opportunity.RecordType.Name}"/>
        
    <style> 
        .search{
            font-size:14pt;
            margin-right: 20px;    
        }
        .fyi{
            color:red;
            font-style:italic;
        }
        .label{ 
            margin-right:10px;
            font-weight:bold;
        }
    </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 = 1;
        
    
        var countDown = waitTime+1;
        var started = false;
        
        function resetTimer(){
        
            countDown=waitTime+1;
            
            if(started==false){
                started=true;
                runCountDown();
            }
        }
        
        function runCountDown(){
        
            countDown--;
            
            if(countDown<=0){
                fetchResults();
                started=false;
            }
            else{
                window.setTimeout(runCountDown,1000);
            }
        }
    
    </script>
   
  
    <apex:form >
    
        <apex:outputPanel id="mainBody">
        
            <apex:outputLabel styleClass="label">PriceBook: </apex:outputLabel>
            <apex:outputText value="{!theBook.Name}"/>&nbsp;
            <!-- apex:commandLink action="{!changePricebook}" value="change" immediate="true"/-->
            <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>
                    <!--  apex:column width="25px"> 
                    <apex:inputcheckbox value="{!checked}"/>
                    </apex:column-->
                    
                    <apex:column headerValue="{!$ObjectType.Product2.LabelPlural}" value="{!s.PriceBookEntry.Product2.Name}"/>
                    
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Profile__c.Label}" rendered="{!opportunity.Market__c != 'MTS DATA'}">
                                       
                        <apex:inputField value="{!s.Profile_CAT__c}" style="width:110px" required="true" onkeyup="refreshTotals();" rendered="{!opportunity.Market__c = 'MTS Cash Markets'}" />
                        <!-- apex:inputField value="{!s.Profile_CAT__c}" style="width:110px" required="true" onkeyup="refreshTotals();" rendered="{!opportunity.Market__c = 'MTS Live'}" /-->
                        <apex:inputField value="{!s.Profile_MMF__c}" style="width:110px" required="true" onkeyup="refreshTotals();" rendered="{!opportunity.Market__c = 'MTS Repo Markets'}" />
                        <apex:inputField value="{!s.Profile_BV__c}" style="width:110px" required="true" onkeyup="refreshTotals();" rendered="{!opportunity.Market__c = 'MTS Bondvision'}" />    
                        <apex:inputField value="{!s.Profile_ACM__c}" style="width:110px" required="true" onkeyup="refreshTotals();" rendered="{!opportunity.Market__c = 'MTS ACM'}" />    
                     </apex:column>
                    
                      <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Quantity.Label}" rendered="{!opportunity.Market__c = 'MTS DATA'}">
                        <apex:inputField value="{!s.Quantity}" style="width:70px" required="true" onkeyup="refreshTotals();" />
                    </apex:column>

                      <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.UnitPrice.Label}" rendered="{!opportunity.Market__c = 'MTS DATA'}">
                        <apex:inputField value="{!s.UnitPrice}" style="width:70px" required="true" onkeyup="refreshTotals();" />
                    </apex:column>
                                                               
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Description.Label}">
                        <apex:inputField value="{!s.Description}" required="false"/>
                    </apex:column>
                    
                </apex:pageblockTable>
            
            
                <apex:pageBlockButtons >
                    <apex:commandButton action="{!onSave}" value="Save"/>
                    <apex:commandButton action="{!onCancel}" value="Cancel" immediate="true"/>
                    <!-- apex:commandButton action="{!onadd}" value="Add Selected"/-->
                </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" rendered="{!OR(opportunity.Market__c = 'MTS Cash Markets', opportunity.Market__c ='MTS DATA', opportunity.Market__c ='MTS Repo Markets')}"/>
                         <apex:actionFunction name="fetchResults1" action="{!updateAvailableList}" reRender="searchResults" status="searchStatus"/>

                <apex:outputPanel styleClass="search" rendered="{!opportunity.Market__c != 'MTS DATA'}">
                    Sections  
                </apex:outputPanel>

                         
                         <!-- apex:outputlabel value="Sections" for="values" /-->
                         <apex:selectList value="{!Sections}" size="1" id="values" rendered="{!opportunity.Market__c != 'MTS DATA'}" >
                             <apex:actionSupport event="onchange" action="{!updateAvailableList}" reRender="searchResults" />
                             <apex:selectOptions value="{!Sectionnames}"/>     
                          </apex:selectList>
                <apex:outputPanel styleClass="search">

                </apex:outputPanel>
                          
                          <apex:commandButton action="{!addAllToShoppingCart}" value="Add All Selected" reRender="selected,searchResults" immediate="true"/>

                    &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/>
            
                <apex:outputPanel id="searchResults">
                
                    <apex:pageBlockTable value="{!AvailableProducts}" var="a">
                    
                    <!-- apex:column width="25px"> 
                    <apex:inputcheckbox value="{!checked}"/>
                    </apex:column-->
                    
                        <apex:column headerValue="{!$ObjectType.Product2.Fields.Name.Label}" value="{!a.Product2.Name}" />
                        <apex:column headerValue="{!$ObjectType.Product2.Fields.Family.Label}" value="{!a.Product2.Family}"/>
                        <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>


FYI - Sectionames is a way we use to select product baskets, to make search/organization easier. you can actually remove this if it does not fit your needs.


and here is the apex code :


public with sharing class opportunityProductEntryExtension {

    public Opportunity theOpp {get;set;}
    public String searchString {get;set;}
    public String searchstring2 {get;set;} 
    
    public opportunityLineItem[] shoppingCart {get;set;}
    public priceBookEntry[] AvailableProducts {get;set;}
    public Pricebook2 theBook {get;set;}   
    public string recordtypeidname {get;set;}
    public boolean checked {get;set;}

    public String Sections{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, Market__c, Main_Profile__c, RecordType.Id, RecordType.name from Opportunity where Id = :controller.getRecord().Id limit 1];
        
        //assegna il valore del recordtype alla variabile
        recordtypeidname = theOpp.RecordType.name;
        // If products were previously selected need to put them in the "selected products" section to start with
        shoppingCart = [select Id, Product_Family__c, quantity, unitprice, discount, Profile_MMF__c, Profile_ACM__c, Profile_CAT__c, Profile_BV__c, Profile_IC__c, Description, PriceBookEntryId, PriceBookEntry.Name, PriceBookEntry.IsActive, PriceBookEntry.Product2Id, PriceBookEntry.Product2.Name, PriceBookEntry.PriceBook2Id from opportunityLineItem where (OpportunityId=:theOpp.Id) AND (Product_Family__c=:theOpp.Market__c)];
        if (theOpp.Market__c=='MTS Live'){
        shoppingCart = [select Id, Product_Family__c, quantity, unitprice, discount, Profile_MMF__c, Profile_ACM__c, Profile_CAT__c, Profile_BV__c, Profile_IC__c, Description, PriceBookEntryId, PriceBookEntry.Name, PriceBookEntry.IsActive, PriceBookEntry.Product2Id, PriceBookEntry.Product2.Name, PriceBookEntry.PriceBook2Id 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, Pricebook2Id, IsActive, Product2.Name, Product2.Profile__c, Product2.Family, Product2.IsActive, Product2.Description, UnitPrice from PricebookEntry where IsActive=true and Pricebook2Id = \'' + theBook.Id + '\'';
        
        String searchstring3 = Sections;
        if(multipleCurrencies)
            qstring += ' and CurrencyIsoCode = \'' + theOpp.get('currencyIsoCode') + '\'';
        
        if(recordtypeidname!=null){
           if (theOpp.Market__c=='MTS Live'){
                      qString+= 'and (Product2.Family = \'' + 'MTS Cash Markets' + '\')' ;
                      qString+= 'and (Product2.MTS_Live_Enabled__c = TRUE)' ;
                      
                      }else{
           qString+= 'and (Product2.Family = \'' + theOpp.Market__c + '\')' ;
           }
        }
        // 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 + '%\')';
        }
        if(searchString3!=null) if (searchString3!='All'){
            qString+= ' and (Product2.Name like \'%' + searchString3 + '%\' or Product2.Description like \'%' + searchString3 + '%\')';
        }
        
        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
    
        for(PricebookEntry d : AvailableProducts){
            if((String)d.Id==toSelect){
                shoppingCart.add(new opportunityLineItem(OpportunityId=theOpp.Id, PriceBookEntry=d, unitprice=d.unitprice, Product_Family__c=d.Product2.family, Profile_ACM__c=theOpp.Main_Profile__c, Profile_CAT__c=theOpp.Main_Profile__c, Profile_BV__c=theOpp.Main_Profile__c, Profile_MMF__c=theOpp.Main_Profile__c, PriceBookEntryId=d.Id));
                break;
            }
        }
        
        updateAvailableList();  
    }
    
    public void addAllToShoppingCart(){
    
        // This function runs when a user hits "select" button next to a product
    
        for(PricebookEntry d : AvailableProducts){
            shoppingCart.add(new opportunityLineItem(OpportunityId=theOpp.Id, PriceBookEntry=d, Profile_ACM__c=theOpp.Main_Profile__c, Profile_CAT__c=theOpp.Main_Profile__c, Product_Family__c=d.Product2.family, Profile_BV__c=theOpp.Main_Profile__c, Profile_MMF__c=theOpp.Main_Profile__c, PriceBookEntryId=d.Id));
           }
        
        updateAvailableList();  
    }
    

    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(){
    
    theOpp.ChangedLineItems__c=true;
    
        // If previously selected products are now removed, we need to delete them
        if(forDeletion.size()>0)
            delete(forDeletion);
            upsert(theOpp);
    
        // 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);
                upsert(theOpp);
        }
        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;
    }
    
    public List<SelectOption> getsectionnames(){ 
         List<SelectOption> options = new List<SelectOption>();
        List<Sections_Picklist__c> Sectionlist = new List<Sections_Picklist__c>();  
        Sectionlist = [Select Id, PicklistValue__c FROM Sections_Picklist__c WHERE family__c =: theOpp.Market__c ORDER BY name];
          for (Integer j=0;j<sectionlist.size();j++)  { 
                 options.add(new SelectOption(sectionlist[j].PicklistValue__c,sectionlist[j].PicklistValue__c));  
                 }
           return options;
      }
    
}



further to this you'll need a redirect page when pressing the add product button on the opportunity page :


<apex:page standardController="opportunityLineItem" extensions="opportunityProductRedirectExtension" action="{!redirect}">
</apex:page>


and the relative apexcode for the redirect :


public with sharing class opportunityProductRedirectExtension {

    Id oppId;

    // we are extending the OpportunityLineItem controller, so we query to get the parent OpportunityId
    public opportunityProductRedirectExtension(ApexPages.StandardController controller) {
        oppId = [select Id, OpportunityId from OpportunityLineItem where Id = :controller.getRecord().Id limit 1].OpportunityId;
    }
    
    // then we redirect to our desired page with the Opportunity Id in the URL
    public pageReference redirect(){
        return new PageReference('/apex/opportunityProductEntry?id=' + oppId);
    }

}



Finally, as requested by Salesforce, you'll need a testclass for the two apex classes (I've made a single apex test class to test both :


@istest
private class opportunityProductEntryTests {

    static testMethod void theTests(){

        TestUtilities tu = TestUtilities.generateTest();
// this is an external class that will generate a number of records I will query below.
// I suggest creating a TestUtility class to be called by your test class instead of creating the 
// records in each test class.
// This to save time, code and providing a clear code organization.
            
        OpportunityLineItem oli = [select Id, PricebookEntryId, PricebookEntry.Pricebook2Id, PricebookEntry.Name, PriceBookEntry.Product2Id, OpportunityId, Opportunity.AccountId from OpportunityLineItem where Description='This is a test' limit 1];
        system.debug('value dell id :' + oli.id);      
                
        ////////////////////////////////////////
        //  test opportunityProductEntry
        ////////////////////////////////////////
        
        // load the page       
        PageReference pageRef = Page.opportunityProductEntry;
        pageRef.getParameters().put('Id',oli.OpportunityId);
        Test.setCurrentPageReference(pageRef);
        
        // load the extension
        opportunityProductEntryExtension oPEE = new opportunityProductEntryExtension(new ApexPages.StandardController(oli.Opportunity));
        
        // test 'getChosenCurrency' method
        if(UserInfo.isMultiCurrencyOrganization())
            System.assert(oPEE.getChosenCurrency()!='');
        else
            System.assertEquals(oPEE.getChosenCurrency(),'');

        // we know that there is at least one line item, so we confirm
        Integer startCount = oPEE.ShoppingCart.size();
        system.assert(startCount>0);

        //test search functionality without finding anything
        oPEE.searchString = 'michaelforce is a hip cat';
        oPEE.updateAvailableList();
        system.assert(oPEE.AvailableProducts.size()==0);
        
        //test remove from shopping cart
        oPEE.toUnselect = oli.PricebookEntryId;
        oPEE.removeFromShoppingCart();
        system.assert(oPEE.shoppingCart.size()==startCount-1);
        
        //test save and reload extension
        oPEE.onSave();
        oPEE = new opportunityProductEntryExtension(new ApexPages.StandardController(oli.Opportunity));
        system.assert(oPEE.shoppingCart.size()==startCount-1);
      
        // test search again, this time we will find something
        oPEE.searchString = oli.PricebookEntry.Name;
        oPEE.updateAvailableList();
        system.assert(oPEE.AvailableProducts.size()>0);

        // test add to Shopping Cart function
        oPEE.toSelect = oPEE.AvailableProducts[0].Id;
        oPEE.addToShoppingCart();
        system.assert(oPEE.shoppingCart.size()==startCount);

        // add required info and try save again
        for(OpportunityLineItem o : oPEE.ShoppingCart){
            o.quantity = 5;
            o.unitprice = 300;
        }
        oPEE.onSave();
        
        // query line items to confirm that the save worked
        opportunityLineItem[] oli2 = [select Id from opportunityLineItem where OpportunityId = :oli.OpportunityId];
        system.assert(oli2.size()==startCount);

        // test search Sections BELGIUM
        oPEE.searchString = '';
        oPEE.updateAvailableList();
        oPEE.Sections = 'MTS BELGIUM';
        oPEE.updateAvailableList();
        system.assert(oPEE.AvailableProducts.size()>0);

        // test here the add all function
        oPEE.addAllToShoppingCart();
        system.assert(oPEE.shoppingCart.size()> 0);
         // ********************      
        
        // test search Sections BELGIUM
        oPEE.Sections = 'All';
        oPEE.updateAvailableList();
        system.assert(oPEE.AvailableProducts.size()>0);

        // load the section names
        oPEE.getsectionnames();
        
        // test on new Opp (no pricebook selected) to make sure redirect is happening
        Opportunity newOpp = new Opportunity(Name='New Opp',stageName='Pipeline',Amount=10,Type='Enable - Expanding business', Market__c='MTS Repo Markets',closeDate=System.Today()+30,AccountId=oli.Opportunity.AccountId);
        insert(newOpp);
        oPEE = new opportunityProductEntryExtension(new ApexPages.StandardController(newOpp));
        oPEE.priceBookCheck();
        //System.assert(oPEE.priceBookCheck()!=null);
        
        // final quick check of cancel button
        System.assert(oPEE.onCancel()!=null);
        
        
        ////////////////////////////////////////
        //  test redirect page
        ////////////////////////////////////////
        
        // load the page
        pageRef = Page.opportunityProductRedirect;
        pageRef.getParameters().put('Id',oli2[0].Id);
        Test.setCurrentPageReference(pageRef);

        // load the extension and confirm that redirect function returns something
        opportunityProductRedirectExtension oPRE = new opportunityProductRedirectExtension(new ApexPages.StandardController(oli2[0]));
        System.assert(oPRE.redirect()!=null);
     
    }
}


Hope this can be of help.

If anything not clear, and I'm afraid I'm not a good teacher. Please feel free to ask.

ciaociao,
Alex

4 commenti:

  1. Alex, if you are going to copy my code and post it in your blog you should give credit. -michaelforce

    RispondiElimina
    Risposte
    1. Thanks for reminding me the source. I actually just posted this to help a friend and did not recall the initial source. As you can see I'm not very active on this blog and again just forgot to even have it.
      I've added the credits on the top of the page, hope to have put this right, now.
      Thanks,
      Alex

      Elimina
    2. sweet! thanks for that. glad the code is helpful.

      Elimina