0

My organization would like to show a related list of all of the Products associated with closed won Opportunities linked to a given Account at the Account level. Essentially if Product X is linked to Opportunity Y, and Opp Y is linked to Account Z, we'd like a related list at the Account level displaying Product X. I've already created embedded charts for this, but the team making the request is insistent that they get this in the form of line items. I know that it should be possible to code a Visualforce Page Related List for this, but I don't even know where to start with coding that. Can anyone please help?

Thanks.

1
  • Are duplicates okay, or not? If duplicates are allowed, just create a custom lookup field from the opportunity line item object to the account directly, and populate it with a trigger. No Visualforce, just a little bit of extra coding.
    – sfdcfox
    Mar 18, 2016 at 14:44

3 Answers 3

2

If you add a custom Account__c lookup on your Opportunity Product, you can implement this entire solution without using any Apex. There are four discrete steps, described in more detail below.

  1. Create the lookup.
  2. Add the Related List to your Page Layout(s).
  3. Update child OpportunityLineItem records when an Opportunity changes its StageName.
    • If it becomes Closed Won, set the lookups.
    • If it is no longer Closed Won, clear the lookups.
  4. If an OpportunityLineItem is added to an Opportunity that is already Closed Won, set the lookup.

Lookup

Add a lookup to the Opportunity Product object. Make sure to set the child relationship name to ClosedWonProducts.


Related List

You can actually just use an OOB Related List in the Page Layout. If you want, though, you can still do it via Visualforce. Just remember to KISS:

<apex:page standardController="Account">
    <apex:relatedList list="ClosedWonProducts">
</apex:page>

Opportunity Trigger

Actually syncing the lookup is a little bit more work, but nothing too crazy. Don't be intimidated by the number of steps below, I got very specific. It should take you long to use Process Builder to create a flow as follows:

  1. Navigate to Create > Workflows & Approvals > Process Builder.

  2. Click New.
    Fill in Process Name > Closed Won Opportunity.
    No need to fill in API Name, it will auto-populate.
    You may fill in an optional Description.
    Click Save.

  3. Click + Add Object.
    Select Opportunity.
    Select Start the process > when a record is created or edited.
    Click Save.

  4. Click + Add Criteria.
    Fill in Criteria Name > Enter Closed Won.
    Select Conditions are met.

    Select Field > Stage.
    Select Operator > Is Changed.
    Select Value > True.

    Select Field > Stage.
    Select Operator > Equals.
    Select Value > Closed Won.

    Click Save.

  5. Click + Add Action.
    Select Action Type > Update Records.
    Fill in Action Name > Set Product Accounts.

    Choose Select a record related to the Opportunity.
    Select OpportunityLineItems.

    Select Field > Account.
    Select Type > Reference.
    Select Value > Opportunity ID > > AccountId.

    Click Save.

  6. Click + Add Criteria.
    Fill in Criteria Name > Leave Closed Won.
    Select Conditions are met.

    Select Field > Stage.
    Select Operator > Is Changed.
    Select Value > True.

    Select Field > Stage.
    Select Operator > Does not equal.
    Select Value > Closed Won.

    Click Save.

  7. Click + Add Action.
    Select Action Type > Update Records.
    Fill in Action Name > Unset Product Accounts.

    Choose Select a record related to the Opportunity.
    Select OpportunityLineItems.

    Select Field > Account.
    Select Type > ID.
    Select Value > 000000000000000. (See: Setting lookup field to blank using Process Builder)

    Click Save.

  8. Click Activate.


Opportunity Product Trigger

One last use case, someone might add an OpportunityLineItem to an Opportunity that is already Closed Won.

  1. Navigate to Create > Workflows & Approvals > Process Builder.

  2. Click New.
    Fill in Process Name > Closed Won Product.
    No need to fill in API Name, it will auto-populate.
    You may fill in an optional Description.
    Click Save.

  3. Click + Add Object.
    Select Opportunity Product.
    Leave Start the process > only when a record is created.
    Click Save.

  4. Click + Add Criteria.
    Fill in Criteria Name > Is Closed Won.
    Select Conditions are met.

    Select Field > Opportunity ID > > Stage.
    Select Operator > Equals.
    Select Value > Closed Won.

  5. Click + Add Action.
    Select Action Type > Update Records.
    Fill in Action Name > Set Account.

    Choose Select the OpportunityLineItem record that started your process.
    Select Field > Account.
    Select Type > Reference.
    Select Value > Opportunity ID > > AccountId.

    Click Save.

  6. Click Activate.

