[SalesForce] “Validation errors while saving records” when not actually saving anything

I have two Visualforce pages and a custom controller that comprise a wizard to add products to an opportunity (yes I know that's already in there but I had to resort to this because I couldn't remove "Sales Price" from the standard one, and we don't use sales price as we rent and make money from commission).

The first page works fine, it displays a list of all products and a checkbox on the left. The second page will eventually display a number of fields for every item that was selected on the previous page, and then the user can save. However, since adding the apex repeat tag in the second page I've been getting the error: Validation Errors While Saving Record(s) every time I navigate to it.

The error detail reads: There were custom validation error(s) encountered while saving the affected record(s). The first validation error encountered was "Product ID: value not of required type: core.apexpages.el.adapters.metadata.SObjectMetadataELAdapter@4d4bebc6".

The interesting thing is I'm not trying to save any records; not yet at least. The insert statement is in the Save action and the button for that isn't even visible until the last page.

Code below, can anyone help?

Controller

public class NewOpportunityProductsController {
 // PROPERTIES
 public List<SelectableListProduct> prodList {get;set;}
 public List<OpportunityLineItem> itemsToAdd {get;set;}
 public List<PricebookEntry> selProducts {get;set;}
 public Boolean hasSelProd {get;set;}
 public Opportunity owner {get;set;}

 // CONSTRUCTOR
 public NewOpportunityProductsController(){
      prodList = new List<SelectableListProduct>();
      selProducts = new List<PricebookEntry>();
      itemsToAdd = new List<OpportunityLineItem>();
      owner = [SELECT Id FROM Opportunity WHERE Id = :ApexPages.currentPage().getParameters().get('oid')];

      for(PricebookEntry p : [SELECT Product2.Name, Id, Product2.ProductCode, Product2.Standard_Install_Cost__c, Product2.Standard_Monthly_Cost__c, Product2.Supplier__c 
      FROM PricebookEntry WHERE Product2.Name != NULL]){
           prodList.add(new SelectableListProduct(p));
      }

      prodList.sort();
 }

 // METHODS
 private void gatherSelectedProducts(){
      selProducts.clear();
      itemsToAdd.clear();
      hasSelProd = false;
      for(SelectableListProduct cProdWrapper : prodList){
           if(cProdWrapper.isSelected){
                hasSelProd = true;
                selProducts.add(cProdWrapper.cProduct);
           }
      }

      //Create line items
      for (PricebookEntry p : selProducts)
      {
          OpportunityLineItem newLineItem = new OpportunityLineItem();
          newLineItem.OpportunityId = ApexPages.currentPage().getParameters().get('oid');
          newLineItem.PricebookEntryId = p.Id;
          newLineItem.UnitPrice = 0;
          newLineItem.Install_Cost__c = p.Product2.Standard_Install_Cost__c;
          newLineItem.Monthly_Cost__c = p.Product2.Standard_Monthly_Cost__c;
          itemsToAdd.add(newLineItem);
      }
      itemsToAdd.sort();

 }

 public PageReference step1() {
     return Page.SelectNewItems;
 }

 public PageReference step2() {
     gatherSelectedProducts();
     return Page.EditNewItems;
 }

 public PageReference cancel() {
     PageReference opportunityPage = new ApexPages.StandardController(owner).view();
     opportunityPage.setRedirect(true);        
     return opportunityPage;
 }

 public PageReference save() {
     try {
         insert itemsToAdd;
     } catch (System.DMLException e) {
         ApexPages.addMessages(e);
         return null;
     }
     PageReference opportunityPage = new ApexPages.StandardController(owner).view();
     opportunityPage.setRedirect(true);        
     return opportunityPage;
 }
}

Wrapper Class

public class SelectableListProduct implements Comparable
{
     public Boolean isSelected {get;set;}
     public PricebookEntry cProduct {get;set;}

     public SelectableListProduct(PricebookEntry cProduct){
         this.cProduct = cProduct;
     }

 public Integer compareTo(Object comparisonObject)
 {
     SelectableListProduct comparisonProduct = (SelectableListProduct)comparisonObject;
     return cProduct.Product2.Name.compareTo(comparisonProduct.cProduct.Product2.Name);
 }
}

Page One

<apex:page controller="NewOpportunityProductsController" tabstyle="Opportunity">
<apex:sectionHeader title="Add New Opportunity Products" subtitle="Select Products"/>
<apex:form >
  <apex:pageBlock title="Product List">
     <apex:pageBlockButtons >
         <apex:commandButton action="{!step2}" value="Next"/>
         <apex:commandButton action="{!cancel}" value="Cancel"/>
     </apex:pageBlockButtons>
     <!-- PRODUCT LIST -->
     <apex:pageBlockTable value="{!prodList}" 
       var="SelectableListProduct">
        <apex:column >
           <apex:inputCheckbox value="{!SelectableListProduct.isSelected}"/>
        </apex:column>
        <apex:column value="{!SelectableListProduct.cProduct.Product2.Name}"/>
        <apex:column value="{!SelectableListProduct.cProduct.Product2.ProductCode}"/>
        <apex:column value="{!SelectableListProduct.cProduct.Product2.Standard_Install_Cost__c}"/>
        <apex:column value="{!SelectableListProduct.cProduct.Product2.Standard_Monthly_Cost__c}"/>
        <apex:column value="{!SelectableListProduct.cProduct.Product2.Supplier__c}"/>
     </apex:pageBlockTable>

  </apex:pageBlock>
</apex:form>
</apex:page>

Page Two

<apex:page controller="NewOpportunityProductsController" tabStyle="Opportunity">
<apex:sectionHeader title="Add New Opportunity Products" subtitle="Edit Products"/>
<apex:form >
    <apex:pageBlock title="Edit Item Information">
        <apex:pageBlockButtons >
            <apex:commandButton action="{!save}" value="Done"/>
            <apex:commandButton action="{!step1}" value="Back"/>
            <apex:commandButton action="{!cancel}" value="Cancel"/>
        </apex:pageBlockButtons>
            <apex:repeat value="{!itemsToAdd}" var="currentItem">
                <apex:outputField value="{!currentItem.PricebookEntry.Product2}" />
                <apex:inputField value="{!currentItem.Install_Cost__c}" />
                <apex:inputField value="{!currentItem.Monthly_Cost__c}" />
            </apex:repeat>
    </apex:pageBlock>
</apex:form>
</apex:page>

Best Answer

Whenever posting back any data to the controller, the validation rules are ran (even if the records are not saved to the database).

You can postpone this until the final 'save' button by adding an immediate="true" parameter to the step2 commandbutton. Note that you will have to do any validations that you do want to do in the step2 method then.

Related Topic