2
  • Thank you! I made a couple of small modifications, but it seems to be working.
    – C. Malloy
    Mar 21, 2016 at 16:14
  • Nice! Glad I could help. Clicks not code! @C.Malloy
    – Adrian Larson
    Mar 21, 2016 at 16:17
0

Create a visualforce page and add it to your page layouts

I haven't tested the below code, so you might need to do some adjustments :)

VisualForce Page:

<apex:page standardController="Account" extensions="ProductsListController" showHeader="false">

    <apex:includeScript value="/soap/ajax/26.0/connection.js"/>
    <apex:includeScript value="/soap/ajax/26.0/apex.js"/>



    <apex:pageMessages />


    <apex:form >
    <apex:actionPoller action="{!refreshContext}" rerender="CustomList" interval="5" />



        <apex:pageblock id="CustomList"  Title="Products">         


        <apex:facet name="header">
           <div class="pbHeader">
                <table border="0" cellpadding="0" cellspacing="0">
                    <tbody>
                        <tr>
                            <td class="pbTitle">                               
                                <h3 >Products</h3>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </apex:facet>

            <div style="height: 200px; overflow-y:auto; display:{!IF(ContentAvailable==false,'none','block')}">
            <!-- VERSION JFO -->
            <table class="list" cellspacing="0" cellpadding="0" border="0">
                <tbody>
                    <tr class="headerRow">
                    <th class="actionColumn" scope="col" width="30%">Product Name</th>
                    <th class=" zen-deemphasize" scope="col" width="20%">Last Modified</th>
                    <th class=" zen-deemphasize" scope="col" width="50%" >Wtv</th> 
                    </tr>


                    <!--Products -->
                     <apex:repeat var="prod" value="{!products}">
                        <tr class="dataRow even ">

                            <td> <a href ="" onclick="OpenMethod('{!prod.Id}');return false;"  style="color:black; cursor: pointer;text-decoration: underline;"> {!prod.Name} </a> </td>
                            <td> {!prod.LastModifiedDate}</td>
                            <td>wtv </td>

                        </tr>
                    </apex:repeat> 



                </tbody>
            </table>
            </div>

        <apex:outputLabel value="No records to display" rendered="{!ContentAvailable==false}" styleClass="noRowsHeader">
         </apex:outputLabel>   
    </apex:pageblock>
    </apex:form>
</apex:page>

ApexClass:

public with sharing class  ProductsListController 
{

    public list<Product2 > products {get; set;}

    public Account acc {get;set;}

    public boolean contentAvailable  {get; set;}

    public ProductsListController (ApexPages.StandardController controller) 
    {
        try
        {
            controller.addFields(new list<String>{'id'});

            this.acc = (Account)controller.getRecord();
            loadProducts();
       }
       catch(Exception e)
       {

       }
    }

     private void loadProducts()
     {
        try
        {
            //do query here 
           products = [Select Id, (Id, Name From Products)
            From Opportunity
            Where where AccountID: accId];


            //flag to show products section
            if(products.size()>0)
                contentAvailable= true;
        }
        catch(Exception e)
        {}
    }

    public PageReference refreshContext() 
    {
        loadProducts();
        return null;
    }


}
0

In case if you decide for the coding, here is a very simple example for you. It is using a standard Account controller and a simple extension with a SOQL query that reads a data.

It will display all related opportunities with all related products like this:

Account
- Opp1
  - Product1
- OppX
  - ProductX

Here you can read how to add a visualforce page to the Account layout: How to add a VF page to Event standard pagelauout

Visualforce page:

<apex:page standardController="Account" extensions="MyExtension">
    <apex:pageBlock>
        <apex:pageBlockTable value="{!opps}" var="opp">
            <!-- Opportunity Header -->
            <apex:column value="{!opp.Name}" />
            <!-- related Products -->
            <apex:column breakBefore="true">
                <apex:pageblocksection title="Products" showheader="true" collapsible="true" columns="1">
                    <apex:pageBlockTable value="{!opp.Products}" var="product">
                        <apex:column value="{!product.Name}"/>
                    </apex:pageBlockTable>
                </apex:pageblockSection>
            </apex:column>
        </apex:pageBlockTable>
    </apex:pageBlock>
<apex:page>

Extension:

public class MyExtension {
    private final Account acct;
    public MyExtension(ApexPages.StandardController stdController) {
        this.acct = (Account)stdController.getRecord();
    }

    // You should use your real Product2 relationship name (here just example with name Products)
    public List<Product2> getProducts() {
        return [Select Id, Name, (Id, Name From Products)
                From Opportunity
                Where AccountId = : acct.Id]
    }
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .